/*
 * $Id: Command.java 220 2007-07-16 10:32:15Z sugimotokenichi $
 * Copyright (C) 2006 SUGIMOTO Ken-ichi
 * 作成日: 2006/03/02
 */
package feat2.impl;

import java.util.ArrayList;
import java.util.Map;

import feat2.Action;
import feat2.ApplicationException;
import feat2.CommandContext;
import feat2.FileUploadException;
import feat2.Interceptor;
import feat2.Processor;
import feat2.PropertyAccessException;
import feat2.ValidationError;
import feat2.config.ActionConfigBase;
import feat2.config.BeanConfig;
import feat2.config.CommandConfig;
import feat2.config.FetchConfig;
import feat2.config.InterceptConfig;
import feat2.config.OutputConfig;
import feat2.config.type.Scope;

/**
 * コマンド。
 * @author SUGIMOTO Ken-ichi
 */
public class Command {

    //private static Log log = LogFactory.getLog(Command.class);

    private CommandConfig config;


    public Command(CommandConfig config) {
        this.config = config;
    }


    /**
     * コマンドを実行する。
     * @param ctx
     * @return レスポンス名。nullを返すこともある
     * @throws PropertyAccessException
     * @throws ApplicationException アプリケーションのコンポーネントで例外がスローされたとき
     * @throws FileUploadException アップロードされたファイルの処理で問題が起きたとき
     */
    public String execute(CommandContext ctx) throws PropertyAccessException, ApplicationException, FileUploadException {

        ActionConfigBase[] actionConfs = config.getActions();
        String ret = null;

        // インターセプタ

        Interceptor[] interceptors = selectInterceptors();

        // コマンド実行前のインターセプタ実行

        for (int i = 0; i < interceptors.length; i++) {
            interceptors[i].before(this, ctx);
        }

        // 各アクションの実行

        for(int i=0; i<actionConfs.length; i++) {

            ret = processAction(actionConfs[i], ctx);

            if ( ret != null )
                return ret;

        }

        // 出力

        ret = processOutput(ctx);

        // コマンド実行後のインターセプタ実行

        for (int i = 0; i < interceptors.length; i++) {
            interceptors[i].after(this, ctx);
        }

        return ret;

    }


    private String processAction(ActionConfigBase actionConf, CommandContext ctx) throws
        PropertyAccessException, FileUploadException, ApplicationException {

        String actionName = actionConf.getName().getValue();
        Object result;

        // 入力値をまとめる

        Map sourceValues = Processor.processInputs(actionConf, ctx);

        // キャッシュの指定があればキャッシュを調べる

        long ttl = 0L;
        ActionCacheKey key = new ActionCacheKey(ctx.getFeatureName(), ctx.getCommandName(), actionName, sourceValues);

        if ( actionConf instanceof FetchConfig ) {
            ttl = ((FetchConfig)actionConf).getCache();
        }

        if ( ttl > 0L ) {
            result = ctx.getCacheObject(key);
            if ( result != null ) {
                ctx.setAttribute(actionName, result);
                return null;
            }
        }

        Action action = actionConf.getActionInstance();

        Object bean = action;
        if ( action instanceof BeanConfig.BeanAction ) {
            bean = ((BeanConfig.BeanAction)action).getBean(ctx);
        }

        // 値の検証

        if ( !Processor.processValidations(bean, sourceValues, actionConf, ctx) )
            return actionConf.getErrorResponse().getValue();

        // アクションのプロパティ値をセット

        Processor.processInjection(bean, sourceValues);

        // エラーがなければ実行
        if ( ctx.getFeatErrors().getErrors(ValidationError.class).length == 0 ) {

            // アクションの実行

            try {
                result = action.execute(ctx);
            }
            catch (Exception ex) {
                throw new ApplicationException(ex);
            }

            ctx.setAttribute(actionName, result);
            if ( ttl > 0L )
                ctx.putCacheObject(key, result, ttl);

        }

        return null;

    }


    /**
     * このコマンドで実行されるインターセプタのインスタンスを作る。
     * @return
     */
    private Interceptor[] selectInterceptors() {

        ArrayList interceptors = new ArrayList();

        InterceptConfig[] interceptConfs = config.getFeatureConfig().getInterceptConfigs();
        for (int i = 0; i < interceptConfs.length; i++) {

            String[] patterns = interceptConfs[i].getPatterns();
            for (int j = 0; j < patterns.length; j++) {

                // パターンのひつとがこのコマンドにマッチしたら
                // インターセプタのインスタンスを作る

                if ( config.getName().getValue().matches(patterns[j]) ) {
                    Interceptor interceptor = interceptConfs[i].getInterceptorInstance();
                    interceptors.add(interceptor);
                    break;
                }

            }

        }

        return (Interceptor[])interceptors.toArray(new Interceptor[interceptors.size()]);

    }


    private String processOutput(CommandContext ctx) {

        // output名の指定がないときは"default"というoutputを指定したことになる

        String name = ctx.getOutputName();
        if ( name == null ) {
            name = "default";
        }

        OutputConfig outputConf = config.getOutputConfig(name);
        if ( outputConf == null ) {
            // TODO エラーを出力
            return null;
        }
        String responseName = outputConf.getResponse().getValue();

        // ローカルスコープのオブジェクトを出力先のスコープにコピー

        OutputConfig.Mapping[] outputMappings = outputConf.getOutputMapping();
        for (int i = 0; i < outputMappings.length; i++) {

            Object local = ctx.getAttribute(outputMappings[i].getLocalName());

            Scope scope = outputMappings[i].getScope();
            if ( scope == null ) {
                scope = outputConf.getScope();
                if ( scope == null )
                    scope = new Scope("request");
            }

            ctx.setAttribute(outputMappings[i].getOutputName(), local, scope);

        }

        return responseName;
    }


    // プロパティ -------------------------------------------------------------

    public CommandConfig getConfig() {
        return config;
    }


    // 内部クラス -------------------------------------------------------------

    private class ActionCacheKey {

        private Map params;
        private String actionName;
        private String featureName;
        private String commandName;

        ActionCacheKey(String featureName, String commandName, String actionName, Map params) {

            this.featureName = featureName;
            this.commandName = commandName;
            this.actionName = actionName;
            this.params = params;

        }

        public int hashCode() {

            return featureName.hashCode() ^ commandName.hashCode() ^ actionName.hashCode() ^ params.hashCode();

        }

        public boolean equals(Object o) {

            ActionCacheKey k = (ActionCacheKey)o;
            return featureName.equals(k.featureName) && commandName.equals(k.commandName) &&
                    actionName.equals(k.actionName) && params.equals(k.params);

        }


    }



}
