/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mobac.program.atlascreators;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

import javax.imageio.ImageIO;

import mobac.exceptions.AtlasTestException;
import mobac.exceptions.MapCreationException;
import mobac.program.annotations.AtlasCreatorName;
import mobac.program.annotations.SupportedParameters;
import mobac.program.atlascreators.tileprovider.ConvertedRawTileProvider;
import mobac.program.atlascreators.tileprovider.TileProvider;
import mobac.program.interfaces.AtlasInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.MapSource;
import mobac.program.interfaces.MapSpace.ProjectionCategory;
import mobac.program.interfaces.RequiresSQLite;
import mobac.program.model.TileImageParameters.Name;
import mobac.utilities.jdbc.SQLiteLoader;

/**
 * http://sourceforge.net/p/mobac/patches/22/
 */
@AtlasCreatorName("Osmdroid SQLite")
@SupportedParameters(names = { Name.format })
public class OsmdroidSQLite extends AtlasCreator implements RequiresSQLite {

	protected Connection conn = null;

	public OsmdroidSQLite() {
		super();
		SQLiteLoader.loadSQLiteOrShowError();
	}

	@Override
	public boolean testMapSource(MapSource mapSource) {
		return mapSource.getMapSpace().getProjectionCategory().equals(ProjectionCategory.SPHERE);
	}

	@Override
	public void startAtlasCreation(AtlasInterface atlas, File customAtlasDir) throws IOException, AtlasTestException,
			InterruptedException {
		super.startAtlasCreation(atlas, customAtlasDir);
		String databaseFile = new File(atlasDir, atlas.getName() + ".sqlite").getAbsolutePath();
		try {
			SQLiteLoader.loadSQLite();
		} catch (SQLException e) {
			throw new AtlasTestException(SQLiteLoader.getMsgSqliteMissing());
		}
		try {
			conn = DriverManager.getConnection("jdbc:sqlite:" + databaseFile);
			Statement stat = conn.createStatement();
			stat.executeUpdate("CREATE TABLE IF NOT EXISTS tiles (key INTEGER PRIMARY KEY, provider TEXT, tile BLOB)");
			stat.close();
		} catch (SQLException e) {
			throw new IOException("Error creating SQL database \"" + databaseFile + "\": " + e.getMessage(), e);
		}
		log.debug("SQLite Database file: " + databaseFile);
	}

	@Override
	public void initializeMap(MapInterface map, TileProvider mapTileProvider) {
		super.initializeMap(map, mapTileProvider);
		if (parameters != null)
			mapDlTileProvider = new ConvertedRawTileProvider(mapDlTileProvider, parameters.getFormat());
	}

	@Override
	public void createMap() throws MapCreationException, InterruptedException {
		try {
			String provider = map.getMapSource().getName();
			int maxMapProgress = 2 * (xMax - xMin + 1) * (yMax - yMin + 1);
			atlasProgress.initMapCreation(maxMapProgress);
			conn.setAutoCommit(false);
			int batchTileCount = 0;

			ImageIO.setUseCache(false);
			PreparedStatement prep = conn.prepareStatement("INSERT or REPLACE INTO tiles VALUES (?, ?, ?);");
			Runtime r = Runtime.getRuntime();
			long heapMaxSize = r.maxMemory();

			for (long x = xMin; x <= xMax; x++)
				for (long y = yMin; y <= yMax; y++) {
					checkUserAbort();
					atlasProgress.incMapCreationProgress();
					byte[] sourceTileData = mapDlTileProvider.getTileData((int) x, (int) y);
					if (sourceTileData != null) {
						long z = zoom;
						long index = (((z << z) + x) << z) + y;
						prep.setLong(1, index);
						prep.setString(2, provider);
						prep.setBytes(3, sourceTileData);
						prep.addBatch();

						long heapAvailable = heapMaxSize - r.totalMemory() + r.freeMemory();

						if ((heapAvailable < HEAP_MIN) || (batchTileCount >= MAX_BATCH_SIZE)) {
							log.trace("Executing batch containing " + batchTileCount + " tiles");
							prep.executeBatch();
							prep.clearBatch();
							System.gc();
							conn.commit();
							atlasProgress.incMapCreationProgress(batchTileCount);
							batchTileCount = 0;
						}

					}
				}
			prep.executeBatch();
			conn.setAutoCommit(true);
			atlasProgress.setMapCreationProgress(maxMapProgress);
		} catch (SQLException e) {
			throw new MapCreationException("Error writing tile image: " + e.getMessage(), map, e);
		} catch (IOException e) {
			throw new MapCreationException("Error writing tile image: " + e.getMessage(), map, e);
		}
	}

	@Override
	public void abortAtlasCreation() throws IOException {
		SQLiteLoader.closeConnection(conn);
		conn = null;
		super.abortAtlasCreation();
	}

	@Override
	public void finishAtlasCreation() throws IOException, InterruptedException {
		SQLiteLoader.closeConnection(conn);
		conn = null;
		super.finishAtlasCreation();
	}

}
