package mil.nga.geopackage.extension;

import java.sql.SQLException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import mil.nga.geopackage.GeoPackageCore;
import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.core.contents.ContentsDataType;
import mil.nga.geopackage.db.AlterTable;
import mil.nga.geopackage.db.CoreSQLUtils;
import mil.nga.geopackage.db.GeoPackageTableCreator;
import mil.nga.geopackage.db.MappedColumn;
import mil.nga.geopackage.db.TableMapping;
import mil.nga.geopackage.db.table.ConstraintParser;
import mil.nga.geopackage.db.table.TableConstraints;
import mil.nga.geopackage.db.table.TableInfo;
import mil.nga.geopackage.extension.coverage.CoverageDataCore;
import mil.nga.geopackage.extension.coverage.GriddedCoverage;
import mil.nga.geopackage.extension.coverage.GriddedCoverageDao;
import mil.nga.geopackage.extension.coverage.GriddedTile;
import mil.nga.geopackage.extension.coverage.GriddedTileDao;
import mil.nga.geopackage.extension.related.ExtendedRelation;
import mil.nga.geopackage.extension.related.ExtendedRelationsDao;
import mil.nga.geopackage.extension.related.RelatedTablesCoreExtension;
import mil.nga.geopackage.features.columns.GeometryColumns;
import mil.nga.geopackage.features.columns.GeometryColumnsDao;
import mil.nga.geopackage.metadata.reference.MetadataReference;
import mil.nga.geopackage.metadata.reference.MetadataReferenceDao;
import mil.nga.geopackage.schema.columns.DataColumns;
import mil.nga.geopackage.schema.columns.DataColumnsDao;
import mil.nga.geopackage.user.custom.UserCustomColumn;
import mil.nga.geopackage.user.custom.UserCustomTable;
import mil.nga.geopackage.user.custom.UserCustomTableReader;

/**
 * GeoPackage extension management class for deleting extensions for a table or
 * in a GeoPackage
 * 
 * @author osbornb
 * @since 1.1.8
 */
public class GeoPackageExtensions {

	/**
	 * Logger
	 */
	private static final Logger logger = Logger
			.getLogger(GeoPackageExtensions.class.getName());

	/**
	 * Delete all table extensions for the table within the GeoPackage
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 */
	public static void deleteTableExtensions(GeoPackageCore geoPackage,
			String table) {

		// Handle deleting any extensions with extra tables here
		NGAExtensions.deleteTableExtensions(geoPackage, table);

		deleteRTreeSpatialIndex(geoPackage, table);
		deleteRelatedTables(geoPackage, table);
		deleteGriddedCoverage(geoPackage, table);
		deleteSchema(geoPackage, table);
		deleteMetadata(geoPackage, table);

		delete(geoPackage, table);
	}

	/**
	 * Delete all extensions
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 */
	public static void deleteExtensions(GeoPackageCore geoPackage) {

		// Handle deleting any extensions with extra tables here
		NGAExtensions.deleteExtensions(geoPackage);

		deleteRTreeSpatialIndexExtension(geoPackage);
		deleteRelatedTablesExtension(geoPackage);
		deleteGriddedCoverageExtension(geoPackage);
		deleteSchemaExtension(geoPackage);
		deleteMetadataExtension(geoPackage);
		deleteCrsWktExtension(geoPackage);

		delete(geoPackage);
	}

	/**
	 * Copy all table extensions for the table within the GeoPackage
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copyTableExtensions(GeoPackageCore geoPackage,
			String table, String newTable) {

		try {

			copyRTreeSpatialIndex(geoPackage, table, newTable);
			copyRelatedTables(geoPackage, table, newTable);
			copyGriddedCoverage(geoPackage, table, newTable);
			copySchema(geoPackage, table, newTable);
			copyMetadata(geoPackage, table, newTable);

			// Handle copying any extensions with extra tables here
			NGAExtensions.copyTableExtensions(geoPackage, table, newTable);

		} catch (Exception e) {
			logger.log(Level.WARNING, "Failed to copy extensions for table: "
					+ newTable + ", copied from table: " + table, e);
		}

	}

	/**
	 * Delete the extensions for the table
	 * 
	 * @param geoPackage
	 * @param table
	 */
	private static void delete(GeoPackageCore geoPackage, String table) {

		ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

		try {
			if (extensionsDao.isTableExists()) {
				extensionsDao.deleteByTableName(table);
			}
		} catch (SQLException e) {
			throw new GeoPackageException(
					"Failed to delete Table extensions. GeoPackage: "
							+ geoPackage.getName() + ", Table: " + table,
					e);
		}
	}

	/**
	 * Delete the extensions
	 * 
	 * @param geoPackage
	 */
	private static void delete(GeoPackageCore geoPackage) {

		ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

		try {
			if (extensionsDao.isTableExists()) {
				geoPackage.dropTable(extensionsDao.getTableName());
			}
		} catch (SQLException e) {
			throw new GeoPackageException(
					"Failed to delete all extensions. GeoPackage: "
							+ geoPackage.getName(),
					e);
		}

	}

	/**
	 * Delete the RTree Spatial extension for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @since 3.2.0
	 */
	public static void deleteRTreeSpatialIndex(GeoPackageCore geoPackage,
			String table) {

		RTreeIndexCoreExtension rTreeIndexExtension = getRTreeIndexExtension(
				geoPackage);
		if (rTreeIndexExtension.has(table)) {
			rTreeIndexExtension.delete(table);
		}

	}

	/**
	 * Delete the RTree Spatial extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteRTreeSpatialIndexExtension(
			GeoPackageCore geoPackage) {

		RTreeIndexCoreExtension rTreeIndexExtension = getRTreeIndexExtension(
				geoPackage);
		if (rTreeIndexExtension.has()) {
			rTreeIndexExtension.deleteAll();
		}

	}

	/**
	 * Copy the RTree Spatial extension for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copyRTreeSpatialIndex(GeoPackageCore geoPackage,
			String table, String newTable) {

		try {

			RTreeIndexCoreExtension rTreeIndexExtension = getRTreeIndexExtension(
					geoPackage);
			if (rTreeIndexExtension.has(table)) {
				GeometryColumnsDao geometryColumnsDao = geoPackage
						.getGeometryColumnsDao();

				GeometryColumns geometryColumns = geometryColumnsDao
						.queryForTableName(newTable);
				if (geometryColumns != null) {
					TableInfo tableInfo = TableInfo
							.info(geoPackage.getDatabase(), newTable);
					if (tableInfo != null) {
						String pk = tableInfo.getPrimaryKey().getName();
						rTreeIndexExtension.create(newTable,
								geometryColumns.getColumnName(), pk);
					}
				}
			}

		} catch (Exception e) {
			logger.log(Level.WARNING, "Failed to create RTree for table: "
					+ newTable + ", copied from table: " + table, e);
		}
	}

	/**
	 * Get a RTree Index Extension used only for deletions
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @return RTree index extension
	 */
	private static RTreeIndexCoreExtension getRTreeIndexExtension(
			GeoPackageCore geoPackage) {
		return new RTreeIndexCoreExtension(geoPackage) {
			@Override
			public void createMinYFunction() {
			}

			@Override
			public void createMinXFunction() {
			}

			@Override
			public void createMaxYFunction() {
			}

			@Override
			public void createMaxXFunction() {
			}

			@Override
			public void createIsEmptyFunction() {
			}
		};
	}

	/**
	 * Delete the Related Tables extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @since 3.2.0
	 */
	public static void deleteRelatedTables(GeoPackageCore geoPackage,
			String table) {

		RelatedTablesCoreExtension relatedTablesExtension = getRelatedTableExtension(
				geoPackage);
		if (relatedTablesExtension.has()) {
			relatedTablesExtension.removeRelationships(table);
		}

	}

	/**
	 * Delete the Related Tables extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteRelatedTablesExtension(GeoPackageCore geoPackage) {

		RelatedTablesCoreExtension relatedTablesExtension = getRelatedTableExtension(
				geoPackage);
		if (relatedTablesExtension.has()) {
			relatedTablesExtension.removeExtension();
		}

	}

	/**
	 * Copy the Related Tables extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copyRelatedTables(GeoPackageCore geoPackage,
			String table, String newTable) {

		try {

			RelatedTablesCoreExtension relatedTablesExtension = getRelatedTableExtension(
					geoPackage);
			if (relatedTablesExtension.has()) {

				ExtendedRelationsDao extendedRelationsDao = relatedTablesExtension
						.getExtendedRelationsDao();
				ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

				List<ExtendedRelation> extendedRelations = extendedRelationsDao
						.getBaseTableRelations(table);
				for (ExtendedRelation extendedRelation : extendedRelations) {

					String mappingTableName = extendedRelation
							.getMappingTableName();

					List<Extensions> extensions = extensionsDao
							.queryByExtension(
									RelatedTablesCoreExtension.EXTENSION_NAME,
									mappingTableName);

					if (!extensions.isEmpty()) {

						String newMappingTableName = CoreSQLUtils.createName(
								geoPackage.getDatabase(), mappingTableName,
								table, newTable);

						UserCustomTable userTable = UserCustomTableReader
								.readTable(geoPackage.getDatabase(),
										mappingTableName);
						AlterTable.copyTable(geoPackage.getDatabase(),
								userTable, newMappingTableName);

						Extensions extension = extensions.get(0);
						extension.setTableName(newMappingTableName);
						extensionsDao.create(extension);

						TableMapping extendedRelationTableMapping = new TableMapping(
								geoPackage.getDatabase(),
								ExtendedRelation.TABLE_NAME);
						extendedRelationTableMapping
								.removeColumn(ExtendedRelation.COLUMN_ID);
						MappedColumn baseTableNameColumn = extendedRelationTableMapping
								.getColumn(
										ExtendedRelation.COLUMN_BASE_TABLE_NAME);
						baseTableNameColumn.setConstantValue(newTable);
						baseTableNameColumn.setWhereValue(table);
						MappedColumn mappingTableNameColumn = extendedRelationTableMapping
								.getColumn(
										ExtendedRelation.COLUMN_MAPPING_TABLE_NAME);
						mappingTableNameColumn
								.setConstantValue(newMappingTableName);
						mappingTableNameColumn.setWhereValue(mappingTableName);
						CoreSQLUtils.transferTableContent(
								geoPackage.getDatabase(),
								extendedRelationTableMapping);

					}
				}
			}

		} catch (Exception e) {
			logger.log(Level.WARNING,
					"Failed to create Related Tables for table: " + newTable
							+ ", copied from table: " + table,
					e);
		}

	}

	/**
	 * Get a Related Table Extension used only for deletions
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @return Related Table Extension
	 */
	private static RelatedTablesCoreExtension getRelatedTableExtension(
			GeoPackageCore geoPackage) {
		return new RelatedTablesCoreExtension(geoPackage) {
		};
	}

	/**
	 * Delete the Gridded Coverage extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @since 3.2.0
	 */
	public static void deleteGriddedCoverage(GeoPackageCore geoPackage,
			String table) {

		if (geoPackage.isTableType(ContentsDataType.GRIDDED_COVERAGE, table)) {

			GriddedTileDao griddedTileDao = geoPackage.getGriddedTileDao();
			GriddedCoverageDao griddedCoverageDao = geoPackage
					.getGriddedCoverageDao();
			ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

			try {
				if (griddedTileDao.isTableExists()) {
					griddedTileDao.delete(table);
				}
				if (griddedCoverageDao.isTableExists()) {
					griddedCoverageDao.delete(table);
				}
				if (extensionsDao.isTableExists()) {
					extensionsDao.deleteByExtension(
							CoverageDataCore.EXTENSION_NAME, table);
				}
			} catch (SQLException e) {
				throw new GeoPackageException(
						"Failed to delete Table Index. GeoPackage: "
								+ geoPackage.getName() + ", Table: " + table,
						e);
			}
		}

	}

	/**
	 * Delete the Gridded Coverage extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteGriddedCoverageExtension(
			GeoPackageCore geoPackage) {

		List<String> coverageTables = geoPackage
				.getTables(ContentsDataType.GRIDDED_COVERAGE);
		for (String table : coverageTables) {
			geoPackage.deleteTable(table);
		}

		GriddedTileDao griddedTileDao = geoPackage.getGriddedTileDao();
		GriddedCoverageDao griddedCoverageDao = geoPackage
				.getGriddedCoverageDao();
		ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

		try {
			if (griddedTileDao.isTableExists()) {
				geoPackage.dropTable(griddedTileDao.getTableName());
			}
			if (griddedCoverageDao.isTableExists()) {
				geoPackage.dropTable(griddedCoverageDao.getTableName());
			}
			if (extensionsDao.isTableExists()) {
				extensionsDao
						.deleteByExtension(CoverageDataCore.EXTENSION_NAME);
			}
		} catch (SQLException e) {
			throw new GeoPackageException(
					"Failed to delete Gridded Coverage extension and tables. GeoPackage: "
							+ geoPackage.getName(),
					e);
		}

	}

	/**
	 * Copy the Gridded Coverage extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copyGriddedCoverage(GeoPackageCore geoPackage,
			String table, String newTable) {

		try {

			if (geoPackage.isTableType(ContentsDataType.GRIDDED_COVERAGE,
					table)) {

				ExtensionsDao extensionsDao = geoPackage.getExtensionsDao();

				if (extensionsDao.isTableExists()) {

					List<Extensions> extensions = extensionsDao
							.queryByExtension(CoverageDataCore.EXTENSION_NAME,
									table);

					if (!extensions.isEmpty()) {

						Extensions extension = extensions.get(0);
						extension.setTableName(newTable);
						extensionsDao.create(extension);

						GriddedCoverageDao griddedCoverageDao = geoPackage
								.getGriddedCoverageDao();
						if (griddedCoverageDao.isTableExists()) {

							CoreSQLUtils.transferTableContent(
									geoPackage.getDatabase(),
									GriddedCoverage.TABLE_NAME,
									GriddedCoverage.COLUMN_TILE_MATRIX_SET_NAME,
									newTable, table, GriddedCoverage.COLUMN_ID);

						}

						GriddedTileDao griddedTileDao = geoPackage
								.getGriddedTileDao();
						if (griddedTileDao.isTableExists()) {

							CoreSQLUtils.transferTableContent(
									geoPackage.getDatabase(),
									GriddedTile.TABLE_NAME,
									GriddedTile.COLUMN_TABLE_NAME, newTable,
									table, GriddedTile.COLUMN_ID);

						}
					}
				}
			}

		} catch (Exception e) {
			logger.log(Level.WARNING,
					"Failed to create Gridded Coverage for table: " + newTable
							+ ", copied from table: " + table,
					e);
		}

	}

	/**
	 * Delete the Schema extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @since 3.2.0
	 */
	public static void deleteSchema(GeoPackageCore geoPackage, String table) {

		DataColumnsDao dataColumnsDao = geoPackage.getDataColumnsDao();
		try {
			if (dataColumnsDao.isTableExists()) {
				dataColumnsDao.deleteByTableName(table);
			}
		} catch (SQLException e) {
			throw new GeoPackageException(
					"Failed to delete Schema extension. GeoPackage: "
							+ geoPackage.getName() + ", Table: " + table,
					e);
		}

	}

	/**
	 * Delete the Schema extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteSchemaExtension(GeoPackageCore geoPackage) {

		SchemaExtension schemaExtension = new SchemaExtension(geoPackage);
		if (schemaExtension.has()) {
			schemaExtension.removeExtension();
		}

	}

	/**
	 * Copy the Schema extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copySchema(GeoPackageCore geoPackage, String table,
			String newTable) {

		try {

			if (geoPackage.isTable(DataColumns.TABLE_NAME)) {

				UserCustomTable dataColumnsTable = UserCustomTableReader
						.readTable(geoPackage.getDatabase(),
								DataColumns.TABLE_NAME);
				UserCustomColumn nameColumn = dataColumnsTable
						.getColumn(DataColumns.COLUMN_NAME);
				if (nameColumn.hasConstraints()) {
					nameColumn.clearConstraints();
					if (dataColumnsTable.hasConstraints()) {
						dataColumnsTable.clearConstraints();
						String constraintSql = GeoPackageTableCreator
								.readSQLScript(
										GeoPackageTableCreator.DATA_COLUMNS)
								.get(0);
						TableConstraints constraints = ConstraintParser
								.getConstraints(constraintSql);
						dataColumnsTable.addConstraints(
								constraints.getTableConstraints());
					}
					AlterTable.alterColumn(geoPackage.getDatabase(),
							dataColumnsTable, nameColumn);
				}

				CoreSQLUtils.transferTableContent(geoPackage.getDatabase(),
						DataColumns.TABLE_NAME, DataColumns.COLUMN_TABLE_NAME,
						newTable, table);

			}

		} catch (Exception e) {
			logger.log(Level.WARNING, "Failed to create Schema for table: "
					+ newTable + ", copied from table: " + table, e);
		}

	}

	/**
	 * Delete the Metadata extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @since 3.2.0
	 */
	public static void deleteMetadata(GeoPackageCore geoPackage, String table) {

		MetadataReferenceDao metadataReferenceDao = geoPackage
				.getMetadataReferenceDao();
		try {
			if (metadataReferenceDao.isTableExists()) {
				metadataReferenceDao.deleteByTableName(table);
			}
		} catch (SQLException e) {
			throw new GeoPackageException(
					"Failed to delete Metadata extension. GeoPackage: "
							+ geoPackage.getName() + ", Table: " + table,
					e);
		}

	}

	/**
	 * Delete the Metadata extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteMetadataExtension(GeoPackageCore geoPackage) {

		MetadataExtension metadataExtension = new MetadataExtension(geoPackage);
		if (metadataExtension.has()) {
			metadataExtension.removeExtension();
		}

	}

	/**
	 * Copy the Metadata extensions for the table
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @param table
	 *            table name
	 * @param newTable
	 *            new table name
	 * @since 3.3.0
	 */
	public static void copyMetadata(GeoPackageCore geoPackage, String table,
			String newTable) {

		try {

			if (geoPackage.isTable(MetadataReference.TABLE_NAME)) {

				CoreSQLUtils.transferTableContent(geoPackage.getDatabase(),
						MetadataReference.TABLE_NAME,
						MetadataReference.COLUMN_TABLE_NAME, newTable, table);

			}

		} catch (Exception e) {
			logger.log(Level.WARNING, "Failed to create Metadata for table: "
					+ newTable + ", copied from table: " + table, e);
		}

	}

	/**
	 * Delete the WKT for Coordinate Reference Systems extension
	 * 
	 * @param geoPackage
	 *            GeoPackage
	 * @since 3.2.0
	 */
	public static void deleteCrsWktExtension(GeoPackageCore geoPackage) {

		CrsWktExtension crsWktExtension = new CrsWktExtension(geoPackage);
		if (crsWktExtension.has()) {
			crsWktExtension.removeExtension();
		}

	}

}