package mil.nga.geopackage.tiles.features.custom;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.extension.index.GeometryIndex;
import mil.nga.geopackage.features.user.FeatureResultSet;
import mil.nga.geopackage.property.GeoPackageJavaProperties;
import mil.nga.geopackage.property.JavaPropertyConstants;
import mil.nga.geopackage.tiles.features.CustomFeaturesTile;

import com.j256.ormlite.dao.CloseableIterator;

/**
 * Draws a tile indicating the number of features that exist within the tile,
 * visible when zoomed in closer. The number is drawn in the center of the tile
 * and by default is surrounded by a colored circle with border. By default a
 * tile border is drawn and the tile is colored (transparently most likely). The
 * paint objects for each draw type can be modified to or set to null (except
 * for the text paint object).
 * 
 * @author osbornb
 * @since 1.1.2
 */
public class NumberFeaturesTile implements CustomFeaturesTile {

	/**
	 * Text size
	 */
	protected int textSize;

	/**
	 * Text font
	 */
	protected String textFont;

	/**
	 * Text color
	 */
	protected Color textColor;

	/**
	 * Circle stroke width
	 */
	protected float circleStrokeWidth;

	/**
	 * Circle color
	 */
	protected Color circleColor;

	/**
	 * Circle fill color
	 */
	protected Color circleFillColor;

	/**
	 * Tile Border stroke width
	 */
	protected float tileBorderStrokeWidth;

	/**
	 * Tile Border color
	 */
	protected Color tileBorderColor;

	/**
	 * Tile fill color
	 */
	protected Color tileFillColor;

	/**
	 * The percentage of border to include around the edges of the text in the
	 * circle
	 */
	private float circlePaddingPercentage;

	/**
	 * Flag indicating whether tiles should be drawn for feature tables that are
	 * not indexed
	 */
	private boolean drawUnindexedTiles;

	/**
	 * Constructor
	 */
	public NumberFeaturesTile() {

		// Set the default text values
		textSize = GeoPackageJavaProperties.getIntegerProperty(
				JavaPropertyConstants.NUMBER_FEATURES_TILE_TEXT,
				JavaPropertyConstants.NUMBER_FEATURES_TILE_TEXT_SIZE);
		textFont = GeoPackageJavaProperties.getProperty(
				JavaPropertyConstants.NUMBER_FEATURES_TILE_TEXT,
				JavaPropertyConstants.NUMBER_FEATURES_TILE_TEXT_FONT);
		textColor = GeoPackageJavaProperties.getColorProperty(
				JavaPropertyConstants.NUMBER_FEATURES_TILE_TEXT,
				JavaPropertyConstants.NUMBER_FEATURES_TILE_COLOR);

		// Set the default circle paint values
		if (GeoPackageJavaProperties
				.getBooleanProperty(JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_DRAW)) {
			circleStrokeWidth = GeoPackageJavaProperties.getFloatProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_DRAW,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_STROKE_WIDTH);
			circleColor = GeoPackageJavaProperties.getColorProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_DRAW,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_COLOR);
		}

		// Set the default circle fill paint values
		if (GeoPackageJavaProperties
				.getBooleanProperty(JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_FILL)) {
			circleFillColor = GeoPackageJavaProperties.getColorProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_FILL,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_COLOR);
		}

		// Set the default tile border paint values
		if (GeoPackageJavaProperties
				.getBooleanProperty(JavaPropertyConstants.NUMBER_FEATURES_TILE_BORDER)) {
			tileBorderStrokeWidth = GeoPackageJavaProperties.getFloatProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_BORDER,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_STROKE_WIDTH);
			tileBorderColor = GeoPackageJavaProperties.getColorProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_BORDER,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_COLOR);
		}

		// Set the default tile fill paint values
		if (GeoPackageJavaProperties
				.getBooleanProperty(JavaPropertyConstants.NUMBER_FEATURES_TILE_FILL)) {
			tileFillColor = GeoPackageJavaProperties.getColorProperty(
					JavaPropertyConstants.NUMBER_FEATURES_TILE_FILL,
					JavaPropertyConstants.NUMBER_FEATURES_TILE_COLOR);
		}

		// Set the default circle padding percentage
		circlePaddingPercentage = GeoPackageJavaProperties
				.getFloatProperty(
						JavaPropertyConstants.NUMBER_FEATURES_TILE,
						JavaPropertyConstants.NUMBER_FEATURES_TILE_CIRCLE_PADDING_PERCENTAGE);

		// Set the default draw unindexed tiles value
		drawUnindexedTiles = GeoPackageJavaProperties.getBooleanProperty(
				JavaPropertyConstants.NUMBER_FEATURES_TILE,
				JavaPropertyConstants.NUMBER_FEATURES_TILE_UNINDEXED_DRAW);
	}

	/**
	 * Get the text size
	 * 
	 * @return text size
	 */
	public int getTextSize() {
		return textSize;
	}

	/**
	 * Set the text size
	 * 
	 * @param textSize
	 *            text size
	 */
	public void setTextSize(int textSize) {
		this.textSize = textSize;
	}

	/**
	 * Get the text color
	 * 
	 * @return text color
	 */
	public Color getTextColor() {
		return textColor;
	}

	/**
	 * Set the text color
	 * 
	 * @param textColor
	 *            text color
	 */
	public void setTextColor(Color textColor) {
		this.textColor = textColor;
	}

	/**
	 * Get the circle stroke width
	 * 
	 * @return circle stroke width
	 */
	public float getCircleStrokeWidth() {
		return circleStrokeWidth;
	}

	/**
	 * Set the circle stroke width
	 * 
	 * @param circleStrokeWidth
	 *            circle stroke width
	 */
	public void setCircleStrokeWidth(float circleStrokeWidth) {
		this.circleStrokeWidth = circleStrokeWidth;
	}

	/**
	 * Get the circle color
	 * 
	 * @return circle color
	 */
	public Color getCircleColor() {
		return circleColor;
	}

	/**
	 * Set the circle color
	 * 
	 * @param circleColor
	 *            circle color
	 */
	public void setCircleColor(Color circleColor) {
		this.circleColor = circleColor;
	}

	/**
	 * Get the circle fill color
	 * 
	 * @return circle fill color
	 */
	public Color getCircleFillColor() {
		return circleFillColor;
	}

	/**
	 * Set the circle fill color
	 * 
	 * @param circleFillColor
	 *            circle fill color
	 */
	public void setCircleFillColor(Color circleFillColor) {
		this.circleFillColor = circleFillColor;
	}

	/**
	 * Get the circle padding percentage around the text
	 *
	 * @return circle padding percentage, 0.0 to 1.0
	 */
	public float getCirclePaddingPercentage() {
		return circlePaddingPercentage;
	}

	/**
	 * Set the circle padding percentage to pad around the text, value between
	 * 0.0 and 1.0
	 *
	 * @param circlePaddingPercentage
	 *            circle padding percentage
	 */
	public void setCirclePaddingPercentage(float circlePaddingPercentage) {
		if (circlePaddingPercentage < 0.0 || circlePaddingPercentage > 1.0) {
			throw new GeoPackageException(
					"Circle padding percentage must be between 0.0 and 1.0: "
							+ circlePaddingPercentage);
		}
		this.circlePaddingPercentage = circlePaddingPercentage;
	}

	/**
	 * Get the tile border stroke width
	 * 
	 * @return tile border stroke width
	 */
	public float getTileBorderStrokeWidth() {
		return tileBorderStrokeWidth;
	}

	/**
	 * Set the tile border stroke width
	 * 
	 * @param tileBorderStrokeWidth
	 *            tile border stroke width
	 */
	public void setTileBorderStrokeWidth(float tileBorderStrokeWidth) {
		this.tileBorderStrokeWidth = tileBorderStrokeWidth;
	}

	/**
	 * Get the tile border color
	 * 
	 * @return tile border color
	 */
	public Color getTileBorderColor() {
		return tileBorderColor;
	}

	/**
	 * Set the tile border color
	 * 
	 * @param tileBorderColor
	 *            tile border color
	 */
	public void setTileBorderColor(Color tileBorderColor) {
		this.tileBorderColor = tileBorderColor;
	}

	/**
	 * Get the tile fill color
	 * 
	 * @return tile fill color
	 */
	public Color getTileFillColor() {
		return tileFillColor;
	}

	/**
	 * Set the tile fill color
	 * 
	 * @param tileFillColor
	 *            tile fill color
	 */
	public void setTileFillColor(Color tileFillColor) {
		this.tileFillColor = tileFillColor;
	}

	/**
	 * Is the draw unindexed tiles option enabled
	 *
	 * @return true if drawing unindexed tiles
	 */
	public boolean isDrawUnindexedTiles() {
		return drawUnindexedTiles;
	}

	/**
	 * Set the draw unindexed tiles option
	 *
	 * @param drawUnindexedTiles
	 *            draw unindexed tiles flag
	 */
	public void setDrawUnindexedTiles(boolean drawUnindexedTiles) {
		this.drawUnindexedTiles = drawUnindexedTiles;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BufferedImage drawTile(int tileWidth, int tileHeight,
			long tileFeatureCount,
			CloseableIterator<GeometryIndex> geometryIndexResults) {

		String featureText = String.valueOf(tileFeatureCount);
		BufferedImage image = drawTile(tileWidth, tileHeight, featureText);

		return image;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BufferedImage drawUnindexedTile(int tileWidth, int tileHeight,
			long totalFeatureCount, FeatureResultSet allFeatureResults) {

		BufferedImage image = null;

		if (drawUnindexedTiles) {
			// Draw a tile indicating we have no idea if there are features
			// inside.
			// The table is not indexed and more features exist than the max
			// feature count set.
			image = drawTile(tileWidth, tileHeight, "?");
		}

		return image;
	}

	/**
	 * Draw a tile with the provided text label in the middle
	 *
	 * @param tileWidth
	 * @param tileHeight
	 * @param text
	 * @return
	 */
	private BufferedImage drawTile(int tileWidth, int tileHeight, String text) {

		// Create the image and graphics
		BufferedImage image = new BufferedImage(tileWidth, tileHeight,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = image.createGraphics();
		graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		// Draw the tile fill paint
		if (tileFillColor != null) {
			graphics.setColor(tileFillColor);
			graphics.fillRect(0, 0, tileWidth, tileHeight);
		}

		// Draw the tile border
		if (tileBorderColor != null) {
			graphics.setColor(tileBorderColor);
			graphics.setStroke(new BasicStroke(tileBorderStrokeWidth));
			graphics.drawRect(0, 0, tileWidth, tileHeight);
		}

		// Determine the text bounds
		graphics.setFont(new Font(textFont, Font.PLAIN, textSize));
		FontMetrics fontMetrics = graphics.getFontMetrics();
		int textWidth = fontMetrics.stringWidth(text);
		int textHeight = fontMetrics.getAscent();

		// Determine the center of the tile
		int centerX = (int) (image.getWidth() / 2.0f);
		int centerY = (int) (image.getHeight() / 2.0f);

		// Draw the circle
		if (circleColor != null || circleFillColor != null) {
			int diameter = Math.max(textWidth, textHeight);
			float radius = diameter / 2.0f;
			radius = radius + (diameter * circlePaddingPercentage);
			int paddedDiameter = Math.round(radius * 2);

			int circleX = Math.round(centerX - radius);
			int circleY = Math.round(centerY - radius);

			// Draw the filled circle
			if (circleFillColor != null) {
				graphics.setColor(circleFillColor);
				graphics.setStroke(new BasicStroke(circleStrokeWidth));
				graphics.fillOval(circleX, circleY, paddedDiameter,
						paddedDiameter);
			}

			// Draw the circle
			if (circleColor != null) {
				graphics.setColor(circleColor);
				graphics.setStroke(new BasicStroke(circleStrokeWidth));
				graphics.drawOval(circleX, circleY, paddedDiameter,
						paddedDiameter);
			}

		}

		// Draw the text
		float textX = centerX - (textWidth / 2.0f);
		float textY = centerY + (textHeight / 2.0f);
		graphics.setColor(textColor);
		graphics.drawString(text, textX, textY);

		return image;
	}

}