/*
 * Copyright 2010, 2011, 2012, 2013 mapsforge.org
 * Copyright 2014 Christian Pesch
 * Copyright 2014 Ludwig M Brinckmann
 * Copyright 2014-2018 devemux86
 * Copyright 2017 usrusr
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.insa.base;

import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.prefs.Preferences;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;

import org.insa.graph.Arc;
import org.insa.graph.Graph;
import org.insa.graph.Path;
import org.insa.graph.io.BinaryGraphReader;
import org.insa.graph.io.BinaryPathReader;
import org.insa.graph.io.Openfile;
import org.mapsforge.core.graphics.Color;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.model.Point;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.core.util.Parameters;
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
import org.mapsforge.map.awt.util.AwtUtil;
import org.mapsforge.map.awt.util.JavaPreferences;
import org.mapsforge.map.awt.view.MapView;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.datastore.MultiMapDataStore;
import org.mapsforge.map.layer.Layers;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.debug.TileCoordinatesLayer;
import org.mapsforge.map.layer.debug.TileGridLayer;
import org.mapsforge.map.layer.download.TileDownloadLayer;
import org.mapsforge.map.layer.download.tilesource.OpenStreetMapMapnik;
import org.mapsforge.map.layer.download.tilesource.TileSource;
import org.mapsforge.map.layer.hills.DiffuseLightShadingAlgorithm;
import org.mapsforge.map.layer.hills.HillsRenderConfig;
import org.mapsforge.map.layer.hills.MemoryCachingHgtReaderTileSource;
import org.mapsforge.map.layer.overlay.Polyline;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.model.Model;
import org.mapsforge.map.model.common.PreferencesFacade;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;

public final class Samples {
	private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
	private static final boolean SHOW_DEBUG_LAYERS = false;
	private static final boolean SHOW_RASTER_MAP = false;

	private static final String MESSAGE = "Are you sure you want to exit the application?";
	private static final String TITLE = "Confirm close";

	/**
	 * Starts the {@code Samples}.
	 *
	 * @param args
	 *            command line args: expects the map files as multiple parameters
	 *            with possible SRTM hgt folder as 1st argument.
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		// Multithreaded map rendering
		Parameters.NUMBER_OF_THREADS = 2;

		// Square frame buffer
		Parameters.SQUARE_FRAME_BUFFER = false;

		HillsRenderConfig hillsCfg = null;
		File demFolder = getDemFolder(args);
		if (demFolder != null) {
			MemoryCachingHgtReaderTileSource tileSource = new MemoryCachingHgtReaderTileSource(
					demFolder, new DiffuseLightShadingAlgorithm(), AwtGraphicFactory.INSTANCE);
			tileSource.setEnableInterpolationOverlap(true);
			hillsCfg = new HillsRenderConfig(tileSource);
			hillsCfg.indexOnThread();
			args = Arrays.copyOfRange(args, 1, args.length);
		}

		List<File> mapFiles = getMapFiles(args);
		final MapView mapView = createMapView();
		final BoundingBox boundingBox = addLayers(mapView, mapFiles, hillsCfg);

		// addAPath(mapView);

		final PreferencesFacade preferencesFacade = new JavaPreferences(
				Preferences.userNodeForPackage(Samples.class));

		final JFrame frame = new JFrame();
		frame.setTitle("Mapsforge Samples");
		frame.add(mapView);
		frame.pack();
		frame.setSize(new Dimension(800, 600));
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				int result = JOptionPane.showConfirmDialog(frame, MESSAGE, TITLE,
						JOptionPane.YES_NO_OPTION);
				if (result == JOptionPane.YES_OPTION) {
					mapView.getModel().save(preferencesFacade);
					mapView.destroyAll();
					AwtGraphicFactory.clearResourceMemoryCache();
					frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
				}
			}

			@Override
			public void windowOpened(WindowEvent e) {
				final Model model = mapView.getModel();
				model.init(preferencesFacade);
				if (model.mapViewPosition.getZoomLevel() == 0
						|| !boundingBox.contains(model.mapViewPosition.getCenter())) {
					byte zoomLevel = LatLongUtils.zoomForBounds(
							model.mapViewDimension.getDimension(), boundingBox,
							model.displayModel.getTileSize());
					model.mapViewPosition.setMapPosition(
							new MapPosition(boundingBox.getCenterPoint(), zoomLevel));
				}
			}
		});
		frame.setVisible(true);
	}

	private static void addAPath(MapView mapView) throws Exception {

		Graph gr = (new BinaryGraphReader(Openfile.open("Maps/midip.map"))).read();
		Path path = (new BinaryPathReader(Openfile.open("Paths/chemin_0x400_119963_96676.path")))
				.readPath(gr);

		Paint paintStroke = AwtGraphicFactory.INSTANCE.createPaint();
		paintStroke.setColor(Color.GREEN);
		paintStroke.setStrokeWidth(3);
		paintStroke.setStyle(Style.STROKE);

		Polyline line = new Polyline(paintStroke, AwtGraphicFactory.INSTANCE);

		for (Arc arc : path.getArcs()) {
			ArrayList<org.insa.graph.Point> points = arc.getPoints();
			for (int i = 0; i < points.size(); ++i) {
				line.getLatLongs().add(
						new LatLong(points.get(i).getLatitude(), points.get(i).getLongitude()));
			}
		}

		mapView.getLayerManager().getLayers().add(line);
	}

	private static BoundingBox addLayers(MapView mapView, List<File> mapFiles,
			HillsRenderConfig hillsRenderConfig) {
		Layers layers = mapView.getLayerManager().getLayers();

		int tileSize = SHOW_RASTER_MAP ? 256 : 512;

		// Tile cache
		TileCache tileCache = AwtUtil.createTileCache(tileSize,
				mapView.getModel().frameBufferModel.getOverdrawFactor(), 1024,
				new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()));

		final BoundingBox boundingBox;
		if (SHOW_RASTER_MAP) {
			// Raster
			mapView.getModel().displayModel.setFixedTileSize(tileSize);
			TileSource tileSource = OpenStreetMapMapnik.INSTANCE;
			TileDownloadLayer tileDownloadLayer = createTileDownloadLayer(tileCache,
					mapView.getModel().mapViewPosition, tileSource);
			layers.add(tileDownloadLayer);
			tileDownloadLayer.start();
			mapView.setZoomLevelMin(tileSource.getZoomLevelMin());
			mapView.setZoomLevelMax(tileSource.getZoomLevelMax());
			boundingBox = new BoundingBox(LatLongUtils.LATITUDE_MIN, LatLongUtils.LONGITUDE_MIN,
					LatLongUtils.LATITUDE_MAX, LatLongUtils.LONGITUDE_MAX);
		}
		else {
			// Vector
			mapView.getModel().displayModel.setFixedTileSize(tileSize);
			MultiMapDataStore mapDataStore = new MultiMapDataStore(
					MultiMapDataStore.DataPolicy.RETURN_ALL);
			for (File file : mapFiles) {
				mapDataStore.addMapDataStore(new MapFile(file), false, false);
			}
			TileRendererLayer tileRendererLayer = createTileRendererLayer(tileCache, mapDataStore,
					mapView.getModel().mapViewPosition, hillsRenderConfig);
			layers.add(tileRendererLayer);
			boundingBox = mapDataStore.boundingBox();
		}

		// Debug
		if (SHOW_DEBUG_LAYERS) {
			layers.add(new TileGridLayer(GRAPHIC_FACTORY, mapView.getModel().displayModel));
			layers.add(new TileCoordinatesLayer(GRAPHIC_FACTORY, mapView.getModel().displayModel));
		}

		return boundingBox;
	}

	private static MapView createMapView() {
		MapView mapView = new MapView();
		mapView.getMapScaleBar().setVisible(true);
		if (SHOW_DEBUG_LAYERS) {
			mapView.getFpsCounter().setVisible(true);
		}

		return mapView;
	}

	@SuppressWarnings("unused")
	private static TileDownloadLayer createTileDownloadLayer(TileCache tileCache,
			MapViewPosition mapViewPosition, TileSource tileSource) {
		return new TileDownloadLayer(tileCache, mapViewPosition, tileSource, GRAPHIC_FACTORY) {
			@Override
			public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) {
				System.out.println("Tap on: " + tapLatLong);
				return true;
			}
		};
	}

	private static TileRendererLayer createTileRendererLayer(TileCache tileCache,
			MapDataStore mapDataStore, MapViewPosition mapViewPosition,
			HillsRenderConfig hillsRenderConfig) {
		TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapDataStore,
				mapViewPosition, false, true, false, GRAPHIC_FACTORY, hillsRenderConfig) {
			@Override
			public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) {
				System.out.println("Tap on: " + tapLatLong);
				return true;
			}
		};
		tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.DEFAULT);
		return tileRendererLayer;
	}

	private static File getDemFolder(String[] args) {
		if (args.length == 0) {
			throw new IllegalArgumentException("missing argument: <mapFile>");
		}

		File demFolder = new File(args[0]);
		if (demFolder.exists() && demFolder.isDirectory() && demFolder.canRead()) {
			return demFolder;
		}
		return null;
	}

	private static List<File> getMapFiles(String[] args) {
		if (args.length == 0) {
			throw new IllegalArgumentException("missing argument: <mapFile>");
		}

		List<File> result = new ArrayList<>();
		for (String arg : args) {
			File mapFile = new File(arg);
			if (!mapFile.exists()) {
				throw new IllegalArgumentException("file does not exist: " + mapFile);
			}
			else if (!mapFile.isFile()) {
				throw new IllegalArgumentException("not a file: " + mapFile);
			}
			else if (!mapFile.canRead()) {
				throw new IllegalArgumentException("cannot read file: " + mapFile);
			}
			result.add(mapFile);
		}
		return result;
	}

	private Samples() {
		throw new IllegalStateException();
	}
}