/*******************************************************************************
 * 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.awt.Point;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EnumSet;

import mobac.exceptions.AtlasTestException;
import mobac.exceptions.MapCreationException;
import mobac.mapsources.mapspace.MercatorPower2MapSpace;
import mobac.program.annotations.AtlasCreatorName;
import mobac.program.annotations.SupportedParameters;
import mobac.program.atlascreators.impl.MapTileBuilder;
import mobac.program.atlascreators.impl.MapTileWriter;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.MapSource;
import mobac.program.model.TileImageFormat;
import mobac.program.model.TileImageParameters;
import mobac.program.model.TileImageType;

/**
 * The following SQL statements create SQLite database for background map:
 * 
 * BEGIN TRANSACTION; CREATE TABLE images(zoom int, x int, y int, flags int, length int, data blob); CREATE TABLE
 * version(version int); INSERT INTO version VALUES(5); INSERT INTO version VALUES(0);
 * 
 * CREATE INDEX index1 on images (zoom,x,y,flags); COMMIT;
 * 
 * The columns in "images" table have the following meaning:
 * 
 * zoom - zoom level from 1 (top level) to 23 (most detailed level). On the top level we have four squares 128x128
 * pixels, it gives 512x512 pixels. So map width = map height = 256 * 2zoom pixels.
 * 
 * x, y - coordinates for given zoom level:
 * 
 * 
 * 
 * flags - layer number. Theoretically map could contain number of layers: street, satellite, hybrid and so on.
 * Practically the program shows only one layer with smallest flags value.
 * 
 * length - size of binary image in bytes.
 * 
 * data - binary tile image in PNG or JPEG format. Practically the program supports only PNG for now.
 * 
 * For more details regarding Web Mercator projection and coordinates please see:
 * 
 * http://msdn.microsoft.com/en-us/library/bb259689.aspx
 */
@AtlasCreatorName(value = "iPhone 3 Map Tiles v5")
@SupportedParameters(names = {})
public class IPhone3MapTiles5 extends RMapsSQLite {

	private static final String INSERT_SQL = "INSERT or REPLACE INTO images(x,y,zoom,data,length,flags) VALUES (?,?,?,?,?,0);";
	private static final String TABLE_IMAGES = "CREATE TABLE IF NOT EXISTS images(zoom int, x int, y int, flags int, length int, data blob); ";
	private static final String TABLE_VERSION = "CREATE TABLE IF NOT EXISTS version(version int);";
	private static final String TABLE_VERSION_DATA = "INSERT INTO version VALUES(5);";
	private static final String INDEX_IMAGES = "CREATE INDEX IF NOT EXISTS index1 on images (zoom,x,y,flags);";

	@Override
	public boolean testMapSource(MapSource mapSource) {
		return MercatorPower2MapSpace.INSTANCE_256.equals(mapSource.getMapSpace());
	}

	@Override
	protected void testAtlas() throws AtlasTestException {
		EnumSet<TileImageType> allowed = EnumSet.of(TileImageType.JPG, TileImageType.PNG);
		// Test of output format - only jpg xor png is allowed
		for (LayerInterface layer : atlas) {
			for (MapInterface map : layer) {
				TileImageParameters parameters = map.getParameters();
				TileImageType currentTit;
				if (parameters == null) {
					currentTit = map.getMapSource().getTileImageType();
					if (!allowed.contains(currentTit))
						throw new AtlasTestException(
								"Map source format incompatible - tile format conversion to PNG or JPG is required for this map.",
								map);
				} else {
					currentTit = parameters.getFormat().getType();
					if (!allowed.contains(currentTit))
						throw new AtlasTestException(
								"Selected custom tile format not supported - only JPG and PNG formats are supported.",
								map);
				}
			}
		}
	}

	@Override
	protected void openConnection() throws SQLException, IOException {
		if (databaseFile.isFile()) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
			databaseFile = new File(atlasDir, atlas.getName() + "_" + sdf.format(new Date()) + ".sqlitedb");
		}
		super.openConnection();
	}

	@Override
	protected void initializeDB() throws SQLException {
		Statement stat = conn.createStatement();
		stat.executeUpdate(TABLE_IMAGES);
		stat.executeUpdate(INDEX_IMAGES);
		if (stat.executeUpdate(TABLE_VERSION) == 0)
			stat.execute(TABLE_VERSION_DATA);
		stat.close();
	}

	@Override
	protected void createTiles() throws InterruptedException, MapCreationException {

		try {
			parameters = new TileImageParameters(128, 128, TileImageFormat.PNG);
			conn.setAutoCommit(false);
			prepStmt = conn.prepareStatement(getTileInsertSQL());

			MapTileWriter mapTileWriter = new SQLiteMapTileWriter(map);
			MapTileBuilder mapTileBuilder = new MapTileBuilder(this, mapTileWriter, true);
			atlasProgress.initMapCreation(mapTileBuilder.getCustomTileCount());
			mapTileBuilder.createTiles();
			mapTileWriter.finalizeMap();
			prepStmt.close();
		} catch (SQLException e) {
			throw new MapCreationException(map, e);
		} catch (IOException e) {
			Throwable t = e;
			if (t.getCause() instanceof SQLException)
				t = t.getCause();
			throw new MapCreationException(map, t);
		}
	}

	@Override
	protected void updateTileMetaInfo() throws SQLException {
	}

	@Override
	protected String getTileInsertSQL() {
		return INSERT_SQL;
	}

	protected String getDatabaseFileName() {
		return atlas.getName() + ".sqlitedb";
	}

	private class SQLiteMapTileWriter implements MapTileWriter {

		private final int z;
		private final int baseTileX;
		private final int baseTileY;

		SQLiteMapTileWriter(MapInterface map) {
			this.z = map.getZoom() + 1;
			Point topLeft = map.getMinTileCoordinate();
			baseTileX = topLeft.x / 128;
			baseTileY = topLeft.y / 128;
		}

		@Override
		public void writeTile(int x, int y, String tileType, byte[] tileData) throws IOException {
			x += baseTileX;
			y += baseTileY;
			try {
				prepStmt.setInt(1, x);
				prepStmt.setInt(2, y);
				prepStmt.setInt(3, z);
				prepStmt.setBytes(4, tileData);
				prepStmt.setInt(5, tileData.length);
				prepStmt.addBatch();
			} catch (SQLException e) {
				throw new IOException(e);
			}
		}

		@Override
		public void finalizeMap() throws IOException {
			try {
				prepStmt.executeBatch();
				prepStmt.clearBatch();
				conn.commit();
			} catch (SQLException e) {
				throw new IOException(e);
			}
		}

	}

}
