/*
 * 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.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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.CSVToArrayUtil;
import org.seasar.codegen.util.CreateTableTypeToTypeUtil;
import org.seasar.codegen.util.IdentityUtil;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;

public class ERWinCSVImportCodeData implements ImportCodeData {

    private Dbms dbms;

    private FKNameConverter fkNameConverter;

    private final static int TableNamePOS = 0;

    private final static int TableNamaePOS = 1;

    private final static int FieldNamePOS = 2;

    private final static int FieldTypeNamePOS = 3;

    private final static int FieldNullStatePOS = 4;

    private final static int PrimaryKeyPOS = 5;

    private final static int FieldDefaultPOS = 7;

    private final static int ParentRefTypePOS = 8;

    private final static int ParentRefTableNamePOS = 10;

    private final static int ParentRefFieldNamePOS = 11;

    private final static int ChildRefTypePOS = 13;

    private final static int ChildRefTableNamePOS = 14;

    private final static int ChildRefFieldNamePOS = 15;

    private String csvEncode = "MS932";

    @Binding(bindingType = BindingType.MUST)
    private CodeGenConfig codeGenConfig;

    @SuppressWarnings("unchecked")
    public Map<String, Table> readCodeData(File srcFile) {
        List csvArray = CSVToArrayUtil.readCSVToArray(srcFile, csvEncode);
        Map<String, Table> tableList = csvArrayToTable(csvArray);
        return tableList;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Table> csvArrayToTable(List csvArray) {
        Iterator ite = csvArray.iterator();
        Map<String, Table> tableList = new HashMap<String, Table>();
        Table table = null;
        while (ite.hasNext()) {
            String[] cells = (String[]) ite.next();
            table = processCSVLine((List<String>) Arrays.asList(cells), table,
                    tableList);
        }
        return tableList;
    }

    private Table processCSVLine(List<String> oneLine, Table table,
            Map<String, Table> tableList) {
        if (hasTableData(oneLine)) {
            table = new Table();
            String tableName = getTableName(oneLine);
            table.setTableName(tableName);
            tableList.put(table.getTableName(), table);
        }
        if (hasFieldData(oneLine)) {
            Field field = new Field();
            setupField(field, oneLine, table);
            setupPrimaryKey(oneLine, field, table);
        }
        setupParentRef(oneLine, table);
        setupChildRef(oneLine, table);
        return table;
    }

    private void setupChildRef(List<String> oneLine, Table table) {
        if (hasChildRef(oneLine)) {
            String tableName = CSVToArrayUtil.getStringFromListPos(oneLine,
                    ChildRefTableNamePOS);
            String fieldName = CSVToArrayUtil.getStringFromListPos(oneLine,
                    ChildRefFieldNamePOS);
            LinkTable linkTable = new LinkTable();
            linkTable.setTableName(tableName);
            linkTable.setParentFieldName(fieldName);
            String relName = fkNameConverter.convertParent(table, linkTable);
            table.addChildTable(relName, linkTable);
        }
    }

    private boolean hasChildRef(List<String> oneLine) {
        String data = CSVToArrayUtil.getStringFromListPos(oneLine,
                ChildRefTypePOS);
        return (data.length() > 0);
    }

    private void setupParentRef(List<String> oneLine, Table table) {
        if (hasParentRef(oneLine)) {
            String tableName = CSVToArrayUtil.getStringFromListPos(oneLine,
                    ParentRefTableNamePOS);
            String fieldName = CSVToArrayUtil.getStringFromListPos(oneLine,
                    ParentRefFieldNamePOS);
            LinkTable linkTable = new LinkTable();
            linkTable.setTableName(tableName);
            linkTable.setChildFieldName(fieldName);
            String relName = fkNameConverter.convertParent(table, linkTable);
            table.addLinkTable(relName, linkTable);
        }
    }

    private boolean hasParentRef(List<String> oneLine) {
        String data = CSVToArrayUtil.getStringFromListPos(oneLine,
                ParentRefTypePOS);
        return (data.length() > 0);
    }

    void setupPrimaryKey(List<String> oneLine, Field field, Table table) {
        if (hasPrimaryKey(oneLine)) {
            PrimaryKey primaryKey = new PrimaryKey();
            primaryKey.setField(field);
            if (codeGenConfig.getIdentityType() != null) {
                if (IdentityUtil.isIdentityConfig(codeGenConfig
                        .getIdentityType())) {
                    field.setUseIdentity(true);
                } else if (IdentityUtil.isSequenceConfig(codeGenConfig
                        .getIdentityType())) {
                    field.setSequence(field.getFieldName());
                }

            }
            table.addPrimaryKey(primaryKey);
        }
    }

    boolean hasPrimaryKey(List<String> oneLine) {
        String data = CSVToArrayUtil.getStringFromListPos(oneLine,
                PrimaryKeyPOS);
        return data.equals("Yes");
    }

    private boolean hasFieldData(List<String> oneLine) {
        String data = CSVToArrayUtil
                .getStringFromListPos(oneLine, FieldNamePOS);
        return (data.length() != 0);
    }

    private void setupField(Field field, List<String> oneLine, Table table) {
        String fieldName = CSVToArrayUtil.getStringFromListPos(oneLine,
                FieldNamePOS);
        field.setFieldName(fieldName);
        field.setFieldAttributeName(fieldName);
        try {
            setupFieldDataType(table, field, oneLine);
        } catch (InternalGenerateException e) {
            throw new NotTypeMatchException(table.getTableName(),
                    field.getFieldName(), e);
        }
        // String fieldDefault =
        // CSVToArrayUtil.getStringFromListPos(oneLine,FieldDefaultPOS);
        // field.setDefaultVal(fieldDefault);
        table.addTableField(field);
    }

    private void setupFieldDataType(Table table, Field field,
            List<String> oneLine) throws InternalGenerateException {
        String fieldTypeName = CSVToArrayUtil.getStringFromListPos(oneLine,
                FieldTypeNamePOS);
        String fieldNullState = CSVToArrayUtil.getStringFromListPos(oneLine,
                FieldNullStatePOS);
        String fieldDefault = CSVToArrayUtil.getStringFromListPos(oneLine,
                FieldDefaultPOS);
        // field.setDefaultVal(fieldDefault);
        DataType type = selectDataType(fieldNullState, fieldTypeName,
                fieldDefault);
        field.setDataType(type);
    }

    private DataType selectDataType(String fieldNullState,
            String fieldTypeName, String fieldDefault)
            throws InternalGenerateException {
        FieldSetting fieldSetting = new FieldSetting();
        String typeName = CreateTableTypeToTypeUtil.getTypeName(fieldTypeName);
        String dataType = dbms.convDBTypeToDataType(typeName);
        fieldSetting.setTypeName(dataType);
        String length = CreateTableTypeToTypeUtil.getLength(fieldTypeName,
                typeName);
        int fullLength = CreateTableTypeToTypeUtil.getFullLength(length);
        fieldSetting.setColmnSize(fullLength);
        int pointNumberLength = CreateTableTypeToTypeUtil
                .getPointNumberLength(length);
        fieldSetting.setPointNumber(pointNumberLength);
        boolean isNotNull = fieldNullState.equals("NOT NULL");
        fieldSetting.setNotNull(isNotNull);
        fieldSetting.setFieldDefault(fieldDefault);
        DataType type = dbms.selectBestDataType(fieldSetting);
        return type;
    }

    private boolean hasTableData(List<String> oneLine) {
        String data = CSVToArrayUtil.getStringFromListPos(oneLine,
                TableNamaePOS);
        return (data.length() != 0);
    }

    private String getTableName(List<String> oneLine) {
        String data = CSVToArrayUtil
                .getStringFromListPos(oneLine, TableNamePOS);
        return data;
    }

    public Dbms getDataTypeSelectUtil() {
        return dbms;
    }

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

    public String getCsvEncode() {
        return csvEncode;
    }

    @Binding(bindingType = BindingType.MAY)
    public void setCsvEncode(String csvEncode) {
        this.csvEncode = csvEncode;
    }

    public FKNameConverter getFkNameConverter() {
        return fkNameConverter;
    }

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

    /**
     * @param codeGenConfig
     *            設定する codeGenConfig。
     */
    public void setCodeGenConfig(CodeGenConfig codeGenConfig) {
        this.codeGenConfig = codeGenConfig;
    }

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

}
