/*******************************************************************************
 * 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.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JPanel;

import mobac.exceptions.MapCreationException;
import mobac.gui.mapview.ScaleBar;
import mobac.gui.mapview.WgsGrid;
import mobac.mapsources.mapspace.MercatorPower2MapSpace;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapSource;
import mobac.program.interfaces.MapSpace;
import mobac.program.interfaces.MapSpace.ProjectionCategory;
import mobac.program.model.Settings;
import mobac.program.model.SettingsPaperAtlas;
import mobac.program.model.SettingsWgsGrid;
import mobac.program.model.UnitSystem;
import mobac.utilities.Utilities;

public abstract class PaperAtlas extends AtlasCreator {

	private static final Font PAGE_NUMBER_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 24), LABEL_FONT = new Font(
			Font.SANS_SERIF, Font.BOLD, 12);

	private static final Color LABEL_BACKGROUND = new Color(0, 127, 0), LABEL_FOREGROUND = Color.WHITE,
			IMAGE_BACKGROUND = new Color(0, 0, 0, 0);

	private static final String UP = " \u2191", DOWN = " \u2193";

	protected static class Page {
		protected final BufferedImage image;
		protected final int number, width, height;

		protected Page(int number, BufferedImage image) {
			this.number = number;
			this.image = image;
			this.width = image.getWidth();
			this.height = image.getHeight();
		}
	}

	private final JComponent dummy = new JPanel();
	private final WgsGrid wgsGrid;
	private final int overlap;
	private final Insets insets = new Insets(0, 0, 0, 0);

	protected final SettingsPaperAtlas s;

	private int tileImageScale = 1;

	private File layerFolder;
	// base: page Size整数倍区域，
	// bottom：最底下的不能被page Height整除的区域
	// right：最右侧的不能被page Width整除的区域
	// corner：右下角区域，不能被page Size整除的部分
	private Dimension base, bottom, right, corner;

	protected PaperAtlas(final boolean usePadding) {

		s = Settings.getInstance().paperAtlas.clone();
		overlap = (int) UnitSystem.pointsToPixels(s.overlap, s.dpi);

		if (s.wgsEnabled) {
			SettingsWgsGrid sWgsGrid = Settings.getInstance().wgsGrid.clone();
			sWgsGrid.enabled = s.wgsEnabled;
			sWgsGrid.density = s.wgsDensity;
			wgsGrid = new WgsGrid(sWgsGrid, dummy);
		} else {
			wgsGrid = null;
		}

		if (usePadding) {
			insets.top = (int) UnitSystem.pointsToPixels(s.marginTop, s.dpi);
			insets.left = (int) UnitSystem.pointsToPixels(s.marginLeft, s.dpi);
			insets.bottom = (int) UnitSystem.pointsToPixels(s.marginBottom, s.dpi);
			insets.right = (int) UnitSystem.pointsToPixels(s.marginTop, s.dpi);
		}

		if (s.paperSize != null) {
			double width = s.paperSize.width - s.marginLeft - s.marginRight;
			double height = s.paperSize.height - s.marginTop - s.marginBottom;
			width = UnitSystem.pointsToPixels(width, s.dpi);
			height = UnitSystem.pointsToPixels(height, s.dpi);
			base = new Dimension((int) width, (int) height);
		}
	}

	@Override
	public void createMap() throws MapCreationException, InterruptedException {
		corner = right = bottom = null;

		tileImageScale = getTileImageScale();

		int mapWidth = (xMax - xMin + 1) * tileSize * tileImageScale;
		int mapHeight = (yMax - yMin + 1) * tileSize * tileImageScale;

		if (base == null) {
			base = new Dimension(mapWidth, mapHeight);
			processPages(1, 1);
			base = null;
		} else {

			// 最后一页少于页宽度的crop百分百的，会裁剪掉，即抛弃。
			int sWidth = (mapWidth) % (base.width - overlap);
			if ((double) sWidth / base.getWidth() * 100d < s.crop) {
				sWidth = 0;
			}

			int sHeight = (mapHeight) % (base.height - overlap);
			if ((double) sHeight / base.getHeight() * 100d < s.crop) {
				sHeight = 0;
			}

			if (sHeight > 0 && sWidth > 0) {
				corner = new Dimension(sWidth, sHeight);
			}
			if (sWidth > 0) {
				right = new Dimension(sWidth, base.height);
			}
			if (sHeight > 0) {
				bottom = new Dimension(base.width, sHeight);
			}
			int rows = (mapHeight) / (base.height - overlap) + (bottom != null ? 1 : 0);
			int cols = (mapWidth) / (base.width - overlap) + (right != null ? 1 : 0);
			processPages(rows, cols);
		}
	}

	protected File getLayerFolder() {
		return layerFolder;
	}

	public void initLayerCreation(LayerInterface layer) throws IOException {
		super.initLayerCreation(layer);
		layerFolder = new File(atlasDir, layer.getName());
		Utilities.mkDirs(layerFolder);
	}

	/**
	 * check first existing image and return its scale. 2 means for retina( 512x512), 1 means normal
	 * 
	 * @return
	 */
	protected int getTileImageScale() {
		int tileImageScale = 1;

		if (mapDlTileProvider == null) {
			return tileImageScale;
		}

		// init tileScale for check if there has retina tile
		try {
			// use loop because xMin, xMax may has no data
			for (int x = xMin; x <= xMax; x++) {
				for (int y = yMin; y <= yMax; y++) {
					BufferedImage tile = mapDlTileProvider.getTileImage(x, y);
					if (tile != null) {
						tileImageScale = tile.getWidth() / tileSize;
						break; // skip when get
					}
				}
			}

			// for invalid tileScale
			if (tileImageScale != 2) {
				log.debug("invalid tile Scale for Paper Atlas " + tileImageScale);
				tileImageScale = 1;
			}
		} catch (IOException e) {
			tileImageScale = 1;
		}
		return tileImageScale;
	}

	protected abstract void processPage(BufferedImage image, int pageNumber) throws MapCreationException;

	private void processPages(final int ROWS, final int COLS) throws MapCreationException, InterruptedException {
		try {

			atlasProgress.initMapCreation(ROWS * COLS * 2);
			for (int row = 0; row < ROWS; row++) {
				for (int col = 0; col < COLS; col++) {
					log.trace(String.format("cal=%d row=%d", col, row));

					// Choose image
					Dimension size;

					boolean firstRow = row == 0;
					boolean lastRow = row + 1 == ROWS;
					boolean lastCol = col + 1 == COLS;
					if (corner != null && lastRow && lastCol) {
						size = new Dimension(corner);
					} else if (bottom != null && lastRow) {
						size = new Dimension(bottom);
					} else if (right != null && lastCol) {
						size = new Dimension(right);
					} else {
						size = new Dimension(base);
					}

					// Compute values
					int pageXMin = col * base.width - col * overlap;
					int pageYMin = row * base.height - row * overlap;
					int firstTileX = pageXMin / (tileSize * tileImageScale) + xMin;
					int firstTileY = pageYMin / (tileSize * tileImageScale) + yMin;
					int firstTileXOffset = pageXMin % (tileSize * tileImageScale);
					int firstTileYOffset = pageYMin % (tileSize * tileImageScale);
					int tilesInCol = (size.height + firstTileYOffset - 1) / (tileSize * tileImageScale) + 1;
					int tilesInRow = (size.width + firstTileXOffset - 1) / (tileSize * tileImageScale) + 1;

					// Create image and graphics
					int imageWidth = size.width + insets.left + insets.right;
					int imageHeight = size.height + insets.top + insets.bottom;
					BufferedImage image = Utilities.safeCreateBufferedImage(imageWidth, imageHeight,
							BufferedImage.TYPE_4BYTE_ABGR);
					Graphics2D g = image.createGraphics();
					g.translate(insets.left, insets.top);
					g.clipRect(0, 0, size.width, size.height);
					// Paint image

					g.setBackground(IMAGE_BACKGROUND);
					g.clearRect(0, 0, size.width, size.height);
					g.translate(-firstTileXOffset, -firstTileYOffset);
					for (int tileRow = 0; tileRow < tilesInCol; tileRow++) {
						for (int tileCol = 0; tileCol < tilesInRow; tileCol++) {
							int tileX = firstTileX + tileCol;
							int tileY = firstTileY + tileRow;
							int x = tileCol * tileSize * tileImageScale;
							int y = tileRow * tileSize * tileImageScale;
							BufferedImage tile = mapDlTileProvider.getTileImage(tileX, tileY);
							if (tile != null)
								g.drawImage(tile, x, y, tileSize * tileImageScale, tileSize * tileImageScale, null);
						}
					}
					g.translate(firstTileXOffset, firstTileYOffset);

					// Paint additions
					dummy.setSize(size);
					Point tlc = new Point(firstTileX * tileSize * tileImageScale + firstTileXOffset, firstTileY
							* tileSize * tileImageScale + firstTileYOffset);
					if (s.wgsEnabled)
						wgsGrid.paintWgsGrid(g, mapSource.getMapSpace(), tlc, zoom);
					if (s.scaleBar)
						ScaleBar.paintScaleBar(dummy, g, mapSource.getMapSpace(), tlc, zoom);
					if (s.compass) {
						Image compassRaw = ImageIO.read(Utilities.loadResourceAsStream("images/compass.png"));
						Image compass = compassRaw.getScaledInstance(150, 150, Image.SCALE_SMOOTH);
						g.drawImage(compass, 0, 0, null);
					}
					if (s.pageNumbers) {
						g.setBackground(LABEL_BACKGROUND);
						g.setColor(LABEL_FOREGROUND);

						String pageNumber = " " + getPageNumber(row, col, ROWS, COLS) + " ";
						g.setFont(PAGE_NUMBER_FONT);
						FontMetrics fontMetrics = g.getFontMetrics();
						int fontHeight = fontMetrics.getHeight();
						int pageNumberStringWidth = fontMetrics.stringWidth(pageNumber);
						g.clearRect(0, 0, pageNumberStringWidth, fontHeight);
						g.drawString(pageNumber, 0, fontHeight - fontMetrics.getDescent());

						int centerX = size.width / 2;
						g.setFont(LABEL_FONT);
						fontMetrics = g.getFontMetrics();
						fontHeight = fontMetrics.getHeight();

						if (!firstRow) {
							String text = UP + getPageNumber(row - 1, col, ROWS, COLS) + " ";
							int stringWidth = fontMetrics.stringWidth(text);
							g.clearRect(centerX - stringWidth / 2, 8, stringWidth, fontHeight);
							g.drawString(text, centerX - stringWidth / 2, 8 + fontHeight - fontMetrics.getDescent());
						}
						if (!lastRow) {
							String text = DOWN + getPageNumber(row + 1, col, ROWS, COLS) + " ";
							int stringWidth = fontMetrics.stringWidth(text);

							g.clearRect(centerX - stringWidth / 2, size.height - 32 - fontHeight, stringWidth,
									fontHeight);
							g.drawString(text, centerX - stringWidth / 2, size.height - 32 - fontMetrics.getDescent());
						}
					}
					g.dispose();

					// Process image
					atlasProgress.incMapCreationProgress();
					checkUserAbort();
					processPage(image, getPageNumber(row, col, ROWS, COLS));
					atlasProgress.incMapCreationProgress();
					checkUserAbort();
				}
			}
		} catch (IOException e) {
			throw new MapCreationException(map, e);
		}

	}

	private int getPageNumber(int row, int col, int ROWS, int COLS) {
		return row * COLS + col + 1;
	}

	@Override
	public boolean testMapSource(MapSource mapSource) {
		MapSpace mapSpace = mapSource.getMapSpace();
		ProjectionCategory projection = mapSpace.getProjectionCategory();
		return (mapSpace instanceof MercatorPower2MapSpace)
				&& (ProjectionCategory.SPHERE.equals(projection) || ProjectionCategory.ELLIPSOID.equals(projection));
	}

}
