/*
 * $Id$
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.struts2.codebehind;

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.UnknownHandler;
import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
import com.opensymphony.xwork2.config.providers.InterceptorBuilder;
import com.opensymphony.xwork2.inject.Inject;

/**
 * Uses code-behind conventions to solve the two unknown problems.  
 */
public class CodebehindUnknownHandler implements UnknownHandler {

    protected String defaultPackageName;
    protected ServletContext servletContext;
    protected Map<String,ResultTypeConfig> resultsByExtension;
    protected String templatePathPrefix;
    protected Configuration configuration;
    protected ObjectFactory objectFactory;
    
    protected static final Log LOG = LogFactory.getLog(CodebehindUnknownHandler.class);

    @Inject
    public CodebehindUnknownHandler(@Inject("struts.codebehind.defaultPackage") String defaultPackage, 
                                    @Inject Configuration configuration) {

        this.configuration = configuration;
        this.defaultPackageName = defaultPackage;
        resultsByExtension = new LinkedHashMap<String,ResultTypeConfig>();
        PackageConfig parentPackage = configuration.getPackageConfig(defaultPackageName);
        Map<String,ResultTypeConfig> results = parentPackage.getAllResultTypeConfigs();
        
        resultsByExtension.put("jsp", results.get("dispatcher"));
        resultsByExtension.put("vm", results.get("velocity"));
        resultsByExtension.put("ftl", results.get("freemarker"));
       
    }                                

    @Inject("struts.codebehind.pathPrefix")
    public void setPathPrefix(String prefix) {
        this.templatePathPrefix=prefix;
    }
    
    @Inject
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
    
    @Inject
    public void setObjectFactory(ObjectFactory objectFactory) {
        this.objectFactory = objectFactory;
    }
    
    public ActionConfig handleUnknownAction(String namespace, String actionName)
            throws XWorkException {
        String pathPrefix = determinePath(templatePathPrefix, namespace);
        ActionConfig actionConfig = null;
        for (String ext : resultsByExtension.keySet()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Trying to locate unknown action template with extension ."+ext+" in directory "+pathPrefix);
            }
            String path = string(pathPrefix, actionName, "." , ext);
            try {
                if (servletContext.getResource(path) != null) {
                    actionConfig = buildActionConfig(path, namespace, actionName, resultsByExtension.get(ext));
                    break;
                }
            } catch (MalformedURLException e) {
                LOG.warn("Unable to parse template path: "+path+", skipping...");
            }
        }
        return actionConfig;
    }

    protected ActionConfig buildActionConfig(String path, String namespace, String actionName, ResultTypeConfig resultTypeConfig) {
        Map<String,ResultConfig> results = new HashMap<String,ResultConfig>();
        HashMap params = new HashMap();
        if (resultTypeConfig.getParams() != null) {
            params.putAll(resultTypeConfig.getParams());
        }
        params.put(resultTypeConfig.getDefaultResultParam(), path);
        
        PackageConfig pkg = configuration.getPackageConfig(defaultPackageName);
        List interceptors = InterceptorBuilder.constructInterceptorReference(pkg, pkg.getFullDefaultInterceptorRef(), 
                Collections.EMPTY_MAP, null, objectFactory); 
        ResultConfig config = new ResultConfig(Action.SUCCESS, resultTypeConfig.getClazz(), params);
        results.put(Action.SUCCESS, config);
        return new ActionConfig("execute", ActionSupport.class.getName(), defaultPackageName, new HashMap(), results, interceptors);
    }

    public Result handleUnknownResult(ActionContext actionContext, String actionName, 
            ActionConfig actionConfig, String resultCode) throws XWorkException {
        
        Result result = null;
        PackageConfig pkg = configuration.getPackageConfig(actionConfig.getPackageName());
        String ns = pkg.getNamespace();
        String pathPrefix = determinePath(templatePathPrefix, ns);
        
        for (String ext : resultsByExtension.keySet()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Trying to locate result with extension ."+ext+" in directory "+pathPrefix);
            }
            String path = string(pathPrefix, actionName, "-", resultCode, "." , ext);
            try {
                if (servletContext.getResource(path) != null) {
                    result = buildResult(path, resultCode, resultsByExtension.get(ext), actionContext);
                    break;
                }
            } catch (MalformedURLException e) {
                LOG.warn("Unable to parse template path: "+path+", skipping...");
            }
            
            path = string(pathPrefix, actionName, "." , ext);
            try {
                if (servletContext.getResource(path) != null) {
                    result = buildResult(path, resultCode, resultsByExtension.get(ext), actionContext);
                    break;
                }
            } catch (MalformedURLException e) {
                LOG.warn("Unable to parse template path: "+path+", skipping...");
            }
        }
        
        return result;
    }
    
    protected Result buildResult(String path, String resultCode, ResultTypeConfig config, ActionContext invocationContext) {
        String resultClass = config.getClazz();

        Map<String,String> params = new LinkedHashMap<String,String>();
        if (config.getParams() != null) {
            params.putAll(config.getParams());
        }
        params.put(config.getDefaultResultParam(), path);

        ResultConfig resultConfig = new ResultConfig(resultCode, resultClass, params);
        try {
            return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
        } catch (Exception e) {
            throw new XWorkException("Unable to build codebehind result", e, resultConfig);
        }
    }

    protected String string(String... parts) {
        StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            sb.append(part);
        }
        return sb.toString();
    }
    
    protected String determinePath(String prefix, String ns) {
        if (ns == null || "/".equals(ns)) {
            ns = "";
        }
        if (ns.length() > 0) {
            if (ns.charAt(0) == '/') {
                ns = ns.substring(1);
            }
            if (ns.charAt(ns.length() - 1) != '/') {
                ns += "/";
            }
        }
        return prefix + ns;
    }

}
