/*
 * blanco Framework
 * Copyright (C) 2004-2005 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.SQLException;

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

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import blanco.commons.util.BlancoStringUtil;
import blanco.db.collector.BlancoDbCollectorDotNet;
import blanco.db.collector.BlancoDbDatabaseConnectionDotNet;
import blanco.db.conf.BlancoDbDatabaseConnectionSettingDefDotNet;
import blanco.db.conf.BlancoDbMetadataDotNet;
import blanco.db.conf.BlancoDbSettingDotNet;
import blanco.db.definition.BlancoDbDefinitionDotNet;
import blanco.db.generator.BlancoDbGeneratorDotNet;

/**
 * blancoDbを利用する際のもっとも基本的なGeneratorを示します。
 */
public abstract class BlancoDbXml2CsClassDotNet implements
        IBlancoDbProgressDotNet {
    private boolean isLogging = false;

    public void setLogging(final boolean arg) {
        isLogging = arg;
    }

    /**
     * 最も一般的な blancoDbのGenerator処理を行います。
     * 
     * @param jdbcDriver
     *            JDBCドライバクラス名
     * @param jdbcUrl
     *            JDBC接続URI
     * @param jdbcUsername
     *            JDBCユーザ名
     * @param jdbcPassword
     *            JDBCパスワード
     * @param blancoSqlDirectory
     *            blancoSqlが格納されたディレクトリ.null指定の際には処理をスキップします.
     * @param rootPackage
     *            blancoに設定するルートパッケージ
     * @param statementTimeout
     *            ステートメントタイムアウト値。
     * @return 正常終了ならtrue, 異常終了ならfalse
     * @throws BlancoDbException
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     * @throws SQLException
     * @throws ClassNotFoundException
     * @throws TransformerException
     */
    public void process(
            final BlancoDbDatabaseConnectionSettingDefDotNet connDef,
            final File blancoSqlDirectory, final String rootPackage,
            String runtimePackage, final String statementTimeout,
            final String blancoTargetSourceDirectory) throws SQLException,
            SAXException, IOException, ParserConfigurationException,
            ClassNotFoundException, TransformerException {
        System.out.println(BlancoDbConstantsDotNet.PRODUCT_NAME + " ("
                + BlancoDbConstantsDotNet.VERSION + ") ソースコード生成: 開始.");

        final BlancoDbSettingDotNet setting = new BlancoDbSettingDotNet();
        setting.setJdbc(connDef);
        setting.setLogging(isLogging);
        if (blancoTargetSourceDirectory != null) {
            setting.setWorkDirectory(blancoTargetSourceDirectory);
        }
        if (BlancoStringUtil.null2Blank(runtimePackage).length() > 0) {
            setting.setRuntimePackage(runtimePackage);
        }

        if (BlancoStringUtil.null2Blank(statementTimeout).length() > 0) {
            try {
                setting.setStatementTimeout(Integer.parseInt(statementTimeout));
            } catch (NumberFormatException ex) {
                throw new IllegalArgumentException("ステートメントタイムアウト値として指定された値["
                        + statementTimeout + "]は数値として解析できませんでした。処理中断します。:"
                        + ex.toString());
            }
        }

        final BlancoDbDatabaseConnectionDotNet dbInfoCollector = new BlancoDbDatabaseConnectionDotNet();
        try {
            dbInfoCollector.connect(connDef);
            dbInfoCollector.getDatabaseVersionInfo();
            setting.setDriverName(dbInfoCollector.getDriverName());

            setting.setRootNameSpace(rootPackage);

            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;
                    }

                    // メタ情報は毎回 破棄します。
                    BlancoDbMetadataDotNet metadata = new BlancoDbMetadataDotNet();
                    parseAndProcessBlancoDbSettingFile(fileSettingXml[index],
                            metadata);

                    // 生成はファイル毎に行います。
                    generate(metadata, setting, dbInfoCollector, rootPackage,
                            new File(blancoSqlDirectory.getAbsolutePath()
                                    + "/valueobject"));
                }
            }

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

    /**
     * 生成を行います。
     * 
     * @param metadata
     * @param setting
     * @param conn
     * @param rootPackage
     * @param outputDirectory
     * @throws SQLException
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws BlancoDbException
     * @throws TransformerException
     */
    private void generate(final BlancoDbMetadataDotNet metadata,
            final BlancoDbSettingDotNet setting,
            final BlancoDbDatabaseConnectionDotNet conn,
            final String rootPackage, final File outputDirectory)
            throws SQLException, SAXException, IOException,
            ParserConfigurationException, TransformerException {
        final BlancoDbCollectorDotNet collector = new BlancoDbCollectorDotNet();
        final BlancoDbDefinitionDotNet definition = collector.collect(metadata,
                conn);

        final BlancoDbGeneratorDotNet generator = new BlancoDbGeneratorDotNet(
                setting);
        generator.setup(definition, rootPackage, outputDirectory);
        generator.generate();
    }

    /**
     * BlancoDb SQL定義書ファイルをパース処理して、必要があればメタ情報に追加します。 このメソッドは 外部から呼び出される場合があります。
     * 
     * @param fileSettingXml
     *            入力となるblancoDb設定ファイル
     * @param metadata
     *            出力となるメタデータ
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    private void parseAndProcessBlancoDbSettingFile(final File fileSettingXml,
            final BlancoDbMetadataDotNet metadata) throws IOException,
            ParserConfigurationException, SAXException {
        System.out.println("ファイル[" + fileSettingXml.getAbsolutePath()
                + "]を処理します");

        final DocumentBuilderFactory documentFactory = DocumentBuilderFactory
                .newInstance();
        DocumentBuilder documentBuilder = null;
        Document document = null;
        try {
            documentBuilder = documentFactory.newDocumentBuilder();
        } catch (ParserConfigurationException ex1) {
            System.err.println("ドキュメント作成時に例外が発生しました.:" + ex1.toString());
            ex1.printStackTrace();
            throw ex1;
        }
        try {
            document = documentBuilder.parse(fileSettingXml);
        } catch (SAXException e) {
            System.err.println("ドキュメントパース時に例外が発生しました:" + e.toString());
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            System.err.println("ドキュメントパース時に例外が発生しました:" + e.toString());
            e.printStackTrace();
            throw e;
        }
        final Element blancoDbElement = document.getDocumentElement();
        if (blancoDbElement == null) {
            System.out.println("blanco-dbルートノードが見つかりません");
            return;
        }
        final NodeList rootNodeList = blancoDbElement.getChildNodes();
        if (rootNodeList == null) {
            return;
        }
        metadata.addXml(fileSettingXml);
    }
}