/*
 * 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<BlancoJsfStructure> listJsfStructure,
            final List<BlancoDbSqlInfoStructure> sqlInfoList,
            final BlancoJsfProcessInput input) {
        fInput = input;

        expandSourceFileInternal(structure, listJsfStructure, sqlInfoList,
                input);
    }

    public void expandSourceFileInternal(final BlancoJsfStructure structure,
            final List<BlancoJsfStructure> listJsfStructure,
            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\")");
        }

        expandMethodGetLoad(structure);

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

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

            expandMethodExecute(structure, processStructure, listJsfStructure,
                    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);
    }

    private void expandMethodGetLoad(final BlancoJsfStructure structure) {
        boolean isInitializeExist = false;
        boolean isViewExist = false;
        for (BlancoJsfProcessStructure processStructure : structure
                .getProcessList()) {
            if ("initialize".equals(processStructure.getType())) {
                isInitializeExist = true;
            } else if ("view".equals(processStructure.getType())) {
                isViewExist = true;
            }
        }
        if (isInitializeExist || isViewExist) {
            if (isInitializeExist) {
                final BlancoCgField field = fCgFactory.createField(
                        "fIsInitialized", "boolean", "初期化されたかどうか。");
                field.setDefault("false");
                fCgClass.getFieldList().add(field);
            }

            final BlancoCgMethod method = fCgFactory.createMethod("getLoad",
                    "画面ロード時。");
            method.setReturn(fCgFactory.createReturn("java.lang.String",
                    "処理内容にかかわらず、何も戻しません。"));
            fCgClass.getMethodList().add(method);

            if (isInitializeExist) {
                method.getLineList().add("if (fIsInitialized == false) {");
                method.getLineList().add("initialize(null);");
                method.getLineList().add("fIsInitialized = true;");
                method.getLineList().add("}");
            }
            if (isViewExist) {
                method.getLineList().add("view(null);");
            }
            method.getLineList().add("");
            method.getLineList().add("return \"\";");
        }
    }

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

        method.getParameterList().add(
                fCgFactory.createParameter("actionEvent",
                        "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()) {
            // Managed Bean 名の調整。
            // 定義書上の Bean名には、クラス名ではなく Managed Bean が入っている場合があります。

            String managedBeanName = null;
            String managedBeanClassName = null;

            // まずは管理Bean名が入っているものと仮定。
            final BlancoJsfStructure jsfStructure = getJsfStructureByManagedBeanName(
                    bean.getName(), listJsfStructure);
            if (jsfStructure == null) {
                // blancoJsf 定義には見つかりませんでした。
                // この場合は、クラスそのままです。
                managedBeanName = BlancoNameAdjuster
                        .toLowerCaseTitle(BlancoNameAdjuster
                                .toClassName(BlancoNameUtil
                                        .trimJavaPackage(bean.getName())));
                managedBeanClassName = bean.getName();
            } else {
                // blancoJsf 定義に見つかりました。
                managedBeanName = BlancoNameAdjuster
                        .toLowerCaseTitle(BlancoNameAdjuster
                                .toClassName(jsfStructure.getName()))
                        + jsfStructure.getSuffix();
                managedBeanClassName = jsfStructure.getBean();
            }

            method.getLineList().add(
                    "// session から Managed Bean [" + managedBeanName
                            + "] を取得します。");
            fCgSourceFile.getImportList().add(managedBeanClassName);

            method.getLineList().add(
                    "final "
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + " arg"
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + " = ("
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + ") rslvr.getValue(elc, null, \""
                            + managedBeanName + "\");");
        }
        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()) {
                String managedBeanClassName = null;

                // まずは管理Bean名が入っているものと仮定。
                final BlancoJsfStructure jsfStructure = getJsfStructureByManagedBeanName(
                        bean.getName(), listJsfStructure);
                if (jsfStructure == null) {
                    // blancoJsf 定義には見つかりませんでした。
                    // この場合は、クラスそのままです。
                    managedBeanClassName = bean.getName();
                } else {
                    // blancoJsf 定義に見つかりました。
                    managedBeanClassName = jsfStructure.getBean();
                }

                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("arg"
                        + BlancoNameUtil.trimJavaPackage(managedBeanClassName));
            }
            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");
            }
            if (processStructure.getActionEvent()) {
                if (isFirst == false) {
                    buf.append(", ");
                } else {
                    isFirst = false;
                }
                buf.append("actionEvent");
            }

            method.getLineList().add("");

            method.getLineList().add("try {");

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

            {
                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();");
                }
            }

            addUtilImport(structure, processStructure);
            method.getLineList().add("} catch (IllegalArgumentException ex) {");
            method
                    .getLineList()
                    .add(
                            "BlancoJsfProxyUtil.logIllegalArgumentException(\"画面ID.メソッド\", ex);");
            method.getLineList().add("} catch (IOException ex) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logIOException(\"画面ID.メソッド\", ex);");
            if (processStructure.getSqlList().size() > 0) {
                fCgSourceFile.getImportList().add("java.sql.SQLException");
                method.getLineList().add("} catch (SQLException ex) {");
                method
                        .getLineList()
                        .add(
                                "BlancoJsfProxyUtil.logSQLException(\"画面ID.メソッド\", ex);");
            }
            fCgSourceFile.getImportList().add("java.io.IOException");
            method.getLineList().add("} catch (Exception ex) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logException(\"画面ID.メソッド\", ex);");
            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");
    }

    /**
     * JSF構造リストから、与えられた管理Bean名をもとにクラス名を取得します。
     * 
     * @param managedBeanName
     * @param listJsfStructure
     * @return 見つからなければ null。
     */
    static BlancoJsfStructure getJsfStructureByManagedBeanName(
            final String managedBeanName,
            final List<BlancoJsfStructure> listJsfStructure) {
        for (BlancoJsfStructure structureSearch : listJsfStructure) {
            if (managedBeanName.equals(BlancoNameAdjuster
                    .toLowerCaseTitle(BlancoNameAdjuster
                            .toClassName(structureSearch.getName()))
                    + structureSearch.getSuffix())) {
                // blancoJsf 定義書から導出できる範囲に、Managed Bean 登録が見つかりました。
                return structureSearch;
            }
        }
        return null;
    }
}
