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

import java.io.File;
import java.io.IOException;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgClass;
import blanco.cg.valueobject.BlancoCgField;
import blanco.cg.valueobject.BlancoCgMethod;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoJavaSourceUtil;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.valueobjectdotnet.resourcebundle.BlancoValueObjectDotNetResourceBundle;
import blanco.valueobjectdotnet.util.BlancoValueObjectDotNetUtil;
import blanco.valueobjectdotnet.valueobject.BlancoValueObjectDotNetClassStructure;
import blanco.valueobjectdotnet.valueobject.BlancoValueObjectDotNetFieldStructure;

/**
 * blancoValueObjectの主たるクラス。
 * 
 * blancoValueObjectを表現するXMLファイルから Javaソースコードを自動生成します。
 * 
 * @author IGA Tosiki
 */
public class BlancoValueObjectDotNetXml2SourceFile {

    /**
     * blancoValueObjectのリソースバンドルオブジェクト。
     */
    private final BlancoValueObjectDotNetResourceBundle bundle = new BlancoValueObjectDotNetResourceBundle();

    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

    /**
     * 内部的に利用するblancoCg用ソースファイル情報。
     */
    private BlancoCgSourceFile fCgSourceFile = null;

    /**
     * 内部的に利用するblancoCg用クラス情報。
     */
    private BlancoCgClass fCgClass = null;

    /**
     * フィールド名やメソッド名の名前変形を行うかどうか。
     */
    private boolean fNameAdjust = true;

    /**
     * 自動生成するソースファイルの文字エンコーディング。
     */
    private String fEncoding = null;

    public void setEncoding(final String argEncoding) {
        fEncoding = argEncoding;
    }

    /**
     * ValueObjectを表現するXMLファイルから Javaソースコードを自動生成します。
     * 
     * @param metaXmlSourceFile
     *            ValueObjectに関するメタ情報が含まれているXMLファイル
     * @param directoryTarget
     *            ソースコード生成先ディレクトリ
     * @throws IOException
     *             入出力例外が発生した場合
     */
    public void process(final File metaXmlSourceFile, final File directoryTarget)
            throws IOException {

        final BlancoValueObjectDotNetClassStructure[] structures = new BlancoValueObjectDotNetXmlParser()
                .parse(metaXmlSourceFile);
        for (int index = 0; index < structures.length; index++) {
            // 得られた情報からJavaソースコードを生成します。
            structure2Source(structures[index], directoryTarget);
        }
    }

    /**
     * 与えられたクラス情報をもとにJavaソースコードを自動生成します。
     * 
     * @param classInfo
     *            クラス情報
     * @param directoryTarget
     *            Javaソースコードの出力先ディレクトリ
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public void structure2Source(
            final BlancoValueObjectDotNetClassStructure argClassStructure,
            final File argDirectoryTarget) throws IOException {
        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(argDirectoryTarget
                .getAbsolutePath()
                + "/main");

        // BlancoCgObjectFactoryクラスのインスタンスを取得します。
        fCgFactory = BlancoCgObjectFactory.getInstance();

        fCgSourceFile = fCgFactory.createSourceFile(argClassStructure
                .getPackage(), null);
        fCgSourceFile.setEncoding(fEncoding);

        // ファイルコメントを設定。
        if (argClassStructure.getFilecomment() != null) {
            fCgSourceFile.getLangDoc().getDescriptionList().add(
                    argClassStructure.getFilecomment());
        }

        // クラスを作成します。
        fCgClass = fCgFactory.createClass(argClassStructure.getName(), "");
        fCgSourceFile.getClassList().add(fCgClass);

        // クラスのアクセスを設定。
        // fCgClass.setAccess(argClassStructure.getAccess());
        // 抽象クラスかどうか。
        // fCgClass.setAbstract(argClassStructure.getAbstract());

        // バリューオブジェクトは直列化可能で生成します。
        fCgClass.getAnnotationList().add("SerializableAttribute");

        // デフォルトで System名前空間は導入します。
        fCgSourceFile.getImportList().add("System.String");

        if (argClassStructure.getDescription() != null) {
            final String[] lines = BlancoValueObjectDotNetUtil
                    .escapeStringAsCsDocWithNewLine(argClassStructure
                            .getDescription());
            for (int index = 0; index < lines.length; index++) {
                fCgClass.getLangDoc().getDescriptionList().add(lines[index]);
            }
        }

        for (String line : argClassStructure.getDescriptionList()) {
            fCgClass.getLangDoc().getDescriptionList().add(line);
        }

        for (int indexField = 0; indexField < argClassStructure.getFieldList()
                .size(); indexField++) {
            final BlancoValueObjectDotNetFieldStructure field = (BlancoValueObjectDotNetFieldStructure) argClassStructure
                    .getFieldList().get(indexField);

            if (field.getName() == null) {
                throw new IllegalArgumentException(bundle
                        .getXml2csclassErr003(argClassStructure.getName()));
            }
            if (field.getType() == null) {
                throw new IllegalArgumentException(bundle.getXml2csclassErr004(
                        argClassStructure.getName(), field.getName()));
            }

            if (field.getType().equals("String")) {
                // あまりにもよくある間違いなので、停止するように仕様変更しました。
                throw new IllegalArgumentException(bundle.getXml2csclassErr006(
                        argClassStructure.getName(), field.getName()));
            }

            final String fieldNameAdjustered = (getNameAdjust() == false ? field
                    .getName()
                    : BlancoNameAdjuster.toClassName(field.getName()));

            final BlancoCgField field1 = fCgFactory.createField("f"
                    + fieldNameAdjustered, field.getType(), bundle
                    .getXml2csclassFieldName(field.getName()));
            fCgClass.getFieldList().add(field1);

            field1.getLangDoc().getDescriptionList().add(
                    bundle.getXml2csclassFieldType(field.getType()));
            if (field.getDefault() != null) {
                field1.getLangDoc().getDescriptionList().add(
                        bundle.getXml2csclassFieldDefault(field.getDefault()));
                if ("System.String".equals(field.getType())
                        || "string".equals(field.getType())) {
                    field1.setDefault("\""
                            + BlancoJavaSourceUtil
                                    .escapeStringAsJavaSource(field
                                            .getDefault()) + "\"");
                } else if (field.getType().equals("bool")
                        || field.getType().equals("short")
                        || field.getType().equals("int")
                        || field.getType().equals("long")) {
                    field1.setDefault(field.getDefault());
                } else if (field.getType().equals("System.Boolean")
                        || field.getType().equals("System.Int32")
                        || field.getType().equals("System.Int64")) {
                    field1.setDefault(field.getDefault());
                } else if (field.getType().equals("System.Int16")) {
                    field1.setDefault(field.getDefault());
                } else if (field.getType().equals("System.Decimal")) {
                    field1
                            .setDefault("new Decimal(" + field.getDefault()
                                    + ")");
                } else if (field.getType().equals("decimal")) {
                    field1
                            .setDefault("new decimal(" + field.getDefault()
                                    + ")");
                } else if (field.getType().equals(
                        "System.Collections.ArrayList")) {
                    // ArrayListの場合には、与えられた文字をそのまま採用します。
                    fCgSourceFile.getImportList().add(
                            "System.Collections.ArrayList");
                    field1.setDefault(field.getDefault());
                } else {
                    throw new IllegalArgumentException(bundle
                            .getXml2csclassErr005(argClassStructure.getName(),
                                    field.getName(), field.getDefault(), field
                                            .getType()));
                }
            }
            if (field.getDescription() != null) {
                final String[] lines = BlancoValueObjectDotNetUtil
                        .escapeStringAsCsDocWithNewLine(field.getDescription());
                for (int index = 0; index < lines.length; index++) {
                    field1.getLangDoc().getDescriptionList().add(lines[index]);
                }
            }

            {
                final BlancoCgMethod cgMethod = fCgFactory.createMethod("Set"
                        + fieldNameAdjustered, bundle
                        .getXml2csclassSetJavadoc01(field.getName()));
                fCgClass.getMethodList().add(cgMethod);

                cgMethod.getLangDoc().getDescriptionList().add(
                        bundle.getXml2csclassSetJavadoc02(field.getType()));
                if (field.getDescription() != null) {
                    final String[] lines = BlancoValueObjectDotNetUtil
                            .escapeStringAsCsDocWithNewLine(field
                                    .getDescription());
                    for (int index = 0; index < lines.length; index++) {
                        cgMethod.getLangDoc().getDescriptionList().add(
                                lines[index]);
                    }
                }
                cgMethod.getParameterList().add(
                        fCgFactory.createParameter("arg" + fieldNameAdjustered,
                                field.getType(), bundle
                                        .getXml2csclassSetArgJavadoc(field
                                                .getName())));

                // メソッドの実装
                cgMethod.getLineList().add(
                        "f" + fieldNameAdjustered + " = " + "arg"
                                + fieldNameAdjustered + ";");
            }

            {
                final BlancoCgMethod cgMethod = fCgFactory.createMethod("Get"
                        + fieldNameAdjustered, bundle
                        .getXml2csclassGetJavadoc01(field.getName()));
                fCgClass.getMethodList().add(cgMethod);

                // メソッドのシグニチャ指定
                cgMethod.getLangDoc().getDescriptionList().add(
                        bundle.getXml2csclassGetJavadoc02(field.getType()));
                if (field.getDefault() != null) {
                    cgMethod.getLangDoc().getDescriptionList().add(
                            bundle.getXml2csclassGetArgJavadoc(field
                                    .getDefault()));
                }
                if (field.getDescription() != null) {
                    final String[] lines = BlancoValueObjectDotNetUtil
                            .escapeStringAsCsDocWithNewLine(field
                                    .getDescription());
                    for (int index = 0; index < lines.length; index++) {
                        cgMethod.getLangDoc().getDescriptionList().add(
                                lines[index]);
                    }
                }

                cgMethod
                        .setReturn(fCgFactory.createReturn(field.getType(),
                                bundle.getXml2csclassGetReturnJavadoc(field
                                        .getName())));
                // メソッドの実装
                cgMethod.getLineList().add(
                        "return f" + fieldNameAdjustered + ";");
            }
        }

        // 収集された情報を元に実際のソースコードを自動生成。
        BlancoCgTransformerFactory.getCsSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }

    /**
     * フィールド名やメソッド名について名前変形を行うかどうかを設定します。
     * 
     * @param isNameAdjust
     *            名前変形を行うかどうか。
     */
    public void setNameAdjust(final boolean isNameAdjust) {
        fNameAdjust = isNameAdjust;
    }

    /**
     * フィールド名やメソッド名について名前変形を行うかどうかを取得します。
     * 
     * @return 名前変形を行うかどうか。
     */
    public boolean getNameAdjust() {
        return fNameAdjust;
    }
}
