/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.druid.util;

import com.alibaba.druid.DbType;
import com.alibaba.druid.pool.DruidPooledConnection;
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 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;

import javax.sql.XAConnection;
import javax.transaction.xa.XAException;

import java.sql.*;
import java.util.*;

public class OracleUtils {
    private static final Log LOG = LogFactory.getLog(OracleUtils.class);

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

    public static int getRowPrefetch(PreparedStatement stmt) throws SQLException {
        OracleStatement oracleStmt = stmt.unwrap(OracleStatement.class);

        if (oracleStmt == null) {
            return -1;
        }

        return oracleStmt.getRowPrefetch();
    }

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

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

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

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

    public static OraclePreparedStatement unwrapInternal(PreparedStatement stmt) throws SQLException {
        if (stmt instanceof OraclePreparedStatement) {
            return (OraclePreparedStatement) stmt;
        }

        OraclePreparedStatement unwrapped = stmt.unwrap(OraclePreparedStatement.class);

        if (unwrapped == null) {
            LOG.error("can not unwrap statement : " + stmt.getClass());
        }

        return unwrapped;
    }

    public static short getVersionNumber(DruidPooledConnection conn) throws SQLException {
        oracle.jdbc.internal.OracleConnection oracleConn = (oracle.jdbc.internal.OracleConnection) unwrap(conn);
        return oracleConn.getVersionNumber();
    }

    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, java.util.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 {
        if (conn instanceof OracleConnection) {
            return (OracleConnection) conn;
        }

        return conn.unwrap(OracleConnection.class);
    }

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

    private static Set<String> builtinFunctions;

    public static boolean isBuiltinFunction(String function) {
        if (function == null) {
            return false;
        }

        String function_lower = function.toLowerCase();

        Set<String> functions = builtinFunctions;

        if (functions == null) {
            functions = new HashSet<String>();
            Utils.loadFromFile("META-INF/druid/parser/oracle/builtin_functions", functions);
            builtinFunctions = functions;
        }

        return functions.contains(function_lower);
    }

    private static Set<String> builtinTables;

    public static boolean isBuiltinTable(String table) {
        if (table == null) {
            return false;
        }

        String table_lower = table.toLowerCase();

        Set<String> tables = builtinTables;

        if (tables == null) {
            tables = new HashSet<String>();
            Utils.loadFromFile("META-INF/druid/parser/oracle/builtin_tables", tables);
            builtinTables = tables;
        }

        return tables.contains(table_lower);
    }

    private static Set<String> keywords;

    public static boolean isKeyword(String name) {
        if (name == null) {
            return false;
        }

        String name_lower = name.toLowerCase();

        Set<String> words = keywords;

        if (words == null) {
            words = new HashSet<String>();
            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<String>();

        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<String>();

        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            StringBuilder sql = new StringBuilder();
            sql.append("select DBMS_METADATA.GET_DDL('TABLE', TABLE_NAME) FROM user_tables");

            if (tables.size() > 0) {
                sql.append(" WHERE TABLE_NAME IN (");
                for (int i = 0; i < tables.size(); ++i) {
                    if (i != 0) {
                        sql.append(", ?");
                    } else {
                        sql.append("?");
                    }
                }
                sql.append(")");
            }
            pstmt = conn.prepareStatement(sql.toString());
            for (int i = 0; i < tables.size(); ++i) {
                pstmt.setString(i + 1, tables.get(i));
            }
            rs = pstmt.executeQuery();
            while (rs.next()) {
                String ddl = rs.getString(1);
                ddlList.add(ddl);
            }
        } finally {
            JdbcUtils.close(rs);
            JdbcUtils.close(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 = OracleUtils.getTableDDL(conn);

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

        String ddlScript = buf.toString();

        if (!(sorted || simplify)) {
            return ddlScript;
        }

        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);
    }
}
