package org.osmdroid.gpkg.tiles.raster;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;

import org.osmdroid.api.IMapView;
import org.osmdroid.config.Configuration;
import org.osmdroid.tileprovider.modules.IFilesystemCache;
import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.util.MapTileIndex;
import org.osmdroid.util.TileSystem;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageManager;
import mil.nga.geopackage.factory.GeoPackageFactory;
import mil.nga.geopackage.projection.ProjectionConstants;
import mil.nga.geopackage.projection.ProjectionTransform;
import mil.nga.geopackage.tiles.retriever.GeoPackageTile;
import mil.nga.geopackage.tiles.retriever.GeoPackageTileRetriever;
import mil.nga.geopackage.tiles.user.TileDao;

/**
 * Geopackage raster tile provider
 * Created by alex on 10/29/15.
 */
public class GeoPackageMapTileModuleProvider extends MapTileModuleProviderBase {

    private final TileSystem tileSystem = org.osmdroid.views.MapView.getTileSystem();

    //TileRetriever retriever;
    protected IFilesystemCache tileWriter = null;
    protected GeoPackageManager manager;

    protected GeopackageRasterTileSource currentTileSource;
    protected Set<GeoPackage> tileSources = new HashSet<>();

    public GeoPackageMapTileModuleProvider(File[] pFile,
                                           final Context context, IFilesystemCache cache) {
        //int pThreadPoolSize, final int pPendingQueueSize
        super(Configuration.getInstance().getTileFileSystemThreads(), Configuration.getInstance().getTileFileSystemMaxQueueSize());
        Log.i(IMapView.LOGTAG, "Geopackage support is BETA. Please report any issues");
        tileWriter = cache;
        // Get a manager
        manager = GeoPackageFactory.getManager(context);
        // Available databases


        // Import database
        for (int i = 0; i < pFile.length; i++) {
            try {
                manager.importGeoPackage((pFile[i]));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        // Available databases
        List<String> databases = manager.databases();
        // Open database
        for (int i = 0; i < databases.size(); i++) {
            tileSources.add(manager.open(databases.get(i)));
        }

    }


    public Drawable getMapTile(final long pMapTileIndex) {
        Drawable tile = null;

        String database = currentTileSource.getDatabase();
        String table = currentTileSource.getTableDao();
        GeoPackage next = manager.open(database);

        TileDao tileDao = next.getTileDao(table);
        GeoPackageTileRetriever retriever = new GeoPackageTileRetriever(tileDao);

        int zoom = MapTileIndex.getZoom(pMapTileIndex);
        int x = MapTileIndex.getX(pMapTileIndex);
        int y = MapTileIndex.getY(pMapTileIndex);


        GeoPackageTile geoPackageTile = retriever.getTile(x, y, zoom);
        if (geoPackageTile != null && geoPackageTile.data != null) {
            byte[] image = geoPackageTile.data;
            if (image != null) {
                BitmapFactory.Options opt = new BitmapFactory.Options();
                opt.outHeight = 256; //360
                opt.outWidth = 256;//248
                Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt);
                tile = new BitmapDrawable(imageBitmap);

            }
        }
        next.close();

        return tile;

    }


    /**
     * returns ALL available raster tile sources for all "imported" geopackage databases
     *
     * @return
     */
    public List<GeopackageRasterTileSource> getTileSources() {
        List<GeopackageRasterTileSource> srcs = new ArrayList<>();

        List<String> databases = manager.databases();
        for (int i = 0; i < databases.size(); i++) {

            GeoPackage open = manager.open(databases.get(i));
            List<String> tileTables = open.getTileTables();
            for (int k = 0; k < tileTables.size(); k++) {
                TileDao tileDao = open.getTileDao(tileTables.get(k));

                ProjectionTransform transform = tileDao.getProjection().getTransformation(ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM);
                mil.nga.geopackage.BoundingBox boundingBox = transform.transform(tileDao.getBoundingBox());
                BoundingBox bounds = new BoundingBox(Math.min(tileSystem.getMaxLatitude(), boundingBox.getMaxLatitude()),
                    boundingBox.getMaxLongitude(),
                    Math.max(tileSystem.getMinLatitude(), boundingBox.getMinLatitude()),
                    boundingBox.getMinLongitude());

                srcs.add(new GeopackageRasterTileSource(databases.get(i), tileTables.get(k), (int)tileDao.getMinZoom(), (int)tileDao.getMaxZoom(), bounds));
            }
            open.close();
        }

        return srcs;
    }

    /**
     * returns ALL available raster tile sources for the specified database.
     * This will throw if the database doesn't exist or isn't registered
     *
     * @return
     */
    public List<GeopackageRasterTileSource> getTileSources(String database) {
        List<GeopackageRasterTileSource> srcs = new ArrayList<>();

        GeoPackage open = manager.open(database);
        List<String> tileTables = open.getTileTables();
        for (int k = 0; k < tileTables.size(); k++) {
            TileDao tileDao = open.getTileDao(tileTables.get(k));

            ProjectionTransform transform = tileDao.getProjection().getTransformation(ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM);
            mil.nga.geopackage.BoundingBox boundingBox = transform.transform(tileDao.getBoundingBox());

            BoundingBox bounds = new BoundingBox(Math.min(tileSystem.getMaxLatitude(), boundingBox.getMaxLatitude()),
                boundingBox.getMaxLongitude(),
                Math.max(tileSystem.getMinLatitude(), boundingBox.getMinLatitude()),
                boundingBox.getMinLongitude());
            srcs.add(new GeopackageRasterTileSource(database, tileTables.get(k), (int)tileDao.getMinZoom(), (int)tileDao.getMaxZoom(), bounds));

        }
        open.close();

        return srcs;
    }

    @Override
    public void detach() {


        if (tileSources != null) {
            Iterator<GeoPackage> iterator = tileSources.iterator();
            while (iterator.hasNext()) {
                iterator.next().close();
            }
            tileSources.clear();
        }
        manager = null;
    }


    protected class TileLoader extends MapTileModuleProviderBase.TileLoader {

        @Override
        public Drawable loadTile(final long pMapTileIndex) {
            try {
                Drawable mapTile = getMapTile(pMapTileIndex);
                return mapTile;
            } catch (final Throwable e) {
                Log.e(IMapView.LOGTAG, "Error loading tile", e);
            } finally {
            }

            return null;
        }
    }

    @Override
    protected String getName() {
        return "Geopackage";
    }

    @Override
    protected String getThreadGroupName() {
        return getName();
    }

    @Override
    public TileLoader getTileLoader() {
        return new TileLoader();
    }

    @Override
    public boolean getUsesDataConnection() {
        return false;
    }

    @Override
    public int getMinimumZoomLevel() {
        if (currentTileSource != null)
            return currentTileSource.getMinimumZoomLevel();
        return 0;
    }

    @Override
    public int getMaximumZoomLevel() {
        if (currentTileSource != null)
            return currentTileSource.getMaximumZoomLevel();
        return 22;
    }

    @Override
    public void setTileSource(ITileSource tileSource) {
        if (tileSource instanceof GeopackageRasterTileSource)
            currentTileSource = (GeopackageRasterTileSource) tileSource;

    }


}