/*
 * blanco Framework
 * Copyright (C) 2004-2006 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.db;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.xml.sax.SAXException;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.BlancoCgTransformer;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.commons.util.BlancoStringUtil;
import blanco.db.common.BlancoDbXml2SqlInfo;
import blanco.db.common.IBlancoDbProgress;
import blanco.db.common.stringgroup.BlancoDbSqlInfoTypeStringGroup;
import blanco.db.common.util.BlancoDbUtil;
import blanco.db.common.valueobject.BlancoDbSetting;
import blanco.db.common.valueobject.BlancoDbSqlInfoStructure;
import blanco.db.expander.exception.BlancoDbExceptionClassDotNet;
import blanco.db.expander.exception.DeadlockExceptionClassDotNet;
import blanco.db.expander.exception.IntegrityConstraintExceptionClassDotNet;
import blanco.db.expander.exception.NoRowFoundExceptionClassDotNet;
import blanco.db.expander.exception.NoRowModifiedExceptionClassDotNet;
import blanco.db.expander.exception.NotSingleRowExceptionClassDotNet;
import blanco.db.expander.exception.TimeoutExceptionClassDotNet;
import blanco.db.expander.exception.TooManyRowsFoundExceptionClassDotNet;
import blanco.db.expander.exception.TooManyRowsModifiedExceptionClassDotNet;
import blanco.db.expander.query.caller.QueryCallerClassDotNet;
import blanco.db.expander.query.caller.odp.QueryCallerClassOdpDotNet;
import blanco.db.expander.query.invoker.QueryInvokerClassDotNet;
import blanco.db.expander.query.invoker.odp.QueryInvokerClassOdpDotNet;
import blanco.db.expander.query.iterator.QueryIteratorClassDotNet;
import blanco.db.expander.query.iterator.odp.QueryIteratorClassOdpDotNet;
import blanco.db.stringgroup.BlancoDbDotNetDataAccessTypeStringGroup;
import blanco.db.util.BlancoDbMappingUtilDotNet;
import blanco.db.util.BlancoDbUtilClassDotNet;
import blanco.db.util.odp.BlancoDbUtilClassOdpDotNet;
import blanco.dbmetadata.valueobject.BlancoDbMetaDataColumnStructure;
import blanco.valueobjectdotnet.BlancoValueObjectDotNetXml2SourceFile;
import blanco.valueobjectdotnet.valueobject.BlancoValueObjectDotNetClassStructure;
import blanco.valueobjectdotnet.valueobject.BlancoValueObjectDotNetFieldStructure;

/**
 * 中間XMLファイルからソースコードを生成します。
 */
public abstract class BlancoDbXml2CsClassDotNet implements IBlancoDbProgress {
    private BlancoDbSetting fDbSetting = null;
    private int fDataAccessType = 0;

    /**
     * XMLファイルからソースコードを生成します。
     * 
     * @param connDef
     *            データベース接続情報。
     * @param blancoSqlDirectory
     *            SQL XMLファイルが格納されているディレクトリ。
     * @param rootPackage
     *            ルートとなる基準パッケージ。
     * @param runtimePackage
     *            blancoに設定するランタイムパッケージ。nullならデフォルトに出力。
     * @param statementTimeout
     *            ステートメントタイムアウト値。
     * @param blancoTargetSourceDirectory
     *            出力先ディレクトリ。
     * @throws SQLException
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws ClassNotFoundException
     * @throws TransformerException
     */
    public void process(final BlancoDbSetting argDbSetting,
            final File blancoSqlDirectory, int dataAccessType)
            throws SQLException, SAXException, IOException,
            ParserConfigurationException, ClassNotFoundException,
            TransformerException {
        System.out.println(BlancoDbDotNetConstants.PRODUCT_NAME + " ("
                + BlancoDbDotNetConstants.VERSION + ") ソースコード生成: 開始.");

        fDbSetting = argDbSetting;
        fDataAccessType = dataAccessType;

        if (BlancoStringUtil.null2Blank(fDbSetting.getRuntimePackage()).trim()
                .length() == 0) {
            fDbSetting.setRuntimePackage(null);
        }

        Connection conn = null;
        try {
            conn = BlancoDbUtil.connect(fDbSetting);
            BlancoDbUtil.getDatabaseVersionInfo(conn, fDbSetting);

            if (blancoSqlDirectory != null) {
                // 指定がある場合にのみ SQL定義書ファイル格納ディレクトリを処理します。

                // ValueObject情報を格納するディレクトリを作成します。
                new File(blancoSqlDirectory.getAbsolutePath() + "/valueobject")
                        .mkdirs();

                final File[] fileSettingXml = blancoSqlDirectory.listFiles();
                for (int index = 0; index < fileSettingXml.length; index++) {
                    if (fileSettingXml[index].getName().endsWith(".xml") == false) {
                        // ファイルの拡張子が xml であるもののみ処理します。
                        continue;
                    }
                    if (progress(index + 1, fileSettingXml.length,
                            fileSettingXml[index].getName()) == false) {
                        break;
                    }

                    // 生成はファイル毎に行います。
                    processEveryFile(conn, fileSettingXml[index], new File(
                            blancoSqlDirectory.getAbsolutePath()
                                    + "/valueobject"));
                }
            }

        } finally {
            BlancoDbUtil.close(conn);
            conn = null;
            System.out.println("ソースコード生成: 終了.");
        }
    }

    /**
     * 個別のXMLファイルを処理します。
     * 
     * @param dbInfoCollector
     * @param rootPackage
     * @param fileSqlForm
     * @param outputDirectory
     * @throws IOException
     * @throws SAXException
     * @throws TransformerException
     * @throws SQLException
     * @throws ParserConfigurationException
     */
    private void processEveryFile(final Connection conn,
            final File fileSqlForm, final File outputDirectory)
            throws IOException, SAXException, TransformerException,
            SQLException, ParserConfigurationException {

        System.out.println("ファイル[" + fileSqlForm.getAbsolutePath() + "]を処理します");

        final BlancoDbXml2SqlInfo collector = new BlancoDbXml2SqlInfo();
        final List<BlancoDbSqlInfoStructure> definition = collector.process(
                conn, fDbSetting, fileSqlForm);

        final String packageNameException = BlancoDbUtil
                .getRuntimePackage(fDbSetting)
                + ".exception";

        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(fDbSetting.getTargetDir()
                + "/main");

        final BlancoCgObjectFactory cgFactory = BlancoCgObjectFactory
                .getInstance();

        final BlancoCgTransformer transformer = BlancoCgTransformerFactory
                .getCsSourceTransformer();

        // exception系
        transformer.transform(adjust(new BlancoDbExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new DeadlockExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(
                adjust(new IntegrityConstraintExceptionClassDotNet(cgFactory,
                        packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new NoRowFoundExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new NoRowModifiedExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new NotSingleRowExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new TimeoutExceptionClassDotNet(cgFactory,
                packageNameException).expand()), fileBlancoMain);
        transformer.transform(adjust(new TooManyRowsFoundExceptionClassDotNet(
                cgFactory, packageNameException).expand()), fileBlancoMain);
        transformer.transform(
                adjust(new TooManyRowsModifiedExceptionClassDotNet(cgFactory,
                        packageNameException).expand()), fileBlancoMain);

        // util系
        switch (fDataAccessType) {
        case BlancoDbDotNetDataAccessTypeStringGroup.ADO:
            transformer.transform(adjust(new BlancoDbUtilClassDotNet(cgFactory,
                    BlancoDbUtil.getRuntimePackage(fDbSetting) + ".util",
                    fDbSetting).expand()), fileBlancoMain);
            break;
        case BlancoDbDotNetDataAccessTypeStringGroup.ODP:
            transformer.transform(adjust(new BlancoDbUtilClassOdpDotNet(
                    cgFactory, BlancoDbUtil.getRuntimePackage(fDbSetting)
                            + ".util", fDbSetting).expand()), fileBlancoMain);
            break;
        default:
            throw new IllegalArgumentException("想定外のエラー。不明なデータアクセスタイプが与えられました:"
                    + Integer.toString(fDataAccessType));
        }

        // iterator, invoker, caller
        for (int index = 0; index < definition.size(); index++) {
            final BlancoDbSqlInfoStructure sqlInfo = (BlancoDbSqlInfoStructure) definition
                    .get(index);
            switch (sqlInfo.getType()) {
            case BlancoDbSqlInfoTypeStringGroup.ITERATOR:
                createRowObjectClass(fDbSetting.getBasePackage(), sqlInfo,
                        outputDirectory);

                switch (fDataAccessType) {
                case BlancoDbDotNetDataAccessTypeStringGroup.ADO:
                    transformer.transform(adjust(new QueryIteratorClassDotNet(
                            fDbSetting, sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                case BlancoDbDotNetDataAccessTypeStringGroup.ODP:
                    transformer.transform(
                            adjust(new QueryIteratorClassOdpDotNet(fDbSetting,
                                    sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                default:
                    throw new IllegalArgumentException(
                            "想定外のエラー。不明なデータアクセスタイプが与えられました:"
                                    + Integer.toString(fDataAccessType));
                }
                break;
            case BlancoDbSqlInfoTypeStringGroup.INVOKER:
                switch (fDataAccessType) {
                case BlancoDbDotNetDataAccessTypeStringGroup.ADO:
                    transformer.transform(adjust(new QueryInvokerClassDotNet(
                            fDbSetting, sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                case BlancoDbDotNetDataAccessTypeStringGroup.ODP:
                    transformer.transform(
                            adjust(new QueryInvokerClassOdpDotNet(fDbSetting,
                                    sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                default:
                    throw new IllegalArgumentException(
                            "想定外のエラー。不明なデータアクセスタイプが与えられました:"
                                    + Integer.toString(fDataAccessType));
                }
                break;
            case BlancoDbSqlInfoTypeStringGroup.CALLER:
                switch (fDataAccessType) {
                case BlancoDbDotNetDataAccessTypeStringGroup.ADO:
                    transformer.transform(adjust(new QueryCallerClassDotNet(
                            fDbSetting, sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                case BlancoDbDotNetDataAccessTypeStringGroup.ODP:
                    transformer.transform(adjust(new QueryCallerClassOdpDotNet(
                            fDbSetting, sqlInfo, cgFactory).expand()),
                            fileBlancoMain);
                    break;
                default:
                    throw new IllegalArgumentException(
                            "想定外のエラー。不明なデータアクセスタイプが与えられました:"
                                    + Integer.toString(fDataAccessType));
                }
                break;
            default:
                throw new IllegalArgumentException(
                        "想定外のエラー。不明なクエリオブジェクトが与えられました。" + sqlInfo.toString());
            }
        }
    }

    /**
     * 行オブジェクトを作成します。
     * 
     * @param className
     * @param packageName
     * @param sqlInfo
     * @param outputDirectory
     * @throws SAXException
     * @throws IOException
     * @throws TransformerException
     */
    private void createRowObjectClass(final String rootPackage,
            final BlancoDbSqlInfoStructure sqlInfo, final File outputDirectory)
            throws SAXException, IOException, TransformerException {
        final String packageName = rootPackage + ".row";
        final String className = BlancoNameAdjuster.toClassName(sqlInfo
                .getName())
                + "Row";

        final List<String[]> listFieldTypes = new ArrayList<String[]>();
        for (int index = 0; index < sqlInfo.getResultSetColumnList().size(); index++) {
            final BlancoDbMetaDataColumnStructure columnStructure = (BlancoDbMetaDataColumnStructure) sqlInfo
                    .getResultSetColumnList().get(index);

            listFieldTypes
                    .add(new String[] {
                            columnStructure.getName(),
                            BlancoDbMappingUtilDotNet
                                    .getFullClassName(columnStructure) });
        }

        final BlancoValueObjectDotNetClassStructure voClass = new BlancoValueObjectDotNetClassStructure();

        voClass.setName(className);
        voClass.setPackage(packageName);
        voClass.setDescription("SQL定義書(blancoDb)から作成された行クラス。");
        voClass.getDescriptionList().add("'" + className + "'行を表現します。");
        for (int index = 0; index < listFieldTypes.size(); index++) {
            final String[] columnTypes = (String[]) listFieldTypes.get(index);
            final String columnName = columnTypes[0];
            final String columnType = columnTypes[1];

            voClass.getDescriptionList().add(
                    "(" + String.valueOf(index + 1) + ") '" + columnName
                            + "'列 型:" + columnType);
        }

        for (int index = 0; index < listFieldTypes.size(); index++) {
            final String[] columnTypes = (String[]) listFieldTypes.get(index);
            final String columnName = columnTypes[0];
            final String columnType = columnTypes[1];

            final BlancoValueObjectDotNetFieldStructure voField = new BlancoValueObjectDotNetFieldStructure();
            voField.setName(columnName);
            voField.setType(columnType);
            voField.setDescription("フィールド[" + columnName + "]です。");
            voClass.getFieldList().add(voField);
        }

        final BlancoValueObjectDotNetXml2SourceFile xml2javaclass = new BlancoValueObjectDotNetXml2SourceFile();
        // TODO C#.NET 版の blancoValueObjectDotnet には、エンコーディング指定機能がまだありません。
        // xml2javaclass.setEncoding(fDbSetting.getEncoding());
        if (fDbSetting.getTargetDir() == null) {
            throw new IllegalArgumentException(
                    "BlancoDbGenerator: blanco出力先フォルダが未設定(null)です。");
        }
        xml2javaclass.structure2Source(voClass, new File(fDbSetting
                .getTargetDir()));
    }

    /**
     * ソース・オブジェクトの内容を調整。
     * 
     * <UL>
     * <LI>ソースコードのエンコーディングを設定。
     * </UL>
     * 
     * @param arg
     * @return
     */
    private BlancoCgSourceFile adjust(final BlancoCgSourceFile arg) {
        if (BlancoStringUtil.null2Blank(fDbSetting.getEncoding()).length() > 0) {
            arg.setEncoding(fDbSetting.getEncoding());
        }
        return arg;
    }
}
