package liquibase.ext.spatial.change;

import java.util.ArrayList;
import java.util.List;

import liquibase.change.AbstractChange;
import liquibase.change.Change;
import liquibase.change.ChangeMetaData;
import liquibase.change.ChangeWithColumns;
import liquibase.change.ColumnConfig;
import liquibase.change.DatabaseChange;
import liquibase.change.DatabaseChangeProperty;
import liquibase.database.Database;
import liquibase.exception.ValidationErrors;
import liquibase.ext.spatial.statement.CreateSpatialIndexStatement;
import liquibase.ext.spatial.xml.XmlConstants;
import liquibase.statement.SqlStatement;
import liquibase.util.StringUtils;

/**
 * The <code>CreateSpatialIndexChange</code> represents a database change to
 * create a spatial index.
 */
@DatabaseChange(name = "createSpatialIndex",
      description = "Creates a spatial index on an existing column or set of columns.",
      priority = ChangeMetaData.PRIORITY_DEFAULT,
      appliesTo = "index")
public class CreateSpatialIndexChange extends AbstractChange
      implements ChangeWithColumns<ColumnConfig> {
   private String catalogName;
   private String schemaName;
   private String tableName;
   private String indexName;
   private String tablespace;
   private List<ColumnConfig> columns = new ArrayList<ColumnConfig>();
   private String geometryType;
   private String srid;

   /**
    * Sets the database catalog name.
    *
    * @param catalogName
    *           the catalog name.
    */
   public void setCatalogName(final String catalogName) {
      this.catalogName = catalogName;
   }

   @DatabaseChangeProperty(description = "Name of the catalog")
   public String getCatalogName() {
      return this.catalogName;
   }

   @DatabaseChangeProperty(mustEqualExisting = "index",
         description = "Name of the index to create",
         requiredForDatabase = "mysql, oracle, postgresql")
   public String getIndexName() {
      return this.indexName;
   }

   public void setIndexName(final String indexName) {
      this.indexName = indexName;
   }

   @DatabaseChangeProperty(mustEqualExisting = "index.schema")
   public String getSchemaName() {
      return this.schemaName;
   }

   public void setSchemaName(final String schemaName) {
      this.schemaName = schemaName;
   }

   @DatabaseChangeProperty(mustEqualExisting = "index.table",
         description = "Name of the table to add the index to",
         exampleValue = "person",
         requiredForDatabase = "all")
   public String getTableName() {
      return this.tableName;
   }

   public void setTableName(final String tableName) {
      this.tableName = tableName;
   }

   /**
    * Returns the geometry type.
    *
    * @return the geometry type.
    */
   @DatabaseChangeProperty(description = "The Well-Known Text geometry type",
         exampleValue = "POINT")
   public String getGeometryType() {
      return this.geometryType;
   }

   /**
    * Sets the geometry type.
    *
    * @param geometryType
    *           the geometry type.
    */
   public void setGeometryType(final String geometryType) {
      this.geometryType = geometryType;
   }

   /**
    * Returns the srid.
    *
    * @return the srid.
    */
   @DatabaseChangeProperty(
         description = "The Spatial Reference ID of the indexed data.  An EPSG SRID is assumed.",
         exampleValue = "4326",
         requiredForDatabase = "derby, h2")
   public String getSrid() {
      return this.srid;
   }

   /**
    * Sets the srid.
    *
    * @param srid
    *           the srid.
    */
   public void setSrid(final String srid) {
      this.srid = srid;
   }

   @Override
   @DatabaseChangeProperty(mustEqualExisting = "index.column",
         description = "Column(s) to add to the index",
         requiredForDatabase = "all")
   public List<ColumnConfig> getColumns() {
      if (this.columns == null) {
         return new ArrayList<ColumnConfig>();
      }
      return this.columns;
   }

   @Override
   public void setColumns(final List<ColumnConfig> columns) {
      this.columns = columns;
   }

   @Override
   public void addColumn(final ColumnConfig column) {
      this.columns.add(column);
   }

   @DatabaseChangeProperty(description = "Tablepace to create the index in.")
   public String getTablespace() {
      return this.tablespace;
   }

   public void setTablespace(final String tablespace) {
      this.tablespace = tablespace;
   }

   /**
    * @see liquibase.change.AbstractChange#validate(liquibase.database.Database)
    */
   @Override
   public ValidationErrors validate(final Database database) {
      final ValidationErrors validationErrors = new ValidationErrors();
      if (this.srid != null) {
         if (!this.srid.matches("[0-9]+")) {
            validationErrors.addError("The SRID must be numeric");
         }
      }

      if (!validationErrors.hasErrors()) {
         validationErrors.addAll(super.validate(database));
      }

      return validationErrors;
   }

   @Override
   public String getConfirmationMessage() {
      final StringBuilder message = new StringBuilder("Spatial index");
      if (StringUtils.trimToNull(getIndexName()) != null) {
         message.append(' ').append(getIndexName().trim());
      }
      message.append(" created");
      if (StringUtils.trimToNull(getTableName()) != null) {
         message.append(" on ").append(getTableName().trim());
      }
      return message.toString();
   }

   @Override
   public SqlStatement[] generateStatements(final Database database) {
      final String[] columns = new String[this.columns.size()];
      int ii = 0;
      for (final ColumnConfig columnConfig : this.columns) {
         columns[ii++] = columnConfig.getName();
      }

      // Parse the string SRID into an integer.
      Integer srid = null;
      if (getSrid() != null) {
         srid = Integer.valueOf(getSrid());
      }

      final CreateSpatialIndexStatement statement = new CreateSpatialIndexStatement(
            getIndexName(), getCatalogName(), getSchemaName(), getTableName(), columns,
            getTablespace(), getGeometryType(), srid);
      return new SqlStatement[] { statement };
   }

   @Override
   protected Change[] createInverses() {
      final DropSpatialIndexChange inverse = new DropSpatialIndexChange();
      inverse.setCatalogName(getCatalogName());
      inverse.setSchemaName(getSchemaName());
      inverse.setTableName(getTableName());
      inverse.setIndexName(getIndexName());

      return new Change[] { inverse };
   }

   @Override
   public String getSerializedObjectNamespace() {
      return XmlConstants.SPATIAL_CHANGELOG_NAMESPACE;
   }
}