/*
 * Copyright 2006-2011 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.codegen.impl;

import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.codegen.CodeGenConfig;
import org.seasar.codegen.ImportCodeData;
import org.seasar.codegen.convert.FKNameConverter;
import org.seasar.codegen.dbms.Dbms;
import org.seasar.codegen.element.DataType;
import org.seasar.codegen.element.Field;
import org.seasar.codegen.element.FieldSetting;
import org.seasar.codegen.element.LinkTable;
import org.seasar.codegen.element.PrimaryKey;
import org.seasar.codegen.element.Table;
import org.seasar.codegen.exception.InternalGenerateException;
import org.seasar.codegen.exception.NotTypeMatchException;
import org.seasar.codegen.util.CreateTableTypeToTypeUtil;
import org.seasar.codegen.util.IdentityUtil;
import org.seasar.codegen.util.LinkUtil;
import org.seasar.codegen.util.SequnceUtil;
import org.seasar.extension.jdbc.util.ConnectionUtil;
import org.seasar.extension.jdbc.util.DataSourceUtil;
import org.seasar.extension.jdbc.util.DatabaseMetaDataUtil;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.util.ResultSetUtil;
import org.seasar.framework.util.StringUtil;

/**
 * 
 * @author azusa
 */
public class DatabaseImportCodeData implements ImportCodeData {

    protected Dbms dbms;

    private Set<String> tables = new HashSet<String>();

    private String ignoreTablePattern;

    private DataSource dataSource;

    protected String schemaName = null;

    private CodeGenConfig codeGenConfig;

    @SuppressWarnings("unused")
    private FKNameConverter fkNameConverter;

    private static Log log = LogFactory.getLog(DatabaseImportCodeData.class);

    public DatabaseImportCodeData(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, Table> readCodeData(File srcFile) {
        Connection con = null;
        try {
            con = DataSourceUtil.getConnection(dataSource);
            return createMap(con);
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (Exception ignore) {
                }
            }
        }

    }

    protected Map<String, Table> createMap(Connection connection) {

        Map<String, Table> tableMap = new HashMap<String, Table>();
        try {
            DatabaseMetaData dbmd = ConnectionUtil.getMetaData(connection);
            ResultSet rsTable = dbmd.getTables(null, schemaName, "%", null);
            Pattern ignoreTable = null;
            if (ignoreTablePattern != null) {
                ignoreTable = Pattern.compile(this.ignoreTablePattern);
            }
            String recycle = getRecyleTableName(dbmd);
            try {
                while (rsTable.next()) {
                    Table table = new Table();
                    String tableName = rsTable.getString("TABLE_NAME");
                    log.debug("table name:" + tableName);
                    log.debug("table type:" + rsTable.getString("TABLE_TYPE"));
                    String type = rsTable.getString("TABLE_TYPE");
                    if ((type.equals("TABLE") == false)
                            && (type.equals("VIEW") == false)) {
                        continue;
                    }
                    if (type.equals("VIEW")) {
                        table.setView(true);
                    }

                    if (ignoreTable != null
                            && ignoreTable.matcher(tableName).matches()) {
                        continue;
                    }
                    if (!StringUtil.isEmpty(recycle)
                            && tableName.startsWith(recycle)) {
                        continue;
                    }
                    if (!tables.isEmpty()) {
                        if (!tables.contains(tableName)) {
                            continue;
                        }
                    }
                    table.setTableName(tableName);
                    String[] primaryKeys = DatabaseMetaDataUtil.getPrimaryKeys(
                            dbmd, tableName);
                    ResultSet rsColumn = dbmd.getColumns(null, schemaName,
                            tableName, "%");
                    try {
                        while (rsColumn.next()) {
                            Field field = getField(tableName, rsColumn);
                            table.addTableField(field);
                            setUpPrimaryKey(table, field, primaryKeys);
                        }
                        tableMap.put(table.getTableName(), table);
                    } finally {
                        ResultSetUtil.close(rsColumn);
                    }
                }
                for (Table table : tableMap.values()) {
                    ResultSet rsExKey = dbmd.getExportedKeys(null, schemaName,
                            table.getTableName());
                    try {
                        while (rsExKey.next()) {
                            LinkTable parentLink = getParentLink(rsExKey);
                            Table childTable = tableMap.get(rsExKey
                                    .getString("FKTABLE_NAME"));
                            if (childTable != null) {
                                childTable.addLinkTable(
                                        childTable.getTableName(), parentLink);
                            }
                        }
                    } finally {
                        ResultSetUtil.close(rsExKey);
                    }
                }

                LinkUtil.setupChildLinks(tableMap);
                return tableMap;
            } finally {
                ResultSetUtil.close(rsTable);
            }
        } catch (SQLException e) {
            throw new SQLRuntimeException(e);
        }
    }

    private String getRecyleTableName(DatabaseMetaData dbmd) {
        String product = getDatabaseProductName(dbmd);
        if (product.startsWith("Oracle")) {
            return "BIN$";
        }
        return "";
    }

    protected String getDatabaseProductName(DatabaseMetaData dbmd) {
        return DatabaseMetaDataUtil.getDatabaseProductName(dbmd);
    }

    private LinkTable getParentLink(ResultSet rsExKey) throws SQLException {
        LinkTable link = new LinkTable();
        link.setTableName(rsExKey.getString("PKTABLE_NAME"));
        link.setParentFieldName(rsExKey.getString("PKCOLUMN_NAME"));
        link.setChildFieldName(rsExKey.getString("FKCOLUMN_NAME"));
        return link;
    }

    protected Field getField(String tableName, ResultSet rsColumn)
            throws SQLException {
        String columnName = rsColumn.getString(4);
        String typeName = rsColumn.getString(6);
        String strNotNull = getNotNull(rsColumn.getInt(11));
        int iColumnSize = rsColumn.getInt(7);
        int decimalDegit = rsColumn.getInt(9);
        String columnSize = null;
        if (decimalDegit != 0) {
            columnSize = "(" + iColumnSize + "," + decimalDegit + ")";
        } else {
            columnSize = "(" + iColumnSize + ")";
        }

        Field field = new Field();
        field.setFieldName(columnName);
        field.setFieldAttributeName(columnName);

        String columnDef = rsColumn.getString(13);
        if (columnDef == null) {
            columnDef = "";
        }

        try {
            field.setDataType(getDataType(typeName, columnSize, strNotNull, ""));
        } catch (InternalGenerateException e) {
            throw new NotTypeMatchException(tableName, columnName, e);
        }
        return field;
    }

    protected void setUpPrimaryKey(Table table, Field field,
            String[] primaryKeys) {
        for (int i = 0; i < primaryKeys.length; i++) {
            // プライマリーキーと列名で大文字小文字が食い違う場合があるのでequalsIgnoreCaseで比較する。
            if (primaryKeys[i].equalsIgnoreCase(field.getFieldName())) {
                Map<String, String> sequnceMap = codeGenConfig
                        .getSequnceMapping();
                PrimaryKey primaryKey = new PrimaryKey();
                primaryKey.setField(field);
                if (sequnceMap != null
                        && sequnceMap.get(table.getTableName()) != null) {
                    SequnceUtil.addSequence(primaryKey,
                            sequnceMap.get(table.getTableName()));
                } else if (IdentityUtil.isIdentityConfig(codeGenConfig
                        .getIdentityType())) {
                    field.setUseIdentity(true);
                } else if (IdentityUtil.isSequenceConfig(codeGenConfig
                        .getIdentityType())) {
                    field.setSequence(field.getFieldNameForDto());
                }
                table.addPrimaryKey(primaryKey);
            }
        }
    }

    protected DataType getDataType(String dataType, String columnSize,
            String notNull, String defaultValue)
            throws InternalGenerateException {
        FieldSetting fieldSetting = new FieldSetting();
        String langType = dbms.convDBTypeToDataType(dataType);
        fieldSetting.setTypeName(langType);
        String length = CreateTableTypeToTypeUtil.getLength(dataType
                + columnSize, dataType);
        int precision = CreateTableTypeToTypeUtil.getFullLength(length);
        fieldSetting.setColmnSize(precision);
        int scale = CreateTableTypeToTypeUtil.getPointNumberLength(length);
        fieldSetting.setPointNumber(scale);
        boolean isNotNull = "NOT NULL".equalsIgnoreCase(notNull);
        fieldSetting.setNotNull(isNotNull);
        fieldSetting.setFieldDefault(defaultValue);
        DataType type = dbms.selectBestDataType(fieldSetting);
        return type;
    }

    protected String getNotNull(int notNull) {
        if (notNull == DatabaseMetaData.columnNoNulls) {
            return "NOT NULL";
        } else {
            return "";
        }
    }

    public void setSchemaName(String schemaName) {
        this.schemaName = schemaName;
    }

    public void setDataTypeSelectUtil(Dbms dataTypeSelectUtil) {
        this.dbms = dataTypeSelectUtil;
    }

    public void addTable(String tableName) {
        tables.add(tableName);
    }

    public void setIgnoreTablePattern(String ignoreTablePattern) {
        this.ignoreTablePattern = ignoreTablePattern;
    }

    public void setDbms(Dbms dbms) {
        this.dbms = dbms;
    }

    public void setFKNameConverter(FKNameConverter converter) {
        this.fkNameConverter = converter;
    }

    public void setCodeGenConfig(CodeGenConfig codeGenConfig) {
        this.codeGenConfig = codeGenConfig;
    }
}
