/*
 * blancoCsv Copyright (C) 2005 Tosiki Iga
 * 
 * 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.csv.expand;

import java.io.File;

import blanco.commons.util.BlancoJavaSourceUtil;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.csv.resourcebundle.BlancoCsvResourceBundle;
import blanco.csv.resourcebundle.BlancoCsvWriterResourceBundle;
import blanco.csv.valueobject.BlancoCsvFieldStructure;
import blanco.csv.valueobject.BlancoCsvStructure;
import blanco.ig.expander.ClassExpander;
import blanco.ig.expander.Type;
import blanco.ig.expander.Value;
import blanco.ig.expander.field.FieldExpander;
import blanco.ig.expander.implementor.Statement;
import blanco.ig.expander.method.Constructor;
import blanco.ig.expander.method.MethodExpander;

public class BlancoCsvExpandWriter {
    final BlancoCsvResourceBundle bundle = new BlancoCsvResourceBundle();

    final BlancoCsvWriterResourceBundle writerBundle = new BlancoCsvWriterResourceBundle();

    public void expand(final BlancoCsvStructure processStructure,
            final File directoryTarget) {
        final String className = BlancoNameAdjuster
                .toClassName(processStructure.getFileDefinitionId())
                + writerBundle.getClassNameSuffix();
        final Type typeCsvIOException = new Type(processStructure
                .getRuntimePackage()
                + writerBundle.getAddusingtype01Suffix());
        final ClassExpander classExpander = new ClassExpander(new Type(
                processStructure.getPackageName()
                        + writerBundle.getPackagePrefix(), className)) {
            protected void expandClassStruct() {
                addImport(typeCsvIOException);
                addFileComment(writerBundle.getFilecomment());

                getJavaDoc()
                        .addLine(
                                writerBundle
                                        .getClasscomment01(processStructure
                                                .getFileDefinitionId()
                                                + (processStructure
                                                        .getFileDescription() == null ? ""
                                                        : "/"
                                                                + processStructure
                                                                        .getFileDescription())));
                getJavaDoc().addLine(writerBundle.getClasscomment02());
                if (processStructure.getEncoding() == null) {
                    getJavaDoc()
                            .addLine(writerBundle.getClasscomment03Case01());
                } else {
                    getJavaDoc().addLine(
                            writerBundle
                                    .getClasscomment03Case02(processStructure
                                            .getEncoding()));
                }
                getJavaDoc().addLine(writerBundle.getClasscomment04());

                final FieldExpander field1 = new FieldExpander(new Type(
                        writerBundle.getFieldWriterType()), writerBundle
                        .getFieldWriterName());
                field1.getJavaDoc().addLine(
                        writerBundle.getFieldWriterJavadoc01());
                field1.getJavaDoc().addLine(
                        writerBundle.getFieldWriterJavadoc02());
                addField(field1);

                final FieldExpander field2 = new FieldExpander(new Type(
                        writerBundle.getFieldLineCounterType()), writerBundle
                        .getFieldLineCounterName());
                field2.getJavaDoc().addLine(
                        writerBundle.getFieldLineCounterJavadoc01());
                field2.getJavaDoc().addLine(
                        writerBundle.getFieldLineCounterJavadoc02());
                field2.setDefaultStatement(new Statement(writerBundle
                        .getFieldLineCounterDefault()));
                addField(field2);

                // 「書式」対応
                for (int indexField = 0; indexField < processStructure
                        .getListField().size(); indexField++) {
                    final BlancoCsvFieldStructure field = (BlancoCsvFieldStructure) processStructure
                            .getListField().get(indexField);

                    if (field.getType().equals("java.util.Date")) {
                        final FieldExpander field3 = new FieldExpander(
                                new Type("java.text.SimpleDateFormat"),
                                writerBundle
                                        .getFieldSimpleDateFormatNamePrefix()
                                        + BlancoNameAdjuster.toClassName(field
                                                .getName()));
                        field3
                                .getJavaDoc()
                                .addLine(
                                        writerBundle
                                                .getFieldSimpleDateFormatJavadoc01(field
                                                        .getName()));
                        field3.getJavaDoc().addLine(
                                writerBundle
                                        .getFieldSimpleDateFormatJavadoc02());
                        addField(field3);
                    }
                }

                addMethod(new Constructor(className) {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine(
                                writerBundle.getConstructor01Javadoc01());
                        getJavaDoc().addLine(
                                writerBundle.getConstructor01Javadoc02());
                        getJavaDoc().addParameter("arg",
                                writerBundle.getConstructor01Arg01Javadoc01());
                        addArgument(new Value(new Type(writerBundle
                                .getConstructor01Arg01Type()), "arg"));
                    }

                    // メソッドの実装
                    public void implement() {
                        getData().addLine("fWriter = arg;");

                        // 「書式」対応
                        for (int indexField = 0; indexField < processStructure
                                .getListField().size(); indexField++) {
                            final BlancoCsvFieldStructure field = (BlancoCsvFieldStructure) processStructure
                                    .getListField().get(indexField);

                            if (field.getType().equals("java.util.Date")) {
                                getData()
                                        .addLine(
                                                "fSimpleDateFormat"
                                                        + BlancoNameAdjuster
                                                                .toClassName(field
                                                                        .getName())
                                                        + " = new SimpleDateFormat("
                                                        + (field.getFormat() == null ? "\"yyyy/MM/dd\""
                                                                : "\""
                                                                        + field
                                                                                .getFormat()
                                                                        + "\"")
                                                        + ");");
                            }
                        }
                    }
                });

                if (processStructure.getTitleRow().equals("title with quote")
                        || processStructure.getTitleRow().equals(
                                "title without quote")) {
                    addMethod(new MethodExpander("writeTitle") {
                        public void setupSignature() {
                            getJavaDoc().addLine(
                                    writerBundle.getWritetitleJavadoc01());
                            getJavaDoc().addLine(
                                    writerBundle.getWritetitleJavadoc02());
                            if (processStructure.getTitleRow().equals(
                                    "title with quote")) {
                                getJavaDoc().addLine(
                                        writerBundle.getWritetitleJavadoc03());
                            }
                            getJavaDoc().addException(
                                    new Type(writerBundle
                                            .getWritetitleThrows01Type()),
                                    writerBundle
                                            .getWriterecordThrows01Javadoc());
                            addException(new Type(writerBundle
                                    .getWritetitleThrows01Type()));
                        }

                        public void implement() {
                            for (int indexField = 0; indexField < processStructure
                                    .getListField().size(); indexField++) {
                                final BlancoCsvFieldStructure field = (BlancoCsvFieldStructure) processStructure
                                        .getListField().get(indexField);
                                final String delimiter = (indexField != 0 ? processStructure
                                        .getDelimiter()
                                        : "");
                                final String quote = (processStructure
                                        .getTitleRow().equals(
                                                "title with quote") ? BlancoJavaSourceUtil
                                        .escapeStringAsJavaSource("\"")
                                        : "");
                                getData()
                                        .addLine(
                                                "fWriter.write(\""
                                                        + delimiter
                                                        + quote
                                                        + BlancoJavaSourceUtil
                                                                .escapeStringAsJavaSource(field
                                                                        .getDescription())
                                                        + quote + "\");");
                            }
                            getData().addLine("fWriter.newLine();");
                        }
                    });
                }

                final String recordClassName = processStructure
                        .getPackageName()
                        + ".record."
                        + BlancoNameAdjuster.toClassName(processStructure
                                .getFileDefinitionId()) + "CsvRecord";

                addMethod(new MethodExpander("writeRecord") {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine(
                                writerBundle.getWriterecordJavadoc01());
                        getJavaDoc().addLine(
                                writerBundle.getWriterecordJavadoc02());
                        getJavaDoc().addParameter("record",
                                writerBundle.getWriterecordArg01Javadoc());
                        getJavaDoc().addException(typeCsvIOException,
                                writerBundle.getWriterecordThrows01Javadoc());
                        getJavaDoc().addException(
                                new Type(writerBundle
                                        .getWriterecordThrows02Type()),
                                writerBundle.getWriterecordThrows02Javadoc());
                        addArgument(new Value(new Type(recordClassName),
                                "record"));
                        addException(typeCsvIOException);
                        addException(new Type(writerBundle
                                .getWriterecordThrows02Type()));
                    }

                    // メソッドの実装
                    public void implement() {
                        getData().addLine("String writeToken = null;");
                        for (int indexField = 0; indexField < processStructure
                                .getListField().size(); indexField++) {
                            final BlancoCsvFieldStructure field = (BlancoCsvFieldStructure) processStructure
                                    .getListField().get(indexField);

                            if (indexField != 0) {
                                // デリミタを出力。
                                getData()
                                        .addLine(
                                                "fWriter.write('"
                                                        + processStructure
                                                                .getDelimiter()
                                                        + "');");
                            }

                            getData().addLine("");
                            getData()
                                    .addLine(
                                            writerBundle
                                                    .getWriterecordLine01(
                                                            (field.getNo() == null ? ""
                                                                    : bundle
                                                                            .getFieldNo(field
                                                                                    .getNo())
                                                                            + " "),
                                                            field.getName()
                                                                    + (field
                                                                            .getDescription() == null ? ""
                                                                            : "/"
                                                                                    + field
                                                                                            .getDescription())));

                            if (field.getType().equals("java.lang.String")
                                    || field.getType().equals(
                                            "java.lang.String(quote)")) {
                                if (field.getRequired()) {
                                    // 必須項目。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData()
                                            .addLine(
                                                    "throw new BlancoCsvIOException(\""
                                                            + bundle
                                                                    .getCsvWriterErr004(getLocationString(
                                                                            indexField,
                                                                            field
                                                                                    .getNo(),
                                                                            field
                                                                                    .getName(),
                                                                            field
                                                                                    .getDescription()))
                                                            + "\");");
                                    getData().addLine("}");
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "().length() == 0) {");
                                    getData()
                                            .addLine(
                                                    "throw new BlancoCsvIOException(\""
                                                            + bundle
                                                                    .getCsvWriterErr005(getLocationString(
                                                                            indexField,
                                                                            field
                                                                                    .getNo(),
                                                                            field
                                                                                    .getName(),
                                                                            field
                                                                                    .getDescription()))
                                                            + "\");");
                                    getData().addLine("}");
                                    getData().addLine(
                                            "writeToken = record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "();");
                                } else {
                                    // 任意項目であるので nullは長さ0の文字列へと読み替えます。
                                    getData()
                                            .addLine(
                                                    "// 任意項目で文字列の項目に nullまたは長さ0の文字列が与えられた場合には、いずれも値が入っていないものとみなします。");
                                    getData().addLine("// この場合には長さチェックは行いません。");
                                    getData()
                                            .addLine(
                                                    "writeToken = record.get"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(field
                                                                            .getName())
                                                            + "() == null ? \"\" : record.get"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(field
                                                                            .getName())
                                                            + "();");
                                    // if文が開始した状態のままにします。
                                    getData().addLine(
                                            "if (writeToken.length() > 0) {");
                                }
                            } else if (field.getType().equals("int")
                                    || field.getType().equals("long")) {
                                if (field.getRequired()) {
                                    // 必須項目。
                                    // int, longはプリミティブ型なのでnullチェックは不要。
                                    getData().addLine(
                                            "writeToken = String.valueOf(record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "());");
                                } else {
                                    // 任意項目。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData().addLine("writeToken = \"\";");
                                    getData().addLine("} else {");
                                    // if文が開始した状態のままにします。
                                    getData().addLine(
                                            "writeToken = String.valueOf(record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "());");
                                }
                            } else if (field.getType().equals("java.util.Date")) {
                                addUsingType(new Type("java.io.IOException"));
                                if (field.getRequired()) {
                                    // 必須項目。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData()
                                            .addLine(
                                                    "throw new BlancoCsvIOException(\""
                                                            + bundle
                                                                    .getCsvWriterErr004(getLocationString(
                                                                            indexField,
                                                                            field
                                                                                    .getNo(),
                                                                            field
                                                                                    .getName(),
                                                                            field
                                                                                    .getDescription()))
                                                            + "\");");
                                    getData().addLine("}");
                                    getData().addLine(
                                            "writeToken = fSimpleDateFormat"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + ".format(record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "());");
                                } else {
                                    // 任意項目。
                                    // 任意項目であるので nullは長さ0の文字列へと読み替えます。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData().addLine("writeToken = \"\";");
                                    getData().addLine("} else {");
                                    // if文が開始した状態のままにします。
                                    getData().addLine(
                                            "writeToken = fSimpleDateFormat"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + ".format(record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "());");
                                }
                            } else if (field.getType().equals(
                                    "java.math.BigDecimal")) {
                                // java.math.BigDecimalのインポートは不要。
                                if (field.getRequired()) {
                                    // 必須項目。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData()
                                            .addLine(
                                                    "throw new BlancoCsvIOException(\""
                                                            + bundle
                                                                    .getCsvWriterErr004(getLocationString(
                                                                            indexField,
                                                                            field
                                                                                    .getNo(),
                                                                            field
                                                                                    .getName(),
                                                                            field
                                                                                    .getDescription()))
                                                            + "\");");
                                    getData().addLine("}");
                                    getData().addLine(
                                            "writeToken = record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "().toString();");
                                } else {
                                    // 任意項目であるので nullは長さ0の文字列へと読み替えます。
                                    getData().addLine(
                                            "if (record.get"
                                                    + BlancoNameAdjuster
                                                            .toClassName(field
                                                                    .getName())
                                                    + "() == null) {");
                                    getData().addLine("writeToken = \"\";");
                                    getData().addLine("} else {");
                                    // if文が開始した状態のままにします。
                                    getData()
                                            .addLine(
                                                    "writeToken = record.get"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(field
                                                                            .getName())
                                                            + "() == null ? \"\" : record.get"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(field
                                                                            .getName())
                                                            + "().toString();");
                                }
                            } else {
                                getData().addLine(
                                        bundle.getNotSupportedType(field
                                                .getType()));
                                getData().addLine("writeToken = \"\";");
                            }

                            if (field.getRequired()) {
                                // 必須項目。
                                getData().addLine("if (writeToken == null) {");
                                getData()
                                        .addLine(
                                                "throw new BlancoCsvIOException(\""
                                                        + bundle
                                                                .getCsvWriterErr004(getLocationString(
                                                                        indexField,
                                                                        field
                                                                                .getNo(),
                                                                        field
                                                                                .getName(),
                                                                        field
                                                                                .getDescription()))
                                                        + "\");");
                                getData().addLine("}");
                            }

                            final String encodingString = (processStructure
                                    .getEncoding() == null ? "" : "\""
                                    + processStructure.getEncoding() + "\"");
                            if (field.getMinLength() != null) {
                                getData().addLine(
                                        "if (writeToken.getBytes("
                                                + encodingString
                                                + ").length < "
                                                + field.getMinLength() + ") {");
                                getData()
                                        .addLine(
                                                "throw new BlancoCsvIOException(\""
                                                        + bundle
                                                                .getCsvWriterErr001(
                                                                        getLocationString(
                                                                                indexField,
                                                                                field
                                                                                        .getNo(),
                                                                                field
                                                                                        .getName(),
                                                                                field
                                                                                        .getDescription()),
                                                                        field
                                                                                .getMinLength())
                                                        + "\");");
                                getData().addLine("}");
                            }
                            if (field.getMaxLength() != null) {
                                getData().addLine(
                                        "if (writeToken.getBytes("
                                                + encodingString
                                                + ").length > "
                                                + field.getMaxLength() + ") {");
                                getData()
                                        .addLine(
                                                "throw new BlancoCsvIOException(\""
                                                        + bundle
                                                                .getCsvWriterErr002(
                                                                        getLocationString(
                                                                                indexField,
                                                                                field
                                                                                        .getNo(),
                                                                                field
                                                                                        .getName(),
                                                                                field
                                                                                        .getDescription()),
                                                                        field
                                                                                .getMaxLength())
                                                        + "\");");
                                getData().addLine("}");
                            }

                            if (field.getRequired()) {
                            } else {
                                // 任意項目はカッコが開きっぱなしであるので、ここで閉じます。
                                getData().addLine("}");
                            }

                            if (field.getType().equals("java.lang.String")) {
                                getData().addLine(
                                        "if (writeToken.indexOf(\""
                                                + processStructure
                                                        .getDelimiter()
                                                + "\") >= 0) {");
                                getData()
                                        .addLine(
                                                "throw new BlancoCsvIOException(\""
                                                        + bundle
                                                                .getCsvWriterErr003(
                                                                        getLocationString(
                                                                                indexField,
                                                                                field
                                                                                        .getNo(),
                                                                                field
                                                                                        .getName(),
                                                                                field
                                                                                        .getDescription()),
                                                                        processStructure
                                                                                .getDelimiter())
                                                        + "\");");
                                getData().addLine("}");
                                getData().addLine("fWriter.write(writeToken);");
                            } else if (field.getType().equals(
                                    "java.lang.String(quote)")) {
                                // 型の利用を追加します。
                                addUsingType(new Type(processStructure
                                        .getRuntimePackage()
                                        + ".util.BlancoCsvRuntimeUtil"));

                                getData().addLine(
                                        writerBundle.getWriterecordLine02());
                                getData()
                                        .addLine(
                                                "fWriter.write(BlancoCsvRuntimeUtil.getTokenWithQuote(writeToken));");
                            } else {
                                getData().addLine("fWriter.write(writeToken);");
                            }
                        }
                        getData().addLine("");
                        getData().addLine(writerBundle.getWriterecordLine03());
                        getData().addLine("fWriter.newLine();");
                        getData().addLine("fLineCounter++;");
                    }
                });

                addMethod(new MethodExpander("getLineCounter") {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine("現在処理中の行カウンタを返します。");
                        getJavaDoc().addReturn("現在処理中の行カウンタ。");
                        setReturnType(new Type("long"));
                    }

                    // メソッドの実装
                    public void implement() {
                        getData().addLine("return fLineCounter;");
                    }
                });

                addMethod(new MethodExpander("flush") {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine(writerBundle.getFlushJavadoc01());
                        getJavaDoc().addLine(writerBundle.getFlushJavadoc02());
                        getJavaDoc().addException(
                                new Type(writerBundle.getFlushThrows01Type()),
                                writerBundle.getFlushThrows01Javadoc());
                        addException(new Type(writerBundle
                                .getFlushThrows01Type()));
                    }

                    // メソッドの実装
                    public void implement() {
                        getData().addLine("fWriter.flush();");
                    }
                });

                addMethod(new MethodExpander("close") {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine(writerBundle.getCloseJavadoc01());
                        getJavaDoc().addLine(writerBundle.getCloseJavadoc02());
                        getJavaDoc().addException(
                                new Type(writerBundle.getCloseThrows01Type()),
                                writerBundle.getCloseThrows01Javadoc());
                        addException(new Type(writerBundle
                                .getCloseThrows01Type()));
                    }

                    // メソッドの実装
                    public void implement() {
                        getData().addLine("fWriter.close();");
                    }
                });
            }
        };

        ClassExpander.generateJavaSource(classExpander, directoryTarget);
    }

    private String getLocationString(int indexField, String fieldNo,
            String fieldName, String fieldDescription) {
        return writerBundle.getGetlocationstringLine01("" + (indexField + 1),
                (fieldNo == null ? "" : bundle.getFieldNo(fieldNo) + " "),
                fieldName
                        + (fieldDescription == null ? "" : "/"
                                + fieldDescription));
    }
}
