/*
 * 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.version;

import java.io.File;
import java.util.List;

import org.seasar.extension.jdbc.gen.internal.exception.IllegalVersionRuntimeException;
import org.seasar.extension.jdbc.gen.sql.SqlExecutionContext;
import org.seasar.extension.jdbc.gen.sql.SqlUnitExecutor;
import org.seasar.extension.jdbc.gen.version.DdlVersionDirectory;
import org.seasar.extension.jdbc.gen.version.DdlVersionDirectoryTree;
import org.seasar.extension.jdbc.gen.version.Migrater;
import org.seasar.extension.jdbc.gen.version.SchemaInfoTable;
import org.seasar.framework.log.Logger;

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

    /** 最新バージョンを表す文字列 */
    protected static String LATEST_VERSION = "latest";

    /** 1つ次のバージョンを表す文字列 */
    protected static String NEXT_VERSION = "next";

    /** 1つ前のバージョンを表す文字列 */
    protected static String PREVIOUS_VERSION = "previous";

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

    /** SQLのひとまとまりの実行者 */
    protected SqlUnitExecutor sqlUnitExecutor;

    /** スキーマのバージョン */
    protected SchemaInfoTable schemaInfoTable;

    /** DDLのバージョンを管理するディレクトリ */
    protected DdlVersionDirectoryTree ddlVersionDirectoryTree;

    /** 環境名 */
    protected String env;

    /** バージョン */
    protected String version;

    /**
     * インスタンスを構築します。
     * 
     * @param sqlUnitExecutor
     *            SQLのひとまとまりの実行者
     * @param schemaInfoTable
     *            スキーマのバージョン
     * @param ddlVersionDirectoryTree
     *            DDLをバージョン管理するディレクトリ
     * @param version
     *            バージョン
     * @param env
     *            環境名
     */
    public MigraterImpl(SqlUnitExecutor sqlUnitExecutor,
            SchemaInfoTable schemaInfoTable,
            DdlVersionDirectoryTree ddlVersionDirectoryTree, String version,
            String env) {
        if (sqlUnitExecutor == null) {
            throw new NullPointerException("sqlUnitExecutor");
        }
        if (schemaInfoTable == null) {
            throw new NullPointerException("schemaInfoTable");
        }
        if (ddlVersionDirectoryTree == null) {
            throw new NullPointerException("ddlVersionDirectoryTree");
        }
        if (version == null) {
            throw new NullPointerException("version");
        }
        if (env == null) {
            throw new NullPointerException("env");
        }
        this.sqlUnitExecutor = sqlUnitExecutor;
        this.schemaInfoTable = schemaInfoTable;
        this.ddlVersionDirectoryTree = ddlVersionDirectoryTree;
        this.version = version;
        this.env = env;
    }

    public void migrate(Callback callback) {
        int from = schemaInfoTable.getVersionNo();
        int to = getDestVersion(from);

        logger.log("IS2JDBCGen0005", new Object[] { from, to });
        migrateInternal(callback, from, to);
        logger.log("IS2JDBCGen0006", new Object[] { from, to });
    }

    /**
     * マイグレーション先のバージョン番号を返します。
     * 
     * @param from
     *            マイグレーション元のバージョン番号
     * @return マイグレーション先のバージョン番号
     */
    protected int getDestVersion(int from) {
        int latest = ddlVersionDirectoryTree.getDdlInfoFile()
                .getCurrentVersionNo();
        if (LATEST_VERSION.equalsIgnoreCase(version)) {
            return latest;
        } else if (NEXT_VERSION.equalsIgnoreCase(version)) {
            long next = from + 1;
            if (next > latest) {
                return latest;
            }
            checkVersionRange(next);
            return (int) next;
        } else if (PREVIOUS_VERSION.equalsIgnoreCase(version)) {
            long previous = from - 1;
            if (previous < 0) {
                return 0;
            }
            checkVersionRange(previous);
            return (int) previous;
        }
        return convertToInt(version);
    }

    /**
     * 文字列としてのバージョン番号をint型に変換します。
     * 
     * @param value
     *            文字列としてのバージョン番号
     * @return intとしてのバージョン番号
     */
    protected int convertToInt(String value) {
        int versionNo;
        try {
            versionNo = Integer.valueOf(value);
        } catch (NumberFormatException e) {
            throw new IllegalVersionRuntimeException(version, value);
        }
        if (versionNo < 0) {
            throw new IllegalVersionRuntimeException(version, value);
        }
        return versionNo;
    }

    /**
     * バージョン番号の範囲をチェックします。
     * 
     * @param value
     *            バージョン番号
     */
    protected void checkVersionRange(long value) {
        if (value < 0 || Integer.MAX_VALUE < value) {
            new IllegalVersionRuntimeException(version, value);
        }
    }

    /**
     * マイグレーション処理を実行します。
     * 
     * @param callback
     *            コールバック
     * @param from
     *            マイグレーション元のバージョン番号
     * @param to
     *            マイグレーション先のバージョン番号
     */
    protected void migrateInternal(final Callback callback, final int from,
            final int to) {
        DdlVersionDirectory fromVersionDir = ddlVersionDirectoryTree
                .getVersionDirectory(from);
        final List<File> dropFileList = fromVersionDir.getDropDirectory()
                .listAllFiles();

        DdlVersionDirectory toVersionDir = ddlVersionDirectoryTree
                .getVersionDirectory(to);
        final List<File> createFileList = toVersionDir.getCreateDirectory()
                .listAllFiles();

        sqlUnitExecutor.execute(new SqlUnitExecutor.Callback() {

            public void execute(SqlExecutionContext context) {
                for (File file : dropFileList) {
                    callback.drop(context, file);
                }
                schemaInfoTable.setVersionNo(to);
                for (File file : createFileList) {
                    callback.create(context, file);
                }
            }
        });
    }
}
