package liquibase.ext.spatial.datatype;

import liquibase.database.Database;
import liquibase.database.core.DerbyDatabase;
import liquibase.database.core.H2Database;
import liquibase.database.core.OracleDatabase;
import liquibase.database.core.PostgresDatabase;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;

/**
 * The <code>GeometryType</code> assists in defining database-specific geometry types and converting
 * SQL representations of geometries.
 */
@DataTypeInfo(name = "geometry", aliases = { "com.vividsolutions.jts.geom.Geometry" }, minParameters = 0, maxParameters = 2, priority = LiquibaseDataType.PRIORITY_DEFAULT)
public class GeometryType extends LiquibaseDataType {
   /**
    * Returns the value geometry type parameter.
    * 
    * @return the geometry type or <code>null</code> if not present.
    */
   public String getGeometryType() {
      String geometryType = null;
      if (getParameters().length > 0 && getParameters()[0] != null) {
         geometryType = getParameters()[0].toString();
      }
      return geometryType;
   }

   /**
    * Returns the value SRID parameter.
    * 
    * @return the SRID or <code>null</code> if not present.
    */
   public Integer getSRID() {
      Integer srid = null;
      if (getParameters().length > 1 && getParameters()[1] != null) {
         srid = Integer.valueOf(getParameters()[1].toString());
      }
      return srid;
   }

   /**
    * Creates the appropriate Geometry <code>DatabaseDataType</code>.
    */
   @Override
   public DatabaseDataType toDatabaseDataType(final Database database) {
      final DatabaseDataType databaseDataType;
      if (database instanceof DerbyDatabase) {
         databaseDataType = new DatabaseDataType("VARCHAR(32672) FOR BIT DATA");
      } else if (database instanceof H2Database) {
         // User's wanting to use a BLOB can use modifySql.
         databaseDataType = new DatabaseDataType("BINARY");
      } else if (database instanceof OracleDatabase) {
         databaseDataType = new DatabaseDataType("SDO_GEOMETRY");
      } else if (database instanceof PostgresDatabase) {
         databaseDataType = new DatabaseDataType(getName(), getParameters());
      } else {
         databaseDataType = new DatabaseDataType("GEOMETRY");
      }
      return databaseDataType;
   }

   /**
    * @see liquibase.datatype.LiquibaseDataType#objectToSql(java.lang.Object,
    *      liquibase.database.Database)
    */
   @Override
   public String objectToSql(final Object value, final Database database) {
      final String returnValue;
      if (value instanceof Geometry) {
         // TODO: Tailor the output for the database.
         returnValue = ((Geometry) value).toText();
      } else if (value instanceof String) {
         returnValue = value.toString();
      } else if (value instanceof DatabaseFunction) {
         returnValue = value.toString();
      } else if (value == null || value.toString().equalsIgnoreCase("null")) {
         returnValue = null;
      } else {
         throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass()
               + " to a Geometry value");
      }
      return returnValue;
   }

   /**
    * @see liquibase.datatype.LiquibaseDataType#sqlToObject(java.lang.String,
    *      liquibase.database.Database)
    */
   @Override
   public Object sqlToObject(final String value, final Database database) {
      final Geometry returnValue;
      if (value == null || value.equalsIgnoreCase("null")) {
         returnValue = null;
      } else {
         final WKTReader reader = new WKTReader();
         try {
            // TODO: Check for SRID.
            returnValue = reader.read(value);
         } catch (final ParseException e) {
            throw new UnexpectedLiquibaseException("Cannot parse " + value + " to a Geometry", e);
         }
      }
      return returnValue;
   }
}