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

import java.io.File;
import java.util.List;

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.commons.util.BlancoNameUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.db.common.valueobject.BlancoDbSqlInfoStructure;
import blanco.jsf.task.valueobject.BlancoJsfProcessInput;
import blanco.jsf.valueobject.BlancoJsfProcessBeanItemStructure;
import blanco.jsf.valueobject.BlancoJsfProcessStructure;
import blanco.jsf.valueobject.BlancoJsfStructure;

/**
 * 処理(blancoJsf)のためのプロキシ・クラス。
 */
class BlancoJsfExpandLogicProxy {
    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

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

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

    private BlancoJsfProcessInput fInput = null;

    /**
     * 収集された情報を元に、ソースコードを自動生成します。
     * 
     * @param argProcessStructureList
     *            メタファイルから収集できた処理構造データ。
     * @param argRuntimePackage
     *            ランタイムパッケージ。nullおよび長さ0の文字列の場合は定義書ごとにランタイムクラスを生成。
     * @param argTargetLang
     *            出力対象プログラミング言語。
     * @param argDirectoryTarget
     *            ソースコードの出力先フォルダ。
     */
    public void expandSourceFile(final BlancoJsfStructure structure,
            final List<BlancoDbSqlInfoStructure> sqlInfoList,
            final BlancoJsfProcessInput input) {
        fInput = input;

        expandSourceFileInternal(structure, sqlInfoList, input);
    }

    public void expandSourceFileInternal(final BlancoJsfStructure structure,
            final List<BlancoDbSqlInfoStructure> sqlInfoList,
            final BlancoJsfProcessInput input) {

        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(new File(input.getTargetdirjee())
                .getAbsolutePath()
                + "/main");

        fCgFactory = BlancoCgObjectFactory.getInstance();
        fCgSourceFile = fCgFactory.createSourceFile(structure.getPackage()
                + ".proxy", null);
        fCgSourceFile.setEncoding(input.getEncoding());
        fCgClass = fCgFactory.createClass(getClassName(structure),
                "JSF の Managed Bean と実際の処理とをつなぐプロキシ・クラス。");
        fCgSourceFile.getClassList().add(fCgClass);

        {
            fCgClass.getLangDoc().getDescriptionList().add(
                    "<p>faces-config.xml には以下のように登録します。</p>");
            fCgClass.getLangDoc().getDescriptionList().add("<pre>");
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("<managed-bean>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("\t<managed-bean-name>"
                                    + BlancoNameAdjuster.toClassName(structure
                                            .getName()) + structure.getSuffix()
                                    + "</managed-bean-name>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("\t<managed-bean-class>"
                                    + structure.getPackage() + ".proxy."
                                    + getClassName(structure)
                                    + "</managed-bean-class>"));
            fCgClass
                    .getLangDoc()
                    .getDescriptionList()
                    .add(
                            BlancoJavaSourceUtil
                                    .escapeStringAsJavaDoc("<managed-bean-scope>request</managed-bean-scope>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("</managed-bean>"));
            fCgClass.getLangDoc().getDescriptionList().add("</pre>");
        }

        if (BlancoStringUtil.null2Blank(structure.getBean()).length() > 0) {
            fCgClass.getExtendClassList().add(
                    fCgFactory.createType(structure.getBean()));
            fCgClass.getAnnotationList().add("SuppressWarnings(\"serial\")");
        }

        boolean isOutcomeExist = false;
        for (BlancoJsfProcessStructure processStructure : structure
                .getProcessList()) {

            final List<String> sqlClassNameList = BlancoJsfXml2SourceFile
                    .getSqlClassNameList(processStructure, sqlInfoList);

            expandMethodExecute(structure, processStructure, sqlClassNameList);

            if (processStructure.getForward()) {
                isOutcomeExist = true;
            }
        }
        if (isOutcomeExist) {
            final BlancoCgField field = fCgFactory.createField("fOutcome",
                    "java.lang.String", "ページの処理結果。");
            field.setDefault("\"error\"");
            fCgClass.getFieldList().add(field);

            expandMethodOutcome();
        }

        BlancoCgTransformerFactory.getJavaSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }

    /**
     * execute メソッドを展開します。
     * 
     * @param processStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandMethodExecute(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure,
            final List<String> sqlClassNameList) {
        final BlancoCgMethod method = fCgFactory.createMethod(processStructure
                .getMethod(), "クラスをインスタンス化して処理を実行する際のエントリポイントです。");
        fCgClass.getMethodList().add(method);
        method.setFinal(true);

        method.getParameterList().add(
                fCgFactory.createParameter("event",
                        "javax.faces.event.ActionEvent", "イベント。"));

        if (processStructure.getBeanList().size() > 0) {
            fCgSourceFile.getImportList().add("javax.el.ELContext");
            fCgSourceFile.getImportList().add("javax.el.ELResolver");
            fCgSourceFile.getImportList().add(
                    "javax.faces.context.FacesContext");

            method
                    .getLineList()
                    .add(
                            "final FacesContext ctx = FacesContext.getCurrentInstance();");
            method.getLineList().add(
                    "final ELContext elc = ctx.getELContext();");
            method
                    .getLineList()
                    .add(
                            "final ELResolver rslvr = ctx.getApplication().getELResolver();");
            method.getLineList().add("");
        }

        for (BlancoJsfProcessBeanItemStructure bean : processStructure
                .getBeanList()) {
            final String classname = BlancoNameUtil.trimJavaPackage(bean
                    .getName());
            method.getLineList().add(
                    "// session から Managed Bean ["
                            + BlancoNameAdjuster.toLowerCaseTitle(classname)
                            + "] を取得します。");
            fCgSourceFile.getImportList().add(bean.getName());
            method.getLineList().add(
                    "final " + BlancoNameUtil.trimJavaPackage(bean.getName())
                            + " arg" + classname + " = ("
                            + BlancoNameUtil.trimJavaPackage(bean.getName())
                            + ") rslvr.getValue(elc, null, \""
                            + BlancoNameAdjuster.toLowerCaseTitle(classname)
                            + "\");");
        }
        method.getLineList().add("");

        if (processStructure.getSqlList().size() > 0) {
            addUtilImport(structure, processStructure);
            fCgSourceFile.getImportList().add("java.sql.Connection");
            method
                    .getLineList()
                    .add(
                            "final Connection conn = BlancoJsfProxyUtil.getConnection();");
            method.getLineList().add("boolean isLogicSuccess = false;");
        }

        for (String sqlClassName : sqlClassNameList) {
            fCgSourceFile.getImportList().add(sqlClassName);
            method.getLineList().add(
                    "final " + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + " arg"
                            + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + " = new "
                            + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + "(conn);");
        }
        method.getLineList().add("");

        String className = BlancoNameAdjuster.toClassName(processStructure
                .getName())
                + BlancoNameAdjuster.toClassName(processStructure.getMethod())
                + structure.getSuffix();
        fCgSourceFile.getImportList().add(
                structure.getPackage() + "." + className);
        method.getLineList().add(
                "// 以下のコードでコンパイル・エラーが発生する場合には、クラス [" + structure.getPackage()
                        + "." + className + "Impl] を新規に作成してください。");
        method.getLineList().add(
                "final " + className + " logic = new " + structure.getPackage()
                        + "." + className + "Impl();");

        {
            final StringBuffer buf = new StringBuffer();
            boolean isFirst = true;
            if (BlancoStringUtil.null2Blank(structure.getBean()).length() > 0) {
                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("this");
            }
            for (BlancoJsfProcessBeanItemStructure bean : processStructure
                    .getBeanList()) {
                final String classname = BlancoNameUtil.trimJavaPackage(bean
                        .getName());
                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("arg" + classname);
            }
            for (String sql : sqlClassNameList) {
                final String classname = BlancoNameUtil.trimJavaPackage(sql);
                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("arg" + classname);
            }
            if (processStructure.getSqlList().size() > 0) {
                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("conn");
            }

            method.getLineList().add("");
            method.getLineList().add("try {");
            {
                String line = "logic.execute(" + buf.toString() + ");";
                if (processStructure.getForward()) {
                    line = "final String outcome = " + line;
                }
                method.getLineList().add(line);

                if (processStructure.getSqlList().size() > 0) {
                    method.getLineList().add("");
                }

                if (processStructure.getSqlList().size() > 0) {
                    method.getLineList().add("");
                    method.getLineList().add("conn.commit();");
                    method.getLineList().add("isLogicSuccess = true;");
                }

                if (processStructure.getForward()) {
                    method.getLineList().add("fOutcome = outcome;");
                }

                // commit や outcome 処理のあとに close。
                for (String sqlClassName : sqlClassNameList) {
                    method.getLineList().add(
                            "arg"
                                    + BlancoNameUtil
                                            .trimJavaPackage(sqlClassName)
                                    + ".close();");
                }
            }

            method.getLineList().add("} catch (IllegalArgumentException ex) {");
            method.getLineList().add("ex.printStackTrace();");
            method.getLineList().add("} catch (IOException ex) {");
            method.getLineList().add("ex.printStackTrace();");
            if (processStructure.getSqlList().size() > 0) {
                fCgSourceFile.getImportList().add("java.sql.SQLException");
                method.getLineList().add("} catch (SQLException ex) {");
                method.getLineList().add("ex.printStackTrace();");
            }
            fCgSourceFile.getImportList().add("java.io.IOException");
            method.getLineList().add("} catch (Exception ex) {");
            method.getLineList().add("ex.printStackTrace();");
            method.getLineList().add("}");

            if (processStructure.getSqlList().size() > 0) {
                method.getLineList().add("");
                method.getLineList().add("if (isLogicSuccess == false) {");
                method.getLineList().add("try {");
                method.getLineList().add("conn.rollback();");
                method.getLineList().add("} catch (Exception ex) {");
                method.getLineList().add("ex.printStackTrace();");
                method.getLineList().add("}");
                method.getLineList().add("}");
                method.getLineList().add("// コネクションの開放。");
                method.getLineList().add(
                        "BlancoJsfProxyUtil.releaseConnection(conn);");
            }
        }
    }

    private void expandMethodOutcome() {
        final BlancoCgMethod method = fCgFactory.createMethod("outcome",
                "実行結果を取得。");
        fCgClass.getMethodList().add(method);
        method.setFinal(true);
        method.setReturn(fCgFactory.createReturn("java.lang.String", "次画面情報。"));
        method.getLineList().add("return fOutcome;");
    }

    private String getClassName(final BlancoJsfStructure argStructure) {
        return BlancoNameAdjuster.toClassName(argStructure.getName())
                + argStructure.getSuffix() + "Proxy";
    }

    /**
     * DB の getConnection が必要な場合に利用。
     * 
     * @param processStructure
     */
    private void addUtilImport(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure) {
        String runtimePackage = structure.getPackage();
        if (BlancoStringUtil.null2Blank(fInput.getRuntimepackage()).length() > 0) {
            runtimePackage = fInput.getRuntimepackage();
        }

        fCgSourceFile.getImportList().add(
                runtimePackage + ".BlancoJsfProxyUtil");
    }
}
