/*
 * Copyright 2004-2015 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.extension.jdbc.gen.internal.desc;

import java.lang.reflect.Field;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.SequenceGenerator;

import org.seasar.extension.jdbc.EntityMeta;
import org.seasar.extension.jdbc.PropertyMeta;
import org.seasar.extension.jdbc.ValueType;
import org.seasar.extension.jdbc.gen.desc.SequenceDesc;
import org.seasar.extension.jdbc.gen.desc.SequenceDescFactory;
import org.seasar.extension.jdbc.gen.dialect.GenDialect;
import org.seasar.extension.jdbc.gen.internal.exception.UnsupportedGenerationTypeRuntimeException;
import org.seasar.extension.jdbc.gen.internal.util.AnnotationUtil;
import org.seasar.extension.jdbc.gen.provider.ValueTypeProvider;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.StringUtil;

/**
 * {@link SequenceDescFactory}の実装クラスです。
 * 
 * @author taedium
 */
public class SequenceDescFactoryImpl implements SequenceDescFactory {

    /** ロガー */
    protected static Logger logger = Logger
            .getLogger(SequenceDescFactoryImpl.class);

    /** 方言 */
    protected GenDialect dialect;

    /** {@link ValueType}の提供者 */
    protected ValueTypeProvider valueTypeProvider;

    /**
     * インスタンスを構築します。
     * 
     * @param dialect
     *            方言
     * @param valueTypeProvider
     *            {@link ValueType}の提供者
     */
    public SequenceDescFactoryImpl(GenDialect dialect,
            ValueTypeProvider valueTypeProvider) {
        if (dialect == null) {
            throw new NullPointerException("dialect");
        }
        if (valueTypeProvider == null) {
            throw new NullPointerException("valueTypeResolver");
        }
        this.dialect = dialect;
        this.valueTypeProvider = valueTypeProvider;
    }

    public SequenceDesc getSequenceDesc(EntityMeta entityMeta,
            PropertyMeta propertyMeta) {
        GenerationType generationType = propertyMeta.getGenerationType();
        if (generationType == GenerationType.AUTO) {
            generationType = dialect.getDefaultGenerationType();
        }
        if (generationType == GenerationType.SEQUENCE) {
            if (!dialect.supportsSequence()) {
                throw new UnsupportedGenerationTypeRuntimeException(
                        GenerationType.SEQUENCE, entityMeta.getName(),
                        propertyMeta.getName());
            }
            SequenceGenerator generator = getSequenceGenerator(entityMeta,
                    propertyMeta);
            SequenceDesc sequenceDesc = new SequenceDesc();
            String sequenceName = getSequenceName(entityMeta, propertyMeta,
                    generator);
            sequenceDesc.setSequenceName(sequenceName);
            sequenceDesc.setInitialValue(generator.initialValue());
            sequenceDesc.setAllocationSize(generator.allocationSize());
            sequenceDesc.setDataType(getDataType(propertyMeta));
            return sequenceDesc;
        }
        return null;
    }

    /**
     * シーケンスジェネレータを返します。
     * 
     * @param entityMeta
     *            エンティティメタデータ
     * @param propertyMeta
     *            プロパティメタデータ
     * @return シーケンスジェネレータ
     */
    protected SequenceGenerator getSequenceGenerator(EntityMeta entityMeta,
            PropertyMeta propertyMeta) {
        Field field = propertyMeta.getField();
        GeneratedValue generatedValue = field
                .getAnnotation(GeneratedValue.class);
        if (generatedValue == null) {
            throw new IllegalStateException("@GeneratedValue not found.");
        }
        String name = generatedValue.generator();
        if (StringUtil.isEmpty(name)) {
            return AnnotationUtil.getDefaultSequenceGenerator();
        }
        SequenceGenerator sequenceGenerator = field
                .getAnnotation(SequenceGenerator.class);
        if (sequenceGenerator != null && name.equals(sequenceGenerator.name())) {
            return sequenceGenerator;
        }
        sequenceGenerator = entityMeta.getEntityClass().getAnnotation(
                SequenceGenerator.class);
        if (sequenceGenerator != null && name.equals(sequenceGenerator.name())) {
            return sequenceGenerator;
        }
        throw new IllegalStateException("@SequenceGenerator not found.");
    }

    /**
     * シーケンスの名前を返します。
     * 
     * @param entityMeta
     *            エンティティメタデータ
     * @param propertyMeta
     *            プロパティメタデータ
     * @param sequenceGenerator
     *            シーケンスジェネレータ
     * @return シーケンスの名前
     */
    protected String getSequenceName(EntityMeta entityMeta,
            PropertyMeta propertyMeta, SequenceGenerator sequenceGenerator) {
        String sequenceName = sequenceGenerator.sequenceName();
        if (!StringUtil.isEmpty(sequenceName)) {
            return sequenceName;
        }
        return entityMeta.getTableMeta().getName() + "_"
                + propertyMeta.getColumnMeta().getName();
    }

    /**
     * シーケンスのデータ型を返します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @return シーケンスのデータ型
     */
    protected String getDataType(PropertyMeta propertyMeta) {
        ValueType valueType = valueTypeProvider.provide(propertyMeta);
        int sqlType = valueType.getSqlType();
        Column column = getColumn(propertyMeta);
        return dialect.getSqlType(sqlType).getDataType(column.length(),
                column.precision(), column.scale(), false);
    }

    /**
     * カラムを返します。
     * 
     * @param propertyMeta
     *            プロパティメタデータ
     * @return カラム
     */
    protected Column getColumn(PropertyMeta propertyMeta) {
        Field field = propertyMeta.getField();
        Column column = field.getAnnotation(Column.class);
        return column != null ? column : AnnotationUtil.getDefaultColumn();
    }

}
