package mil.nga.geopackage.tiles.features;

import android.content.Context;

import mil.nga.geopackage.BoundingBox;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.extension.link.FeatureTileTableLinker;
import mil.nga.geopackage.tiles.TileBoundingBoxUtils;
import mil.nga.geopackage.tiles.TileGenerator;
import mil.nga.geopackage.tiles.TileGrid;
import mil.nga.sf.proj.Projection;
import mil.nga.sf.proj.ProjectionConstants;
import mil.nga.sf.proj.ProjectionTransform;

/**
 * Creates a set of tiles within a GeoPackage by generating tiles from features
 *
 * @author osbornb
 */
public class FeatureTileGenerator extends TileGenerator {

    /**
     * Feature tiles
     */
    private final FeatureTiles featureTiles;

    /**
     * Flag indicating whether the feature and tile tables should be linked
     */
    private boolean linkTables = true;

    /**
     * Constructor
     *
     * @param context      app context
     * @param geoPackage   GeoPackage
     * @param tableName    table name
     * @param featureTiles feature tiles
     * @param minZoom      min zoom
     * @param maxZoom      max zoom
     * @param boundingBox  tiles bounding box
     * @param projection   tiles projection
     * @since 1.3.0
     */
    public FeatureTileGenerator(Context context, GeoPackage geoPackage,
                                String tableName, FeatureTiles featureTiles, int minZoom, int maxZoom, BoundingBox boundingBox, Projection projection) {
        this(context, geoPackage, tableName, featureTiles, geoPackage, minZoom, maxZoom,
                boundingBox, projection);
    }

    /**
     * Constructor
     *
     * @param context           app context
     * @param geoPackage        GeoPackage
     * @param tableName         table name
     * @param featureTiles      feature tiles
     * @param featureGeoPackage feature GeoPackage if different from the destination
     * @param minZoom           min zoom
     * @param maxZoom           max zoom
     * @param boundingBox       tiles bounding box
     * @param projection        tiles projection
     * @since 3.2.0
     */
    public FeatureTileGenerator(Context context, GeoPackage geoPackage, String tableName,
                                FeatureTiles featureTiles, GeoPackage featureGeoPackage,
                                int minZoom, int maxZoom, BoundingBox boundingBox,
                                Projection projection) {
        super(context, geoPackage, tableName, minZoom, maxZoom, getBoundingBox(
                featureGeoPackage, featureTiles, boundingBox, projection),
                projection);
        this.featureTiles = featureTiles;
    }

    /**
     * Constructor, find the the bounding box from the feature table
     *
     * @param context      app context
     * @param geoPackage   GeoPackage
     * @param tableName    table name
     * @param featureTiles feature tiles
     * @param minZoom      min zoom
     * @param maxZoom      max zoom
     * @param projection   tiles projection
     * @since 3.2.0
     */
    public FeatureTileGenerator(Context context, GeoPackage geoPackage, String tableName,
                                FeatureTiles featureTiles, int minZoom, int maxZoom,
                                Projection projection) {
        this(context, geoPackage, tableName, featureTiles, minZoom, maxZoom, null,
                projection);
    }

    /**
     * Constructor, find the the bounding box from the feature table
     *
     * @param context           app context
     * @param geoPackage        GeoPackage
     * @param tableName         table name
     * @param featureTiles      feature tiles
     * @param featureGeoPackage feature GeoPackage if different from the destination
     * @param minZoom           min zoom
     * @param maxZoom           max zoom
     * @param projection        tiles projection
     * @since 3.2.0
     */
    public FeatureTileGenerator(Context context, GeoPackage geoPackage, String tableName,
                                FeatureTiles featureTiles, GeoPackage featureGeoPackage,
                                int minZoom, int maxZoom, Projection projection) {
        this(context, geoPackage, tableName, featureTiles, featureGeoPackage, minZoom,
                maxZoom, null, projection);
    }

    /**
     * Get the bounding box for the feature tile generator, from the provided
     * and from the feature table
     *
     * @param geoPackage   GeoPackage
     * @param featureTiles feature tiles
     * @param boundingBox  bounding box
     * @param projection   projection
     * @return bounding box
     */
    private static BoundingBox getBoundingBox(GeoPackage geoPackage,
                                              FeatureTiles featureTiles, BoundingBox boundingBox,
                                              Projection projection) {

        String tableName = featureTiles.getFeatureDao().getTableName();
        boolean manualQuery = boundingBox == null;
        BoundingBox featureBoundingBox = geoPackage.getBoundingBox(projection,
                tableName, manualQuery);
        if (featureBoundingBox != null) {
            if (boundingBox == null) {
                boundingBox = featureBoundingBox;
            } else {
                boundingBox = boundingBox.overlap(featureBoundingBox);
            }
        }

        if (boundingBox != null) {
            boundingBox = featureTiles.expandBoundingBox(boundingBox,
                    projection);
        }

        return boundingBox;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BoundingBox getBoundingBox(int zoom) {

        ProjectionTransform projectionToWebMercator = projection
                .getTransformation(ProjectionConstants.EPSG_WEB_MERCATOR);
        BoundingBox webMercatorBoundingBox = boundingBox
                .transform(projectionToWebMercator);

        TileGrid tileGrid = TileBoundingBoxUtils.getTileGrid(webMercatorBoundingBox, zoom);
        BoundingBox tileBoundingBox = TileBoundingBoxUtils.getWebMercatorBoundingBox(
                tileGrid.getMinX(), tileGrid.getMinY(), zoom);

        BoundingBox expandedBoundingBox = featureTiles.expandBoundingBox(webMercatorBoundingBox, tileBoundingBox);

        BoundingBox zoomBoundingBox = expandedBoundingBox.transform(projectionToWebMercator.getInverseTransformation());

        return zoomBoundingBox;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        featureTiles.close();
        super.close();
    }

    /**
     * Is the feature table going to be linked with the tile table? Defaults to
     * true.
     *
     * @return true if tables will be linked upon generation
     * @since 1.2.5
     */
    public boolean isLinkTables() {
        return linkTables;
    }

    /**
     * Set the link tables flag
     *
     * @param linkTables link tables flag
     * @since 1.2.5
     */
    public void setLinkTables(boolean linkTables) {
        this.linkTables = linkTables;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void preTileGeneration() {

        // Link the feature and tile table if they are in the same GeoPackage
        GeoPackage geoPackage = getGeoPackage();
        String featureTable = featureTiles.getFeatureDao().getTableName();
        String tileTable = getTableName();
        if (linkTables && geoPackage.isFeatureTable(featureTable)
                && geoPackage.isTileTable(tileTable)) {
            FeatureTileTableLinker linker = new FeatureTileTableLinker(
                    geoPackage);
            linker.link(featureTable, tileTable);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected byte[] createTile(int z, long x, long y) {

        byte[] tileData = featureTiles.drawTileBytes((int) x, (int) y, z);

        return tileData;
    }

}