/*
 * 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.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.commons.util.BlancoStringUtil;
import blanco.db.conf.BlancoDbSettingDotNet;
import blanco.db.definition.BlancoDbDefinitionDotNet;
import blanco.db.definition.QueryCallerDotNet;
import blanco.db.definition.QueryFieldDotNet;
import blanco.db.definition.QueryInvokerDotNet;
import blanco.db.definition.QueryIteratorDotNet;
import blanco.db.definition.TableFieldDotNet;
import blanco.db.exception.DeadlockExceptionClassDotNet;
import blanco.db.exception.IntegrityConstraintExceptionClassDotNet;
import blanco.db.exception.NoRowFoundExceptionClassDotNet;
import blanco.db.exception.NoRowModifiedExceptionClassDotNet;
import blanco.db.exception.NotSingleRowExceptionClassDotNet;
import blanco.db.exception.TimeoutExceptionClassDotNet;
import blanco.db.exception.TooManyRowsFoundExceptionClassDotNet;
import blanco.db.exception.TooManyRowsModifiedExceptionClassDotNet;
import blanco.db.expander.query.caller.QueryCallerClassDotNet;
import blanco.db.expander.query.invoker.QueryInvokerClassDotNet;
import blanco.db.expander.query.iterator.QueryIteratorClassDotNet;
import blanco.db.util.BlancoDbObjectStorageDotNet;
import blanco.db.util.BlancoDbUtilClassDotNet;
import blanco.ig.expander.IgType;
import blanco.ig.expander.Type;
import blanco.ig.generator.ImplementGeneratorDotNet;
import blanco.valueobject.BlancoValueObjectDotNetXml2CsClass;
import blanco.valueobject.concretesax.BlancoValueObjectDotNetSerializer;

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

    private ImplementGeneratorDotNet _generator = null;

    public BlancoDbGeneratorDotNet(final BlancoDbSettingDotNet setting) {
        storage.setSetting(setting);
    }

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

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

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

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

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

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

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

        String runtimePackage = storage.getSetting().getRootNameSpace();
        if (BlancoStringUtil.null2Blank(
                storage.getSetting().getRuntimePackage()).length() > 0) {
            runtimePackage = storage.getSetting().getRuntimePackage();
        }

        final String packageNameRuntime = runtimePackage + ".util";
        generatorRuntime.addMain(new BlancoDbUtilClassDotNet(storage,
                new IgType(packageNameRuntime,
                        BlancoDbUtilClassDotNet.CLASS_NAME)));

        generatorRuntime.generate();
    }

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

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

        final String packageNameException = runtimePackage + ".exception";
        generatorRuntime.addMain(new IntegrityConstraintExceptionClassDotNet(
                new Type(packageNameException,
                        IntegrityConstraintExceptionClassDotNet.CLASS_NAME)));

        // 2006.06.30 t.iga DeadlockおよびTimeout例外について対応。
        generatorRuntime
                .addMain(new DeadlockExceptionClassDotNet(new Type(
                        packageNameException,
                        DeadlockExceptionClassDotNet.CLASS_NAME)));
        generatorRuntime.addMain(new TimeoutExceptionClassDotNet(new Type(
                packageNameException, TimeoutExceptionClassDotNet.CLASS_NAME)));

        generatorRuntime.addMain(new NoRowFoundExceptionClassDotNet(
                new Type(packageNameException,
                        NoRowFoundExceptionClassDotNet.CLASS_NAME)));
        generatorRuntime.addMain(new NoRowModifiedExceptionClassDotNet(
                new Type(packageNameException,
                        NoRowModifiedExceptionClassDotNet.CLASS_NAME)));
        generatorRuntime.addMain(new NotSingleRowExceptionClassDotNet(new Type(
                packageNameException,
                NotSingleRowExceptionClassDotNet.CLASS_NAME)));
        generatorRuntime.addMain(new TooManyRowsFoundExceptionClassDotNet(
                new Type(packageNameException,
                        TooManyRowsFoundExceptionClassDotNet.CLASS_NAME)));
        generatorRuntime.addMain(new TooManyRowsModifiedExceptionClassDotNet(
                new Type(packageNameException,
                        TooManyRowsModifiedExceptionClassDotNet.CLASS_NAME)));

        generatorRuntime.generate();
    }

    private void createRowObjectClass(final QueryIteratorDotNet 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 TableFieldDotNet) {
                TableFieldDotNet field = (TableFieldDotNet) objField;
                fieldName = field.getName();
                fieldType = field.getJavaType().getFullName();
            } else if (objField instanceof QueryFieldDotNet) {
                QueryFieldDotNet field = (QueryFieldDotNet) objField;
                fieldName = field.getName();
                fieldType = field.getJavaType().getFullName();
            }

            if (fieldType.equals("java.sql.Date")) {
                // java.sql.Dateは System.DateTimeに読み替えます。
                fieldType = "System.DateTime";
            } else if (fieldType.equals("java.io.Reader")) {
                fieldType = "String";
            }

            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));
            BlancoValueObjectDotNetSerializer serializer = new BlancoValueObjectDotNetSerializer(
                    outStream);

            serializer.startDocument();
            serializer.startElementWorkbook();
            serializer.characters("\n");
            serializer.startElementSheet("ValueObject");
            serializer.characters("\n");
            serializer.characters("  ");
            serializer.startElementBlancovalueobjectdotnetCommon();
            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.endElementBlancovalueobjectdotnetCommon();
            serializer.characters("\n");
            serializer.characters("  ");
            serializer.startElementBlancovalueobjectdotnetList();
            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.endElementBlancovalueobjectdotnetList();
            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();
            }
        }

        BlancoValueObjectDotNetXml2CsClass xml2javaclass = new BlancoValueObjectDotNetXml2CsClass();
        xml2javaclass.process(fileWorkXml, new File(storage.getSetting()
                .getWorkDirectory()));
    }

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