/*
 * 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.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jiemamy.JiemamyContext;
import org.jiemamy.dddbase.DefaultEntityRef;
import org.jiemamy.dddbase.EntityRef;
import org.jiemamy.model.column.JmColumn;
import org.jiemamy.model.constraint.JmForeignKeyConstraint;
import org.jiemamy.model.constraint.JmPrimaryKeyConstraint;
import org.jiemamy.model.datatype.TypeParameterKey;
import org.jiemamy.model.table.JmTable;
import org.jiemamy.serializer.JiemamySerializer;
import org.jiemamy.serializer.SerializationException;
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.CodeGenException;
import org.seasar.codegen.exception.InternalGenerateException;
import org.seasar.codegen.exception.NotTypeMatchException;
import org.seasar.codegen.util.LinkUtil;
import org.seasar.codegen.util.SequnceUtil;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.util.StringUtil;

/**
 * <a href="http://www.jiemamy.org/">Jiemamy</a>の作成したER図を元にコードを生成するクラスです。
 * 
 * @author azusa
 * 
 */
public class JiemamyImportCodeData implements ImportCodeData {

    private Dbms dbms;

    private FKNameConverter converter;

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

    /*
     * (非 Javadoc)
     * 
     * @see org.seasar.codegen.ImportCodeData#readCodeData(java.io.File)
     */
    public Map<String, Table> readCodeData(File srcFile) {
        JiemamySerializer serializer = JiemamyContext.findSerializer();
        JiemamyContext context;
        try {
            context = deserialize(srcFile, serializer);
        } catch (FileNotFoundException e) {
            throw new CodeGenException("ECDG0004", e);
        } catch (SerializationException e) {
            throw new CodeGenException("ECDG0004", e);
        }

        Set<JmTable> tableSet = context.getTables();
        Map<String, Table> tableMap = new HashMap<String, Table>();
        for (JmTable jmTable : tableSet) {
            Table table = getTable(jmTable);
            List<LinkTable> parentLink = getParentLink(tableSet, jmTable, table);
            for (LinkTable linkTable : parentLink) {
                String relName = converter.convertParent(table, linkTable);
                table.addLinkTable(relName, linkTable);
            }
            tableMap.put(table.getTableName(), table);

        }

        setupChildLinks(tableMap);
        return tableMap;
    }

    JiemamyContext deserialize(File srcFile, JiemamySerializer serializer)
            throws SerializationException, FileNotFoundException {
        return serializer.deserialize(new FileInputStream(srcFile));
    }

    Table getTable(JmTable jmTable) {
        String tableName = jmTable.getName();
        Table table = new Table();
        table.setTableName(tableName);
        List<JmColumn> columnModels = jmTable.getColumns();
        for (JmColumn columnModel : columnModels) {
            Field field = getField(columnModel, jmTable);
            table.addTableField(field);
            PrimaryKey primaryKey = getPrimaryKey(jmTable, field);
            if (primaryKey != null) {
                table.addPrimaryKey(primaryKey);
            }
        }
        return table;
    }

    Field getField(JmColumn jmColumn, JmTable jmTable) {
        Field field = new Field();
        field.setFieldName(jmColumn.getName());
        FieldSetting fieldSetting = new FieldSetting();
        String langType = dbms.convDBTypeToDataType(jmColumn.getDataType()
                .getRawTypeDescriptor().getTypeName());
        fieldSetting.setTypeName(langType);
        int length = 0;
        if (jmColumn.getDataType().getParam(TypeParameterKey.SIZE) != null) {
            Integer ilength = jmColumn.getDataType().getParam(
                    TypeParameterKey.SIZE);
            if (ilength != null) {
                length = ilength;
            }
        }
        int scale = 0;
        if (jmColumn.getDataType().getParam(TypeParameterKey.SCALE) != null) {
            scale = jmColumn.getDataType().getParam(TypeParameterKey.SCALE);

        }
        if (jmColumn.getDataType().getParam(TypeParameterKey.PRECISION) != null) {
            length = jmColumn.getDataType()
                    .getParam(TypeParameterKey.PRECISION);
        }
        fieldSetting.setColmnSize(length);
        fieldSetting.setPointNumber(scale);
        if (jmTable.isNotNullColumn(new DefaultEntityRef<JmColumn>(jmColumn))) {
            fieldSetting.setNotNull(true);
        }
        fieldSetting.setFieldDefault(jmColumn.getDefaultValue() == null ? ""
                : jmColumn.getDefaultValue());

        DataType dataType = null;
        try {
            dataType = dbms.selectBestDataType(fieldSetting);
        } catch (InternalGenerateException e) {
            throw new NotTypeMatchException(jmTable.getName(),
                    jmColumn.getName(), e);
        }
        field.setDataType(dataType);
        if (StringUtil.isEmpty(jmColumn.getLogicalName())) {
            field.setFieldAttributeName(jmColumn.getName());
        } else {
            field.setFieldAttributeName(jmColumn.getLogicalName());
        }

        return field;
    }

    PrimaryKey getPrimaryKey(JmTable jmTable, Field field) {
        if (jmTable.getPrimaryKey() != null) {
            JmPrimaryKeyConstraint keyConstraint = jmTable.getPrimaryKey();
            for (EntityRef<? extends JmColumn> columnRef : keyConstraint
                    .getKeyColumns()) {
                JmColumn jmColumn = jmTable.getColumn(columnRef);
                if (jmColumn.getName().equals(field.getFieldName())) {
                    PrimaryKey primaryKey = new PrimaryKey();
                    primaryKey.setField(field);
                    if (jmColumn.getDataType() != null
                            && jmColumn.getDataType().getParam(
                                    TypeParameterKey.SERIAL) != null
                            && jmColumn.getDataType().getParam(
                                    TypeParameterKey.SERIAL)) {
                        field.setUseIdentity(true);
                    }
                    Map<String, String> sequenceMap = codeGenConfig
                            .getSequnceMapping();
                    if (sequenceMap != null) {
                        String sequenceName = sequenceMap
                                .get(jmTable.getName());
                        SequnceUtil.addSequence(primaryKey, sequenceName);

                    }
                    return primaryKey;
                }
            }

        }
        return null;
    }

    List<LinkTable> getParentLink(Set<JmTable> tableSet, JmTable jmTable,
            Table table) {

        Collection<? extends JmForeignKeyConstraint> foreignKeys = jmTable
                .getForeignKeyConstraints();
        List<LinkTable> linkTables = new ArrayList<LinkTable>(
                foreignKeys.size());

        for (JmForeignKeyConstraint foreignKey : foreignKeys) {
            LinkTable linkTable = new LinkTable();
            linkTable.setChildFieldName(findColumn(jmTable,
                    foreignKey.getKeyColumns(), linkTable));
            JmTable parentTable = foreignKey.findReferenceTable(tableSet);
            linkTable.setParentFieldName(findColumn(parentTable,
                    foreignKey.getReferenceColumns(), linkTable));
            linkTable.setTableName(parentTable.getName());
            linkTables.add(linkTable);
        }
        return linkTables;
    }

    private String findColumn(JmTable jmTable,
            List<EntityRef<? extends JmColumn>> keyColumns, LinkTable linkTable) {
        for (EntityRef<? extends JmColumn> entityRef : keyColumns) {
            for (JmColumn column : jmTable.getColumns()) {
                if (column.getId().equals(entityRef.getReferentId())) {
                    return column.getName();
                }
            }
        }
        throw new UnsupportedOperationException();
    }

    private void setupChildLinks(Map<String, Table> tableMap) {
        LinkUtil.setupChildLinks(tableMap);
    }

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

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

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

}
