package liquibase.ext.spatial.sqlgenerator;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.util.StringUtils;

/**
 * <code>OracleSpatialUtils</code> provides utility methods for Oracle Spatial.
 */
public class OracleSpatialUtils {

   /** The Oracle function that converts an EPSG SRID to the corresponding Oracle SRID. */
   public static final String EPSG_TO_ORACLE_FUNCTION = "SDO_CS.MAP_EPSG_SRID_TO_ORACLE";

   /** The mapping of EPSG SRID to Oracle SRID. */
   private final static Map<String, String> EPSG_TO_ORACLE_MAP = Collections
         .synchronizedMap(new HashMap<String, String>());

   /** Hide the default constructor. */
   private OracleSpatialUtils() {
   }

   /**
    * Converts the given Well-Known Text string to one that will work in Oracle. If the string is
    * greater than 4000 characters, the string is broken into pieces where each piece is converted
    * to a CLOB. The CLOB handling assumes that the result will be wrapped in single quotes so it
    * wraps the result in "<code>' || TO_CLOB(...) || '</code>".
    *
    * @param wkt
    *           the Well-Known Text string to convert.
    * @return the original WKT or a <code>TO_CLOB</code> concatenation of the WKT.
    */
   public static String getOracleWkt(final String wkt) {
      final String oracleWkt;
      // Strings longer than 4000 characters need to be converted to CLOBs.
      if (wkt.length() > 4000) {
         oracleWkt = "' || " + convertToClob(wkt) + " || '";
      } else {
         oracleWkt = wkt;
      }
      return oracleWkt;
   }

   /**
    * Generates the SQL to convert the given string to a CLOB.
    *
    * @param varchar
    *           the value to convert.
    * @return the SQL to convert the string to a CLOB.
    */
   public static String convertToClob(final String varchar) {
      int startIndex = 0;
      int endIndex = Math.min(startIndex + 4000, varchar.length());
      final StringBuilder clobs = new StringBuilder("TO_CLOB('").append(
            varchar.substring(startIndex, endIndex)).append("')");
      while (endIndex < varchar.length()) {
         startIndex = endIndex;
         endIndex = Math.min(startIndex + 4000, varchar.length());
         clobs.append(" || TO_CLOB('").append(varchar.substring(startIndex, endIndex)).append("')");
      }
      return clobs.toString();
   }

   /**
    * Converts the given EPSG SRID to the corresponding Oracle SRID.
    *
    * @param srid
    *           the EPSG SRID.
    * @param database
    *           the database instance.
    * @return the corresponding Oracle SRID.
    */
   public static String getOracleSrid(final String srid, final Database database) {
      final String oracleSrid;
      if (StringUtils.trimToNull(srid) == null) {
         oracleSrid = null;
      } else if (EPSG_TO_ORACLE_MAP.containsKey(srid)) {
         oracleSrid = EPSG_TO_ORACLE_MAP.get(srid);
      } else {
         oracleSrid = loadOracleSrid(srid, database);
         EPSG_TO_ORACLE_MAP.put(srid, oracleSrid);
      }
      return oracleSrid;
   }

   /**
    * Queries to the database to convert the given EPSG SRID to the corresponding Oracle SRID.
    *
    * @param srid
    *           the EPSG SRID.
    * @param database
    *           the database instance.
    * @return the corresponding Oracle SRID.
    */
   public static String loadOracleSrid(final String srid, final Database database) {
      final String oracleSrid;
      final JdbcConnection jdbcConnection = (JdbcConnection) database.getConnection();
      final Connection connection = jdbcConnection.getUnderlyingConnection();
      Statement statement = null;
      try {
         statement = connection.createStatement();
         final ResultSet resultSet = statement.executeQuery("SELECT " + EPSG_TO_ORACLE_FUNCTION
               + "(" + srid + ") FROM dual");
         resultSet.next();
         oracleSrid = resultSet.getString(1);
      } catch (final SQLException e) {
         throw new UnexpectedLiquibaseException("Failed to find the Oracle SRID for EPSG:" + srid,
               e);
      } finally {
         try {
            statement.close();
         } catch (final SQLException ignore) {
         }
      }
      return oracleSrid;
   }
}