/*
 * Copyright (C) 2003-2011 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.jcr.impl.util.jdbc;

import org.exoplatform.commons.utils.IOUtil;
import org.exoplatform.services.database.utils.DialectConstants;
import org.exoplatform.services.database.utils.JDBCUtils;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.storage.jdbc.DBConstants;
import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCDataContainerConfig;
import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCDataContainerConfig.DatabaseStructureType;
import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.IOException;

/**
 * Created by The eXo Platform SAS.
 * 
 * <br>Date: 2011
 *
 * @author <a href="mailto:alex.reshetnyak@exoplatform.com.ua">Alex Reshetnyak</a> 
 * @version $Id: DBInitializerHelper.java 111 2011-11-11 11:11:11Z rainf0x $
 */
public class DBInitializerHelper
{
   /**
    * Logger.
    */
   protected final static Log LOG = ExoLogger.getLogger("exo.jcr.component.core.DBInitializerHelper");

   /**
    * Table prefix for all tables used in JCR.
    */
   public static final String JCR_TABLE_PREFIX = "JCR_";

   /**
    * Returns SQL scripts for initialization database for defined {@link JDBCDataContainerConfig}.  
    */
   public static String prepareScripts(JDBCDataContainerConfig containerConfig) throws IOException
   {
      String itemTableSuffix = getItemTableSuffix(containerConfig);
      String valueTableSuffix = getValueTableSuffix(containerConfig);
      String refTableSuffix = getRefTableSuffix(containerConfig);

      boolean isolatedDB = containerConfig.dbStructureType == DatabaseStructureType.ISOLATED;

      return prepareScripts(containerConfig.initScriptPath, itemTableSuffix, valueTableSuffix, refTableSuffix, isolatedDB);
   }

   /**
    * Returns SQL scripts for initialization database for defined {@link WorkspaceEntry}. 
    * 
    * @param wsEntry
    *          workspace configuration 
    * @param dialect
    *          database dialect which is used, since {@link JDBCWorkspaceDataContainer#DB_DIALECT} parameter 
    *          can contain {@link DialectConstants#DB_DIALECT_AUTO} value it is necessary to resolve dialect
    *          before based on database connection.
    */
   public static String prepareScripts(WorkspaceEntry wsEntry, String dialect) throws IOException,
      RepositoryConfigurationException
   {
      String itemTableSuffix = getItemTableSuffix(wsEntry);
      String valueTableSuffix = getValueTableSuffix(wsEntry);
      String refTableSuffix = getRefTableSuffix(wsEntry);

      DatabaseStructureType dbType = DBInitializerHelper.getDatabaseType(wsEntry);

      boolean isolatedDB = dbType == DatabaseStructureType.ISOLATED;
      String initScriptPath = DBInitializerHelper.scriptPath(dialect, dbType.isMultiDatabase());

      return prepareScripts(initScriptPath, itemTableSuffix, valueTableSuffix, refTableSuffix, isolatedDB);
   }

   /**
    * Preparing SQL scripts for database initialization.
    */
   private static String prepareScripts(String initScriptPath, String itemTableSuffix, String valueTableSuffix,
      String refTableSuffix, boolean isolatedDB) throws IOException
   {
      String scripts = IOUtil.getStreamContentAsString(DBInitializerHelper.class.getResourceAsStream(initScriptPath));

      if (isolatedDB)
      {
         scripts =
            scripts.replace("MITEM", itemTableSuffix).replace("MVALUE", valueTableSuffix)
               .replace("MREF", refTableSuffix);
      }

      return scripts;
   }

   /**
    * Returns path where SQL scripts for database initialization is stored.
    */
   public static String scriptPath(String dbDialect, boolean multiDb)
   {
      String suffix = multiDb ? "m" : "s";

      String sqlPath = null;
      if (dbDialect.startsWith(DBConstants.DB_DIALECT_ORACLE))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.ora.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_PGSQL))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.pgsql.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL_NDB))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql-ndb.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL_NDB_UTF8))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql-ndb-utf8.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL_MYISAM))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql-myisam.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL_UTF8))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql-utf8.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_MYSQL_MYISAM_UTF8))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mysql-myisam-utf8.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_MSSQL))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.mssql.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_DERBY))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.derby.sql";
      }
      else if (dbDialect.equals(DBConstants.DB_DIALECT_DB2V8))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.db2v8.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_DB2))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.db2.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_SYBASE))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.sybase.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_INGRES))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.ingres.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_H2))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.h2.sql";
      }
      else if (dbDialect.startsWith(DBConstants.DB_DIALECT_HSQLDB))
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.sql";
      }
      else
      {
         sqlPath = "/conf/storage/jcr-" + suffix + "jdbc.sql";
      }

      return sqlPath;
   }

   /**
    * Initialization script for root node based on {@link JDBCDataContainerConfig}.
    */
   public static String getRootNodeInitializeScript(JDBCDataContainerConfig containerConfig)
   {
      boolean multiDb = containerConfig.dbStructureType.isMultiDatabase();
      String itemTableName = getItemTableName(containerConfig);

      return getRootNodeInitializeScript(itemTableName, multiDb);
   }

   /**
    * Initialization script for root node.
    */
   public static String getRootNodeInitializeScript(String itemTableName, boolean multiDb)
   {
      String singeDbScript =
         "insert into " + itemTableName + "(ID, PARENT_ID, NAME, CONTAINER_NAME, VERSION, I_CLASS, I_INDEX, "
            + "N_ORDER_NUM) VALUES('" + Constants.ROOT_PARENT_UUID + "', '" + Constants.ROOT_PARENT_UUID + "', '"
            + Constants.ROOT_PARENT_NAME + "', '" + Constants.ROOT_PARENT_CONAINER_NAME + "', 0, 0, 0, 0)";

      String multiDbScript =
         "insert into " + itemTableName + "(ID, PARENT_ID, NAME, VERSION, I_CLASS, I_INDEX, " + "N_ORDER_NUM) VALUES('"
            + Constants.ROOT_PARENT_UUID + "', '" + Constants.ROOT_PARENT_UUID + "', '" + Constants.ROOT_PARENT_NAME
            + "', 0, 0, 0, 0)";

      return multiDb ? multiDbScript : singeDbScript;
   }

   public static String getItemTableSuffix(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "ITEM");
   }

   public static String getValueTableSuffix(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "VALUE");
   }

   public static String getRefTableSuffix(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "REF");
   }

   public static String getItemTableName(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return JCR_TABLE_PREFIX + getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "ITEM");
   }

   public static String getValueTableName(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return JCR_TABLE_PREFIX + getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "VALUE");
   }

   public static String getRefTableName(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      return JCR_TABLE_PREFIX + getTableSuffix(getDatabaseType(wsConfig), getDBTableSuffix(wsConfig), "REF");
   }

   public static String getItemTableSuffix(JDBCDataContainerConfig containerConfig)
   {
      return getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "ITEM");
   }

   public static String getValueTableSuffix(JDBCDataContainerConfig containerConfig)
   {
      return getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "VALUE");
   }

   public static String getRefTableSuffix(JDBCDataContainerConfig containerConfig)
   {
      return getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "REF");
   }

   public static String getItemTableName(JDBCDataContainerConfig containerConfig)
   {
      return JCR_TABLE_PREFIX + getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "ITEM");
   }

   public static String getValueTableName(JDBCDataContainerConfig containerConfig)
   {
      return JCR_TABLE_PREFIX + getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "VALUE");
   }

   public static String getRefTableName(JDBCDataContainerConfig containerConfig)
   {
      return JCR_TABLE_PREFIX + getTableSuffix(containerConfig.dbStructureType, containerConfig.dbTableSuffix, "REF");
   }

   private static String getTableSuffix(JDBCDataContainerConfig.DatabaseStructureType dbType, String dbTableSuffix,
      String forTable)
   {
      String tableSuffix = "";
      switch (dbType)
      {
         case MULTI :
            tableSuffix = "M" + forTable;
            break;
         case SINGLE :
            tableSuffix = "S" + forTable;
            break;
         case ISOLATED :
            tableSuffix = forTable.substring(0, 1) + dbTableSuffix;
            break;
      }
      return tableSuffix;
   }

   /**
    * Returns SQL script for create objects such as index, primary of foreign key. 
    */
   public static String getObjectScript(String objectName, boolean multiDb, String dialect, WorkspaceEntry wsEntry)
      throws RepositoryConfigurationException, IOException
   {
      String scripts = prepareScripts(wsEntry, dialect);

      String sql = null;
      for (String query : JDBCUtils.splitWithSQLDelimiter(scripts))
      {
         String q = JDBCUtils.cleanWhitespaces(query);
         if (q.contains(objectName))
         {
            if (sql != null)
            {
               throw new RepositoryConfigurationException("Can't find unique script for object creation. Object name: "
                  + objectName);
            }

            sql = q;
         }
      }

      if (sql != null)
      {
         return sql;
      }

      throw new RepositoryConfigurationException("Script for object creation is not found. Object name: " + objectName);
   }

   /**
    * Returns {@link DatabaseStructureType} based on workspace configuration.
    */
   public static DatabaseStructureType getDatabaseType(WorkspaceEntry wsConfig) throws RepositoryConfigurationException
   {
      try
      {
         if (wsConfig.getContainer().getParameterBoolean("multi-db"))
         {
            return JDBCDataContainerConfig.DatabaseStructureType.MULTI;
         }
         else
         {
            return JDBCDataContainerConfig.DatabaseStructureType.SINGLE;
         }
      }
      catch (Exception e)
      {
         String dbStructureType =
            wsConfig.getContainer().getParameterValue(JDBCWorkspaceDataContainer.DB_STRUCTURE_TYPE).toUpperCase();
         return JDBCDataContainerConfig.DatabaseStructureType.valueOf(dbStructureType);
      }
   }

   /**
    * Returns value of {@link JDBCWorkspaceDataContainer#DB_TABLENAME_SUFFIX} parameter 
    * from workspace configuration.
    */
   public static String getDBTableSuffix(WorkspaceEntry wsConfig)
   {
      String defaultSuffix = replaceIncorrectChars(wsConfig.getName());

      String suffix =
         wsConfig.getContainer().getParameterValue(JDBCWorkspaceDataContainer.DB_TABLENAME_SUFFIX, defaultSuffix);
      return suffix;
   }

   /**
    * Tries to fix name of the workspace if it is not corresponding to SQL table name specification.
    */
   private static String replaceIncorrectChars(String workspaceName)
   {
      return workspaceName.replaceAll("[^A-Za-z_0-9]", "").toUpperCase();
   }

   /**
    * Returns database dialects stored in configuration and {@link DialectConstants#DB_DIALECT_AUTO}
    * in other case. Always in upper register.
    */
   public static String getDatabaseDialect(WorkspaceEntry wsConfig)
   {
      String dialect =
         wsConfig.getContainer().getParameterValue(JDBCWorkspaceDataContainer.DB_DIALECT, DBConstants.DB_DIALECT_AUTO);

      return dialect.toUpperCase();
   }

   /**
    * Use sequence for order number.
    *
    * @param wsConfig The workspace configuration.
    * @return true if the sequence are enable. False otherwise.
    */
   public static boolean useSequenceForOrderNumber(WorkspaceEntry wsConfig, String dbDialect) throws RepositoryConfigurationException
   {
      try
      {
         if (wsConfig.getContainer().getParameterValue(JDBCWorkspaceDataContainer.USE_SEQUENCE_FOR_ORDER_NUMBER, JDBCWorkspaceDataContainer.USE_SEQUENCE_AUTO).equalsIgnoreCase(JDBCWorkspaceDataContainer.USE_SEQUENCE_AUTO))
         {
            return JDBCWorkspaceDataContainer.useSequenceDefaultValue();
         }
         else
         {
            return wsConfig.getContainer().getParameterBoolean(JDBCWorkspaceDataContainer.USE_SEQUENCE_FOR_ORDER_NUMBER);
         }
      }
      catch (RepositoryConfigurationException e)
      {
         return JDBCWorkspaceDataContainer.useSequenceDefaultValue();
      }
   }
}
