/*
 * blancoDb
 * Copyright (C) 2004-2005 Yasuo Nakanishi
 * 
 * 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.generator;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;

import org.xml.sax.SAXException;

import sun.nio.cs.Surrogate.Generator;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.db.conf.BlancoDbSetting;
import blanco.db.definition.BlancoDbDefinition;
import blanco.db.definition.QueryCaller;
import blanco.db.definition.QueryField;
import blanco.db.definition.QueryInvoker;
import blanco.db.definition.QueryIterator;
import blanco.db.definition.TableField;
import blanco.db.exception.DeadlockExceptionClass;
import blanco.db.exception.IntegrityConstraintExceptionClass;
import blanco.db.exception.NoRowFoundExceptionClass;
import blanco.db.exception.NoRowModifiedExceptionClass;
import blanco.db.exception.NotSingleRowExceptionClass;
import blanco.db.exception.TimeoutExceptionClass;
import blanco.db.exception.TooManyRowsFoundExceptionClass;
import blanco.db.exception.TooManyRowsModifiedExceptionClass;
import blanco.db.expander.query.caller.QueryCallerClass;
import blanco.db.expander.query.invoker.QueryInvokerClass;
import blanco.db.expander.query.iterator.QueryIteratorClass;
import blanco.db.util.BlancoDbObjectStorage;
import blanco.db.util.BlancoDbUtilClass;
import blanco.ig.expander.Type;
import blanco.ig.generator.ImplementGenerator;
import blanco.valueobject.BlancoValueObjectXml2JavaClass;
import blanco.valueobject.concretesax.BlancoValueObjectSerializer;

/**
 * @author Yasuo Nakanishi
 */
public class BlancoDbGenerator extends Generator {
    private final BlancoDbObjectStorage storage = new BlancoDbObjectStorage();

    private ImplementGenerator _generator = null;

    public BlancoDbGenerator(final BlancoDbSetting setting) {
        storage.setSetting(setting);
    }

    /**
     * クラス出力処理のセットアップを行います。
     * 
     * @param definition
     * @throws IOException
     * @throws SAXException
     * @throws TransformerException
     */
    public void setup(final BlancoDbDefinition definition,
            final String rootPackage, final File outputDirectory)
            throws SAXException, IOException, TransformerException {

        _generator = new ImplementGenerator(storage.getSetting());

        setupQueryIteratorExpander(definition, rootPackage, outputDirectory);
        setupQueryInvokerExpander(definition);
        setupQueryCallerExpander(definition);
        setupUtilExpander(definition);
        setupExceptionExpander(definition);
    }

    private void setupQueryIteratorExpander(
            final BlancoDbDefinition definition, final String rootPackage,
            final File outputDirectory) throws SAXException, IOException,
            TransformerException {
        final Iterator iterator = definition.getQueryIteratorIterator();
        while (iterator.hasNext()) {
            final QueryIterator queryIterator = (QueryIterator) iterator.next();
            createRowObjectClass(queryIterator, rootPackage, outputDirectory);
            _generator.addMain(new QueryIteratorClass(storage, new Type(storage
                    .getSetting().getRootNameSpace()
                    + ".query", BlancoNameAdjuster.toClassName(queryIterator
                    .getName())
                    + "Iterator"), queryIterator));

        }
    }

    private void setupQueryInvokerExpander(final BlancoDbDefinition definition) {
        final Iterator iterator = definition.getQueryInvokerIterator();
        while (iterator.hasNext()) {
            final QueryInvoker invoker = (QueryInvoker) iterator.next();
            _generator.addMain(new QueryInvokerClass(storage, new Type(storage
                    .getSetting().getRootNameSpace()
                    + ".query", BlancoNameAdjuster.toClassName(invoker
                    .getName())
                    + "Invoker"), invoker));
        }
    }

    private void setupQueryCallerExpander(BlancoDbDefinition definition)
            throws TransformerConfigurationException, SAXException, IOException {
        final Iterator iterator = definition.getQueryCallerIterator();
        while (iterator.hasNext()) {
            final QueryCaller queryCaller = (QueryCaller) iterator.next();
            _generator.addMain(new QueryCallerClass(storage, new Type(storage
                    .getSetting().getRootNameSpace()
                    + ".query", BlancoNameAdjuster.toClassName(queryCaller
                    .getName())
                    + "Caller"), queryCaller));
        }
    }

    /**
     * ランタイムユーティリティクラスの生成をおこないます。
     * 
     * @param definition
     * @throws TransformerConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    public void setupUtilExpander(final BlancoDbDefinition definition)
            throws TransformerConfigurationException, SAXException, IOException {
        // ランタイムパッケージの変化に対応。
        String runtimePackage = storage.getSetting().getRootNameSpace();
        if (storage.getSetting().getRuntimePackage() != null) {
            runtimePackage = storage.getSetting().getRuntimePackage();
        }

        _generator.addMain(new BlancoDbUtilClass(storage, new Type(
                runtimePackage + ".util", BlancoDbUtilClass.CLASS_NAME)));
    }

    public void setupExceptionExpander(final BlancoDbDefinition definition)
            throws TransformerConfigurationException, SAXException, IOException {
        // ランタイムパッケージの変化に対応。
        String runtimePackage = storage.getSetting().getRootNameSpace();
        if (storage.getSetting().getRuntimePackage() != null) {
            runtimePackage = storage.getSetting().getRuntimePackage();
        }

        final ImplementGenerator generatorRuntime = new ImplementGenerator(
                storage.getSetting());

        final String packageNameException = runtimePackage + ".exception";
        generatorRuntime.addMain(new IntegrityConstraintExceptionClass(
                new Type(packageNameException,
                        IntegrityConstraintExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new DeadlockExceptionClass(new Type(
                packageNameException, DeadlockExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new TimeoutExceptionClass(new Type(
                packageNameException, TimeoutExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new NoRowFoundExceptionClass(new Type(
                packageNameException, NoRowFoundExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new NoRowModifiedExceptionClass(new Type(
                packageNameException, NoRowModifiedExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new NotSingleRowExceptionClass(new Type(
                packageNameException, NotSingleRowExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new TooManyRowsFoundExceptionClass(
                new Type(packageNameException,
                        TooManyRowsFoundExceptionClass.CLASS_NAME)));
        generatorRuntime.addMain(new TooManyRowsModifiedExceptionClass(
                new Type(packageNameException,
                        TooManyRowsModifiedExceptionClass.CLASS_NAME)));

        generatorRuntime.generate();
    }

    private void createRowObjectClass(final QueryIterator queryIterator,
            final String rootPackage, final File outputDirectory)
            throws SAXException, IOException, TransformerException {
        final String typeName = BlancoNameAdjuster.toClassName(queryIterator
                .getName())
                + "Row";
        // Queryの場合には、クラス名への変形を実施しておく必要があります。
        createRowObjectClass(typeName, rootPackage + ".row", queryIterator
                .getFieldIterator(), outputDirectory);
    }

    private void createRowObjectClass(final String className,
            final String packageName, final Iterator iterator,
            final File outputDirectory) throws SAXException, IOException,
            TransformerException {
        ArrayList listFieldTypes = new ArrayList();
        for (; iterator.hasNext();) {
            String fieldName = null;
            String fieldType = null;
            Object objField = iterator.next();
            if (objField instanceof TableField) {
                TableField field = (TableField) objField;
                fieldName = field.getName();
                fieldType = field.getJavaType().getFullName();
            } else if (objField instanceof QueryField) {
                QueryField field = (QueryField) objField;
                fieldName = field.getName();
                fieldType = field.getJavaType().getFullName();
            }

            if (fieldType.equals("java.sql.Date")) {
                // java.sql.Dateは java.util.Dateに読み替えます。
                fieldType = "java.util.Date";
            }

            listFieldTypes.add(new String[] { fieldName, fieldType });
        }

        OutputStream outStream = null;
        final File fileWorkXml = new File(outputDirectory.getAbsolutePath()
                + "/" + className + ".blancovalueobject");
        try {
            outStream = new BufferedOutputStream(new FileOutputStream(
                    fileWorkXml));
            BlancoValueObjectSerializer serializer = new BlancoValueObjectSerializer(
                    outStream);
            serializer.startDocument();
            serializer.startElementWorkbook();
            serializer.characters("\n");
            serializer.startElementSheet("ValueObject");
            serializer.characters("\n");
            serializer.characters("  ");
            serializer.startElementBlancovalueobjectCommon();
            serializer.characters("\n");
            serializer.characters("    ");
            serializer.startElementName();
            // 名前
            serializer.characters(className);
            serializer.endElementName();
            serializer.characters("\n");
            serializer.characters("    ");
            serializer.startElementPackage();
            serializer.characters(packageName);
            serializer.endElementPackage();
            serializer.characters("\n");
            serializer.characters("    ");
            serializer.startElementDescription();
            serializer.characters("SQL定義書(blancoDb)から作成された行クラス。\n");
            serializer.characters("'" + className + "'行を表現します。\n");
            for (int index = 0; index < listFieldTypes.size(); index++) {
                String[] fieldTypes = (String[]) listFieldTypes.get(index);
                String fieldName = fieldTypes[0];
                String fieldType = fieldTypes[1];

                serializer.characters("(" + String.valueOf(index + 1) + ") '"
                        + fieldName + "'列 型:" + fieldType + "\n");
            }
            serializer.endElementDescription();
            serializer.characters("\n");
            serializer.characters("    ");
            serializer.startElementFileDescription();
            serializer.characters("'" + className + "'行を表現する行クラス。\n");

            serializer.endElementFileDescription();
            serializer.characters("\n");
            serializer.characters("  ");
            serializer.endElementBlancovalueobjectCommon();
            serializer.characters("\n");
            serializer.characters("  ");
            serializer.startElementBlancovalueobjectList();
            serializer.characters("\n");

            for (int index = 0; index < listFieldTypes.size(); index++) {
                String[] fieldTypes = (String[]) listFieldTypes.get(index);
                String fieldName = fieldTypes[0];
                String fieldType = fieldTypes[1];

                serializer.characters("    ");
                serializer.startElementField();
                serializer.characters("\n");
                serializer.characters("      ");
                serializer.characters("\n");
                serializer.characters("      ");
                serializer.startElementName();
                serializer.characters(fieldName);
                serializer.endElementName();
                serializer.characters("\n");
                serializer.characters("      ");
                serializer.startElementType();
                serializer.characters(fieldType);
                serializer.endElementType();
                serializer.characters("\n");

                serializer.characters("      ");
                serializer.startElementDescription();
                serializer.characters("フィールド[" + fieldName + "]です。");
                serializer.endElementDescription();
                serializer.characters("\n");

                serializer.characters("    ");
                serializer.endElementField();
                serializer.characters("\n");
            }

            serializer.characters("  ");
            serializer.endElementBlancovalueobjectList();
            serializer.characters("\n");
            serializer.endElementSheet();
            serializer.characters("\n");
            serializer.endElementWorkbook();
            serializer.endDocument();
            try {
                outStream.flush();
            } catch (IOException e) {
                new SAXException(e);
            }
        } finally {
            if (outStream != null) {
                outStream.close();
            }
        }

        BlancoValueObjectXml2JavaClass xml2javaclass = new BlancoValueObjectXml2JavaClass();
        if (storage.getSetting().getWorkDirectory() == null) {
            throw new IllegalArgumentException(
                    "BlancoDbGenerator: blanco作業用フォルダが未設定(null)です。");
        }
        xml2javaclass.process(fileWorkXml, new File(storage.getSetting()
                .getWorkDirectory()));
    }

    public void generate() throws IOException {
        _generator.generate();
    }
}