package mil.nga.geopackage.test;

import java.io.File;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;

import mil.nga.geopackage.BoundingBox;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.core.srs.SpatialReferenceSystem;
import mil.nga.geopackage.features.columns.GeometryColumns;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.features.user.FeatureRow;
import mil.nga.geopackage.geom.GeoPackageGeometryData;
import mil.nga.geopackage.manager.GeoPackageManager;
import mil.nga.geopackage.schema.TableColumnKey;
import mil.nga.sf.Geometry;
import mil.nga.sf.LineString;
import mil.nga.sf.Point;
import mil.nga.sf.Polygon;
import mil.nga.sf.proj.ProjectionConstants;
import mil.nga.sf.util.GeometryEnvelopeBuilder;

/**
 * For testing performance of feature inserts through duration logging and
 * profiling
 * 
 * @author osbornb
 */
public class GeoPackagePerformance {

	private static final Logger LOGGER = Logger
			.getLogger(GeoPackagePerformance.class.getName());
	private static final String GEOPACKAGE_FILE = "performance.gpkg";
	private static final String TABLE_NAME = "features";
	private static final String COLUMN_NAME = "geom";
	private static final int CREATE_COUNT = 50000000;
	private static final boolean AUTO_COMMIT = false;
	private static final int COMMIT_CHUNK = 1000;
	private static final int LOG_CHUNK = 1000000;

	/**
	 * Test feature inserts
	 * 
	 * @param args
	 *            arguments
	 * @throws SQLException
	 *             upon failure
	 */
	public static void main(String[] args) throws SQLException {

		File file = new File(GEOPACKAGE_FILE);
		if (file.exists()) {
			file.delete();
		}

		LOGGER.log(Level.INFO, "File: " + file.getAbsolutePath());
		LOGGER.log(Level.INFO, "Table Name: " + TABLE_NAME);
		LOGGER.log(Level.INFO, "Column Name: " + COLUMN_NAME);
		LOGGER.log(Level.INFO, "Features: " + CREATE_COUNT);
		LOGGER.log(Level.INFO, "Auto Commit: " + AUTO_COMMIT);
		if (!AUTO_COMMIT) {
			LOGGER.log(Level.INFO, "Commit Chunk: " + COMMIT_CHUNK);
		}
		if (LOG_CHUNK > 0) {
			LOGGER.log(Level.INFO, "Log Chunk: " + LOG_CHUNK);
		}

		GeoPackageManager.create(file);

		GeoPackage geoPackage = GeoPackageManager.open(file);

		Geometry geometry = createGeometry();

		GeometryColumns geometryColumns = new GeometryColumns();
		geometryColumns.setId(new TableColumnKey(TABLE_NAME, COLUMN_NAME));
		geometryColumns.setGeometryType(geometry.getGeometryType());
		geometryColumns.setZ((byte) 0);
		geometryColumns.setM((byte) 0);

		BoundingBox boundingBox = new BoundingBox(
				GeometryEnvelopeBuilder.buildEnvelope(geometry));

		SpatialReferenceSystem srs = geoPackage.getSpatialReferenceSystemDao()
				.getOrCreateCode(ProjectionConstants.AUTHORITY_EPSG,
						ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM);

		geoPackage.createFeatureTableWithMetadata(geometryColumns, boundingBox,
				srs.getId());

		GeoPackageGeometryData geometryData = new GeoPackageGeometryData(
				srs.getSrsId());
		geometryData.setGeometry(geometry);

		FeatureDao dao = geoPackage.getFeatureDao(geometryColumns);

		if (AUTO_COMMIT) {
			dao.getConnection().setAutoCommit(true);
		} else {
			dao.beginTransaction();
		}
		// Connection connection = dao.getConnection();
		// connection.setAutoCommit(AUTO_COMMIT);

		try {

			Instant startTime = Instant.now();
			Instant logTime = Instant.now();

			for (int count = 1; count <= CREATE_COUNT; count++) {

				FeatureRow newRow = dao.newRow();
				newRow.setGeometry(geometryData);

				dao.create(newRow);

				if (!AUTO_COMMIT && count % COMMIT_CHUNK == 0) {
					dao.commit();
				}

				if (LOG_CHUNK > 0 && count % LOG_CHUNK == 0) {
					Instant time = Instant.now();
					LOGGER.log(Level.INFO, "Total Count: " + count);
					Duration duration = Duration.between(logTime, time);
					LOGGER.log(Level.INFO, "Chunk Time: "
							+ duration.toString().substring(2));
					LOGGER.log(Level.INFO,
							"Chunk Average: "
									+ (duration.toMillis() / (float) LOG_CHUNK)
									+ " ms");
					Duration totalDuration = Duration.between(startTime, time);
					LOGGER.log(Level.INFO, "Total Time: "
							+ totalDuration.toString().substring(2));
					LOGGER.log(
							Level.INFO,
							"Feature Average: "
									+ (totalDuration.toMillis() / (float) count)
									+ " ms");
					logTime = time;
				}

			}

			if (!AUTO_COMMIT) {
				dao.endTransaction();
			}

		} catch (Exception e) {
			if (!AUTO_COMMIT) {
				dao.failTransaction();
			}
			throw e;
		}

		geoPackage.close();

		geoPackage = GeoPackageManager.open(file);
		dao = geoPackage.getFeatureDao(TABLE_NAME);
		LOGGER.log(Level.INFO, "Final Count: " + dao.count());
		geoPackage.close();

	}

	private static Geometry createGeometry() {

		Polygon polygon = new Polygon();
		LineString ring = new LineString();
		ring.addPoint(new Point(-104.802246, 39.720343));
		ring.addPoint(new Point(-104.802246, 39.719753));
		ring.addPoint(new Point(-104.802183, 39.719754));
		ring.addPoint(new Point(-104.802184, 39.719719));
		ring.addPoint(new Point(-104.802138, 39.719694));
		ring.addPoint(new Point(-104.802097, 39.719691));
		ring.addPoint(new Point(-104.802096, 39.719648));
		ring.addPoint(new Point(-104.801646, 39.719648));
		ring.addPoint(new Point(-104.801644, 39.719722));
		ring.addPoint(new Point(-104.801550, 39.719723));
		ring.addPoint(new Point(-104.801549, 39.720207));
		ring.addPoint(new Point(-104.801648, 39.720207));
		ring.addPoint(new Point(-104.801648, 39.720341));
		ring.addPoint(new Point(-104.802246, 39.720343));
		polygon.addRing(ring);

		return polygon;
	}

}