/*
 * 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.views.java;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.struts2.StrutsException;
import org.apache.struts2.components.template.BaseTemplateEngine;
import org.apache.struts2.components.template.Template;
import org.apache.struts2.components.template.TemplateEngine;
import org.apache.struts2.components.template.TemplateEngineManager;
import org.apache.struts2.components.template.TemplateRenderingContext;
import org.apache.struts2.views.java.simple.SimpleTheme;

import java.util.HashMap;
import java.util.StringTokenizer;

/**
 * Template engine that renders tags using java implementations
 */
public class JavaTemplateEngine extends BaseTemplateEngine {

    private static final Logger LOG = LogManager.getLogger(JavaTemplateEngine.class);

    // The struts template engine manager
    protected TemplateEngineManager templateEngineManager;

    // The struts default template type. If struts ever changes this will need updating.
    private String defaultTemplateType = "ftl";

    @Inject
    public void setTemplateEngineManager(TemplateEngineManager mgr) {
        this.templateEngineManager = mgr;
    }

    private Themes themes = new Themes() {
        {
            add(new SimpleTheme());
        }
    };

    @Override
    protected String getSuffix() {
        return "java";
    }

    public void renderTemplate(TemplateRenderingContext templateContext) throws Exception {
        Template t = templateContext.getTemplate();
        Theme theme = themes.get(t.getTheme());
        if (theme == null) {
            // Theme not supported, so do what struts would have done if we were not here.
            LOG.debug("Theme not found [{}] trying default template engine using template type [{}]", t.getTheme(), defaultTemplateType);
            final TemplateEngine engine = templateEngineManager.getTemplateEngine(templateContext.getTemplate(), defaultTemplateType);

            if (engine == null) {
                // May be the default template has changed?
                throw new ConfigurationException("Unable to find a TemplateEngine for template type '" + defaultTemplateType
                        + "' whilst trying to render template " + templateContext.getTemplate());
            } else {
                try {
                    // Retry render
                    engine.renderTemplate(templateContext);
                } catch (Exception e) {
                    // Give up and throw a new StrutsException(e);
                    throw new StrutsException("Cannot render tag [" + t.getName() + "] because theme ["
                            + t.getTheme() + "] was not found.", e);
                }
            }
        } else {
            // Render our template
            theme.renderTag(t.getName(), templateContext);
        }
    }

    private static class Themes {
        private HashMap<String, Theme> themes = new HashMap<String, Theme>();

        public void add(Theme theme) {
            themes.put(theme.getName(), theme);
        }

        public Theme get(String name) {
            return themes.get(name);
        }
    }

    /**
     * Allows for providing custom theme classes (implementations of the org.apache.struts2.views.java.Theme) interface
     * for custom rendering of tags using the javatemplates engine
     *
     * @param themeClasses a comma delimited list of custom theme class names
     */
    @Inject(value = "struts.javatemplates.customThemes", required = false)
    public void setThemeClasses(String themeClasses) {
        StringTokenizer customThemes = new StringTokenizer(themeClasses, ",");
        while (customThemes.hasMoreTokens()) {
            String themeClass = customThemes.nextToken().trim();
            try {
                LOG.info("Registering custom theme [{}] to javatemplates engine", themeClass);
                ObjectFactory factory = ActionContext.getContext().getContainer().getInstance(ObjectFactory.class);
                Theme theme = (Theme) factory.buildBean(themeClass, new HashMap<String, Object>());
                themes.add(theme);
            } catch (ClassCastException cce) {
                LOG.error("Invalid java them class [{}]. Class does not implement 'org.apache.struts2.views.java.Theme' interface", themeClass, cce);
            } catch (ClassNotFoundException cnf) {
                LOG.error("Invalid java theme class [{}]. Class not found!", themeClass, cnf);
            } catch (Exception e) {
                LOG.error("Could not find messages file [{}].properties. Skipping!", themeClass, e);
            }
        }
    }

    /**
     * Allows for providing an alternative default struts theme. Will default to "ftl" otherwise.
     *
     * @param defaultTemplateTheme the struts default theme
     */
    @Inject(value = "struts.javatemplates.defaultTemplateType", required = false)
    public void setDefaultTemplateType(String defaultTemplateTheme) {
        // Make sure we don't set ourself as default for race condition
        if (defaultTemplateTheme != null && !defaultTemplateTheme.equalsIgnoreCase(getSuffix())) {
            this.defaultTemplateType = defaultTemplateTheme.toLowerCase();
        } else {
            LOG.error("Invalid struts.javatemplates.defaultTemplateType value. Cannot be [{}]!", getSuffix());
        }
    }

}
