/*
 * BlancoDbDefaultGenerator.
 * もっとも典型的なBlancoDbのGenerator
 * 
 * Copyright (C) 2005 tosiki.iga@nifty.ne.jp
 */

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.BlancoDbCollector;
import blanco.db.collector.BlancoDbDatabaseConnection;
import blanco.db.conf.BlancoDbDatabaseConnectionSettingDef;
import blanco.db.conf.BlancoDbMetadata;
import blanco.db.conf.BlancoDbSetting;
import blanco.db.definition.BlancoDbDefinition;
import blanco.db.generator.BlancoDbGenerator;

/**
 * blancoDbを利用する際のもっとも基本的なGeneratorを示します。
 */
public abstract class BlancoDbXml2JavaClass implements IBlancoDbProgress {
    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 runtimePackage
     *            blancoに設定するランタイムパッケージ。nullならデフォルトに出力。
     * @param statementTimeout
     *            ステートメントタイムアウト値。
     * @return 正常終了ならtrue, 異常終了ならfalse
     * @throws BlancoDbException
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     * @throws SQLException
     * @throws ClassNotFoundException
     * @throws TransformerException
     */
    public void process(final BlancoDbDatabaseConnectionSettingDef 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(BlancoDbConstants.PRODUCT_NAME + " ("
                + BlancoDbConstants.VERSION + ") ソースコード生成: 開始.");

        if (BlancoStringUtil.null2Blank(runtimePackage).trim().length() == 0) {
            runtimePackage = null;
        }

        final BlancoDbSetting setting = new BlancoDbSetting();
        setting.setJdbc(connDef);
        setting.setLogging(isLogging);
        if (blancoTargetSourceDirectory != null) {
            setting.setWorkDirectory(blancoTargetSourceDirectory);
        }
        if (runtimePackage != null) {
            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 BlancoDbDatabaseConnection dbInfoCollector = new BlancoDbDatabaseConnection();
        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;
                    }

                    // メタ情報は毎回 破棄します。
                    BlancoDbMetadata metadata = new BlancoDbMetadata();
                    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 BlancoDbMetadata metadata,
            final BlancoDbSetting setting,
            final BlancoDbDatabaseConnection conn, final String rootPackage,
            final File outputDirectory) throws SQLException, SAXException,
            IOException, ParserConfigurationException, TransformerException {
        final BlancoDbCollector collector = new BlancoDbCollector();
        final BlancoDbDefinition definition = collector.collect(metadata, conn);

        final BlancoDbGenerator generator = new BlancoDbGenerator(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 BlancoDbMetadata 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);
    }
}