package mil.nga.geopackage.tiles.features;

import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import mil.nga.geopackage.BoundingBox;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.db.CoreSQLUtils;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.features.user.FeatureResultSet;
import mil.nga.geopackage.tiles.TileBoundingBoxUtils;
import mil.nga.sf.proj.Projection;
import mil.nga.sf.proj.ProjectionConstants;
import mil.nga.sf.proj.ProjectionFactory;

/**
 * Feature Preview for drawing a preview tile from a feature table
 *
 * @author osbornb
 * @since 3.5.0
 */
public class FeaturePreview {

	/**
	 * GeoPackage
	 */
	private final GeoPackage geoPackage;

	/**
	 * Feature Tiles for drawing
	 */
	private final FeatureTiles featureTiles;

	/**
	 * Manual bounding box query flag for non indexed and empty contents bounds
	 * feature tables
	 */
	private boolean manual = false;

	/**
	 * Buffer percentage for drawing empty non features edges (greater than or equal to 0.0 and less than 0.5)
	 */
	private double bufferPercentage = 0.0;

	/**
	 * Query columns
	 */
	private Set<String> columns = new LinkedHashSet<>();

	/**
	 * Where clause
	 */
	private String where;

	/**
	 * Where clause arguments
	 */
	private String[] whereArgs = null;

	/**
	 * Query feature limit
	 */
	private Integer limit = null;

	/**
	 * Constructor
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param featureTable
	 *            feature table
	 */
	public FeaturePreview(GeoPackage geoPackage, String featureTable) {
		this(geoPackage, geoPackage.getFeatureDao(featureTable));
	}

	/**
	 * Constructor
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param featureDao
	 *            feature DAO
	 */
	public FeaturePreview(GeoPackage geoPackage, FeatureDao featureDao) {
		this(geoPackage, new DefaultFeatureTiles(geoPackage, featureDao));
	}

	/**
	 * Constructor
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param featureTiles
	 *            feature tiles
	 */
	public FeaturePreview(GeoPackage geoPackage, FeatureTiles featureTiles) {
		this.geoPackage = geoPackage;
		this.featureTiles = featureTiles;
		FeatureDao featureDao = featureTiles.getFeatureDao();
		columns.add(featureDao.getIdColumnName());
		columns.add(featureDao.getGeometryColumnName());
		where = CoreSQLUtils.quoteWrap(featureDao.getGeometryColumnName())
				+ " IS NOT NULL";
	}

	/**
	 * Get the GeoPackage
	 * 
	 * @return GeoPackage
	 */
	public GeoPackage getGeoPackage() {
		return geoPackage;
	}

	/**
	 * Get the feature tiles
	 * 
	 * @return feature tiles
	 */
	public FeatureTiles getFeatureTiles() {
		return featureTiles;
	}

	/**
	 * Is manual bounding box query enabled for non indexed and empty contents
	 * bounds feature tables
	 * 
	 * @return manual flag
	 */
	public boolean isManual() {
		return manual;
	}

	/**
	 * Set the manual bounding box query flag for non indexed and empty contents
	 * bounds feature tables
	 * 
	 * @param manual
	 *            manual flag
	 */
	public void setManual(boolean manual) {
		this.manual = manual;
	}

	/**
	 * Get the buffer percentage for drawing empty non features edges (i.e. 0.1
	 * equals 10% buffer edges)
	 * 
	 * @return buffer percentage (greater than or equal to 0.0 and less than 0.5)
	 */
	public double getBufferPercentage() {
		return bufferPercentage;
	}

	/**
	 * Set the buffer percentage for drawing empty non features edges (i.e. 0.1
	 * equals 10% buffer edges)
	 * 
	 * @param bufferPercentage
	 *            buffer percentage (greater than or equal to 0.0 and less than 0.5)
	 */
	public void setBufferPercentage(double bufferPercentage) {
		if (bufferPercentage < 0.0 || bufferPercentage >= 0.5) {
			throw new GeoPackageException(
					"Buffer percentage must be in the range: 0.0 <= bufferPercentage < 0.5. invalid value: "
							+ bufferPercentage);
		}
		this.bufferPercentage = bufferPercentage;
	}

	/**
	 * Get the query columns
	 * 
	 * @return columns
	 */
	public Set<String> getColumns() {
		return Collections.unmodifiableSet(columns);
	}

	/**
	 * Add query columns
	 * 
	 * @param columns
	 *            columns
	 */
	public void addColumns(Collection<String> columns) {
		this.columns.addAll(columns);
	}

	/**
	 * Add query columns
	 * 
	 * @param columns
	 *            columns
	 */
	public void addColumns(String[] columns) {
		for (String column : columns) {
			addColumn(column);
		}
	}

	/**
	 * Add a query column
	 * 
	 * @param column
	 *            column
	 */
	public void addColumn(String column) {
		columns.add(column);
	}

	/**
	 * Get the where clause
	 * 
	 * @return where
	 */
	public String getWhere() {
		return where;
	}

	/**
	 * Set the where clause
	 * 
	 * @param where
	 *            where
	 */
	public void setWhere(String where) {
		this.where = where;
	}

	/**
	 * Append to the where clause
	 * 
	 * @param where
	 *            where
	 */
	public void appendWhere(String where) {
		this.where = (this.where != null ? this.where + " AND " : "") + where;
	}

	/**
	 * Get the where arguments
	 * 
	 * @return where args
	 */
	public String[] getWhereArgs() {
		return whereArgs;
	}

	/**
	 * Set the where arguments
	 * 
	 * @param whereArgs
	 *            where arguments
	 */
	public void setWhereArgs(String[] whereArgs) {
		this.whereArgs = whereArgs;
	}

	/**
	 * Get the feature query limit
	 * 
	 * @return limit
	 */
	public Integer getLimit() {
		return limit;
	}

	/**
	 * Set the feature query limit
	 * 
	 * @param limit
	 *            limit
	 */
	public void setLimit(Integer limit) {
		this.limit = limit;
	}

	/**
	 * Draw a preview image
	 * 
	 * @return preview image
	 */
	public BufferedImage draw() {

		BufferedImage image = null;

		FeatureDao featureDao = featureTiles.getFeatureDao();
		String table = featureDao.getTableName();

		Projection webMercator = ProjectionFactory
				.getProjection(ProjectionConstants.EPSG_WEB_MERCATOR);

		BoundingBox boundingBox = geoPackage.getFeatureBoundingBox(webMercator,
				table, false);
		if (boundingBox == null) {
			boundingBox = geoPackage.getContentsBoundingBox(webMercator, table);
		}
		if (boundingBox == null && manual) {
			boundingBox = geoPackage.getFeatureBoundingBox(webMercator, table,
					manual);
		}
		if (boundingBox != null) {
			boundingBox = TileBoundingBoxUtils
					.boundWebMercatorBoundingBox(boundingBox);
			BoundingBox expandedBoundingBox = boundingBox
					.squareExpand(bufferPercentage);
			expandedBoundingBox = TileBoundingBoxUtils
					.boundWebMercatorBoundingBox(expandedBoundingBox);
			int zoom = TileBoundingBoxUtils.getZoomLevel(expandedBoundingBox);

			FeatureResultSet results = featureDao.query(
					columns.toArray(new String[] {}), where, whereArgs, null,
					null, null, limit != null ? limit.toString() : null);
			image = featureTiles.drawTile(zoom, expandedBoundingBox, results);
		}

		return image;
	}

}