package com.alibaba.druid.util;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleResultSet;
import oracle.jdbc.OracleStatement;
import oracle.jdbc.driver.T4CXAConnection;
import oracle.jdbc.internal.OraclePreparedStatement;
import oracle.jdbc.xa.client.OracleXAConnection;
import oracle.sql.ROWID;

public class OracleUtils {
   private static final Log LOG = LogFactory.getLog(OracleUtils.class);
   private static Set<String> builtinFunctions;
   private static Set<String> builtinTables;
   private static Set<String> keywords;

   public static XAConnection OracleXAConnection(Connection oracleConnection) throws XAException {
      String oracleConnectionClassName = oracleConnection.getClass().getName();
      return (XAConnection)("oracle.jdbc.driver.T4CConnection".equals(oracleConnectionClassName) ? new T4CXAConnection(oracleConnection) : new OracleXAConnection(oracleConnection));
   }

   public static int getRowPrefetch(PreparedStatement stmt) throws SQLException {
      OracleStatement oracleStmt = (OracleStatement)stmt.unwrap(OracleStatement.class);
      return oracleStmt == null ? -1 : oracleStmt.getRowPrefetch();
   }

   public static void setRowPrefetch(PreparedStatement stmt, int value) throws SQLException {
      OracleStatement oracleStmt = (OracleStatement)stmt.unwrap(OracleStatement.class);
      if (oracleStmt != null) {
         oracleStmt.setRowPrefetch(value);
      }

   }

   public static void enterImplicitCache(PreparedStatement stmt) throws SQLException {
      OraclePreparedStatement oracleStmt = unwrapInternal(stmt);
      if (oracleStmt != null) {
         oracleStmt.enterImplicitCache();
      }

   }

   public static void exitImplicitCacheToClose(PreparedStatement stmt) throws SQLException {
      OraclePreparedStatement oracleStmt = unwrapInternal(stmt);
      if (oracleStmt != null) {
         oracleStmt.exitImplicitCacheToClose();
      }

   }

   public static void exitImplicitCacheToActive(PreparedStatement stmt) throws SQLException {
      OraclePreparedStatement oracleStmt = unwrapInternal(stmt);
      if (oracleStmt != null) {
         oracleStmt.exitImplicitCacheToActive();
      }

   }

   public static OraclePreparedStatement unwrapInternal(PreparedStatement stmt) throws SQLException {
      if (stmt instanceof OraclePreparedStatement) {
         return (OraclePreparedStatement)stmt;
      } else {
         OraclePreparedStatement unwrapped = (OraclePreparedStatement)stmt.unwrap(OraclePreparedStatement.class);
         if (unwrapped == null) {
            LOG.error("can not unwrap statement : " + stmt.getClass());
         }

         return unwrapped;
      }
   }

   public static void setDefaultRowPrefetch(Connection conn, int value) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      oracleConn.setDefaultRowPrefetch(value);
   }

   public static int getDefaultRowPrefetch(Connection conn, int value) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      return oracleConn.getDefaultRowPrefetch();
   }

   public static boolean getImplicitCachingEnabled(Connection conn) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      return oracleConn.getImplicitCachingEnabled();
   }

   public static int getStatementCacheSize(Connection conn) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      return oracleConn.getStatementCacheSize();
   }

   public static void purgeImplicitCache(Connection conn) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      oracleConn.purgeImplicitCache();
   }

   public static void setImplicitCachingEnabled(Connection conn, boolean cache) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      oracleConn.setImplicitCachingEnabled(cache);
   }

   public static void setStatementCacheSize(Connection conn, int size) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      oracleConn.setStatementCacheSize(size);
   }

   public static int pingDatabase(Connection conn) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      return oracleConn.pingDatabase();
   }

   public static void openProxySession(Connection conn, int type, Properties prop) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      oracleConn.openProxySession(type, prop);
   }

   public static int getDefaultExecuteBatch(Connection conn) throws SQLException {
      OracleConnection oracleConn = unwrap(conn);
      return oracleConn.getDefaultExecuteBatch();
   }

   public static OracleConnection unwrap(Connection conn) throws SQLException {
      return conn instanceof OracleConnection ? (OracleConnection)conn : (OracleConnection)conn.unwrap(OracleConnection.class);
   }

   public static ROWID getROWID(ResultSet rs, int columnIndex) throws SQLException {
      OracleResultSet oracleResultSet = (OracleResultSet)rs.unwrap(OracleResultSet.class);
      return oracleResultSet.getROWID(columnIndex);
   }

   public static boolean isBuiltinFunction(String function) {
      if (function == null) {
         return false;
      } else {
         String function_lower = function.toLowerCase();
         Set<String> functions = builtinFunctions;
         if (functions == null) {
            functions = new HashSet();
            Utils.loadFromFile("META-INF/druid/parser/oracle/builtin_functions", functions);
            builtinFunctions = functions;
         }

         return functions.contains(function_lower);
      }
   }

   public static boolean isBuiltinTable(String table) {
      if (table == null) {
         return false;
      } else {
         String table_lower = table.toLowerCase();
         Set<String> tables = builtinTables;
         if (tables == null) {
            tables = new HashSet();
            Utils.loadFromFile("META-INF/druid/parser/oracle/builtin_tables", tables);
            builtinTables = tables;
         }

         return tables.contains(table_lower);
      }
   }

   public static boolean isKeyword(String name) {
      if (name == null) {
         return false;
      } else {
         String name_lower = name.toLowerCase();
         Set<String> words = keywords;
         if (words == null) {
            words = new HashSet();
            Utils.loadFromFile("META-INF/druid/parser/oracle/keywords", words);
            keywords = words;
         }

         return words.contains(name_lower);
      }
   }

   public static List<String> showTables(Connection conn) throws SQLException {
      List<String> tables = new ArrayList();
      Statement stmt = null;
      ResultSet rs = null;

      try {
         stmt = conn.createStatement();
         rs = stmt.executeQuery("select table_name from user_tables");

         while(rs.next()) {
            String tableName = rs.getString(1);
            tables.add(tableName);
         }
      } finally {
         JdbcUtils.close(rs);
         JdbcUtils.close(stmt);
      }

      return tables;
   }

   public static List<String> getTableDDL(Connection conn, String... tables) throws SQLException {
      return getTableDDL(conn, Arrays.asList(tables));
   }

   public static List<String> getTableDDL(Connection conn, List<String> tables) throws SQLException {
      List<String> ddlList = new ArrayList();
      PreparedStatement pstmt = null;
      ResultSet rs = null;

      try {
         String sql = "select DBMS_METADATA.GET_DDL('TABLE', TABLE_NAME) FROM user_tables";
         if (tables.size() > 0) {
            sql = sql + "IN (";

            for(int i = 0; i < tables.size(); ++i) {
               if (i != 0) {
                  sql = sql + ", ?";
               } else {
                  sql = sql + "?";
               }
            }

            sql = sql + ")";
         }

         pstmt = conn.prepareStatement(sql);

         for(int i = 0; i < tables.size(); ++i) {
            pstmt.setString(i + 1, (String)tables.get(i));
         }

         rs = pstmt.executeQuery();

         while(rs.next()) {
            String ddl = rs.getString(1);
            ddlList.add(ddl);
         }
      } finally {
         JdbcUtils.close(rs);
         JdbcUtils.close((Statement)pstmt);
      }

      return ddlList;
   }

   public static String getCreateTableScript(Connection conn) throws SQLException {
      return getCreateTableScript(conn, true, true);
   }

   public static String getCreateTableScript(Connection conn, boolean sorted, boolean simplify) throws SQLException {
      List<String> ddlList = getTableDDL(conn);
      StringBuilder buf = new StringBuilder();

      for(String ddl : ddlList) {
         buf.append(ddl);
         buf.append(';');
      }

      String ddlScript = buf.toString();
      if (!sorted && !simplify) {
         return ddlScript;
      } else {
         List stmtList = SQLUtils.parseStatements(ddlScript, DbType.oracle);
         if (simplify) {
            for(Object o : stmtList) {
               if (o instanceof SQLCreateTableStatement) {
                  SQLCreateTableStatement createTableStmt = (SQLCreateTableStatement)o;
                  createTableStmt.simplify();
               }
            }
         }

         if (sorted) {
            SQLCreateTableStatement.sort(stmtList);
         }

         return SQLUtils.toSQLString(stmtList, DbType.oracle);
      }
   }
}
