/*******************************************************************************
 * 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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EnumSet;

import org.apache.log4j.Logger;

import mobac.exceptions.AtlasTestException;
import mobac.exceptions.MapCreationException;
import mobac.gui.AtlasProgress;
import mobac.program.AtlasThread;
import mobac.program.PauseResumeHandler;
import mobac.program.atlascreators.tileprovider.TileProvider;
import mobac.program.interfaces.AtlasInterface;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.MapSource;
import mobac.program.model.AtlasOutputFormat;
import mobac.program.model.Settings;
import mobac.program.model.TileImageFormat;
import mobac.program.model.TileImageParameters;
import mobac.utilities.Utilities;

/**
 * Abstract base class for all AtlasCreator implementations.
 * 
 * The general call schema is as follows:
 * <ol>
 * <li>AtlasCreator instantiation via {@link AtlasOutputFormat#createAtlasCreatorInstance()}</li>
 * <li>AtlasCreator atlas initialization via {@link #startAtlasCreation(AtlasInterface, File)}</li>
 * <li>1 to n times {@link #initializeMap(MapInterface, TileProvider)} followed by {@link #createMap()}</li>
 * <li>AtlasCreator atlas finalization via {@link #finishAtlasCreation()}</li>
 * </ol>
 */
public abstract class AtlasCreator {

	public static final Charset TEXT_FILE_CHARSET = StandardCharsets.ISO_8859_1;

	protected final Logger log;

	/************************************************************/
	/** atlas specific fields **/
	/************************************************************/

	protected AtlasInterface atlas;

	protected File atlasDir;

	protected AtlasProgress atlasProgress = null;

	protected PauseResumeHandler pauseResumeHandler = null;

	/************************************************************/
	/** map specific fields **/
	/************************************************************/

	protected MapInterface map;
	protected int xMin;
	protected int xMax;
	protected int yMin;
	protected int yMax;
	protected int zoom;
	protected MapSource mapSource;
	protected int tileSize;

	/**
	 * Custom tile processing parameters. <code>null</code> if disabled in GUI
	 */
	protected TileImageParameters parameters;

	protected AtlasOutputFormat atlasOutputFormat;

	protected TileProvider mapDlTileProvider;

	private boolean aborted = false;

	/**
	 * Default constructor - initializes the logging environment
	 */
	protected AtlasCreator() {
		log = Logger.getLogger(this.getClass());
	};

	/**
	 * @param customAtlasDir
	 *            if not <code>null</code> the customAtlasDir is used instead of the generated atlas directory name
	 * @throws InterruptedException
	 * @see AtlasCreator
	 */
	public void startAtlasCreation(AtlasInterface atlas, File customAtlasDir) throws AtlasTestException, IOException,
			InterruptedException {
		this.atlas = atlas;
		testAtlas();

		if (customAtlasDir == null) {
			// No explicit atlas output directory has been set - generate a new directory name
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
			String atlasDirName = atlas.getName() + "_" + sdf.format(new Date());
			File atlasOutputDir = Settings.getInstance().getAtlasOutputDirectory();
			atlasDir = new File(atlasOutputDir, atlasDirName);
		} else
			atlasDir = customAtlasDir;
		Utilities.mkDirs(atlasDir);
	}

	protected void testAtlas() throws AtlasTestException {
	}

	public void initLayerCreation(LayerInterface layer) throws IOException {

	}

	public void finishLayerCreation() throws IOException {

	}

	/**
	 * @throws InterruptedException
	 * @see AtlasCreator
	 */
	public void finishAtlasCreation() throws IOException, InterruptedException {
	}

	public void abortAtlasCreation() throws IOException {
		this.aborted = true;
	}

	public boolean isAborted() {
		return aborted;
	}

	/**
	 * Test if the {@link AtlasCreator} instance supports the selected {@link MapSource}
	 * 
	 * @param mapSource
	 * @return <code>true</code> if supported otherwise <code>false</code>
	 * @see AtlasCreator
	 */
	public abstract boolean testMapSource(MapSource mapSource);

	/**
	 * @see AtlasCreator
	 */
	public void initializeMap(MapInterface map, TileProvider mapTileProvider) {
		LayerInterface layer = map.getLayer();
		if (mapTileProvider == null)
			throw new NullPointerException();
		this.mapDlTileProvider = mapTileProvider;
		this.map = map;
		this.mapSource = map.getMapSource();
		this.tileSize = mapSource.getMapSpace().getTileSize();
		this.parameters = map.getParameters();
		xMin = map.getMinTileCoordinate().x / tileSize;
		xMax = map.getMaxTileCoordinate().x / tileSize;
		yMin = map.getMinTileCoordinate().y / tileSize;
		yMax = map.getMaxTileCoordinate().y / tileSize;
		this.zoom = map.getZoom();
		this.atlasOutputFormat = layer.getAtlas().getOutputFormat();

		Thread t = Thread.currentThread();
		if (!(t instanceof AtlasThread))
			throw new RuntimeException("Calling thread must be AtlasThread!");
		AtlasThread at = (AtlasThread) t;
		atlasProgress = at.getAtlasProgress();
		pauseResumeHandler = at.getPauseResumeHandler();
	}

	/**
	 * @throws InterruptedException
	 * @see AtlasCreator
	 */
	public abstract void createMap() throws MapCreationException, InterruptedException;

	/**
	 * Checks if the user has aborted atlas creation and if <code>true</code> an {@link InterruptedException} is thrown.
	 * 
	 * @throws InterruptedException
	 */
	public void checkUserAbort() throws InterruptedException {
		if (Thread.currentThread().isInterrupted())
			throw new InterruptedException();
		pauseResumeHandler.pauseWait();
	}

	public AtlasProgress getAtlasProgress() {
		return atlasProgress;
	}

	public int getXMin() {
		return xMin;
	}

	public int getXMax() {
		return xMax;
	}

	public int getYMin() {
		return yMin;
	}

	public int getYMax() {
		return yMax;
	}

	public MapInterface getMap() {
		return map;
	}

	public TileImageParameters getParameters() {
		return parameters;
	}

	public TileProvider getMapDlTileProvider() {
		return mapDlTileProvider;
	}

	/**
	 * Tests all maps of the currently active atlas if a custom tile image format has been specified and if the
	 * specified format is equal to the <code>allowedFormat</code>.
	 * 
	 * @param allowedFormat
	 * @throws AtlasTestException
	 */
	protected void performTest_AtlasTileFormat(EnumSet<TileImageFormat> allowedFormats) throws AtlasTestException {
		for (LayerInterface layer : atlas) {
			for (MapInterface map : layer) {
				TileImageParameters parameters = map.getParameters();
				if (parameters == null)
					continue;
				if (!allowedFormats.contains(parameters.getFormat()))
					throw new AtlasTestException(
							"Selected custom tile format not supported - only the following format(s) are supported: "
									+ allowedFormats, map);
			}
		}
	}

	protected void performTest_MaxMapZoom(int maxZoom) throws AtlasTestException {
		for (LayerInterface layer : atlas) {
			for (MapInterface map : layer) {
				if (map.getZoom() > maxZoom)
					throw new AtlasTestException("Maximum zoom is " + maxZoom + " for this atlas format", map);
			}
		}
	}

}
