package com.heckmansoft.surjey.controller.action;

import java.io.IOException;
import java.lang.reflect.Constructor;

import java.util.Properties;
import java.util.Enumeration;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import org.apache.log4j.Logger;

/**
 * Constructs action instances using action.properties file.
 *
 * Based upon the "Factory" design pattern.
 */
public class ActionFactory {
    private static final String ACTIONS_FILE = "/WEB-INF/actions.properties";

    Properties actionProperties;
    Logger log = Logger.getLogger(ActionFactory.class);

    /**
     * Contructor.
     *
     * Based on Factory pattern.
     *
     * @param config used to find action.properties file.
     */
    private ActionFactory(ServletConfig config)
        throws ServletException
    {
        actionProperties = new Properties();

        String actionName = "";
        String actionClassName = "";

        // Load action.properties and init action factory
        try {
            actionProperties.load(
                config.getServletContext().getResourceAsStream(ACTIONS_FILE)
            );

            // Verify that a default action is defined
            String defaultActionName = actionProperties.getProperty("defaultAction");
            actionClassName = actionProperties.getProperty(defaultActionName);
            if (defaultActionName == null || actionClassName == null) {
                throw new ServletException(
                    "No default action defined in actions.properties file."
                );
            }

            // Validate action properties to ensure that each class exists
            Enumeration enum = actionProperties.propertyNames();
            while (enum.hasMoreElements()) {
                actionName = (String) enum.nextElement();

                // Skip the "default action" entry
                if (actionName.equals("defaultAction")) continue;

                // Lookup action class name
                actionClassName = actionProperties.getProperty(actionName);

                if (actionClassName == null) {
                    throw new ServletException(
                        "No action class defined for action: " + actionName +
                        " in actions.properties file."
                    );
                }

                // Get Class object for class -- throws exception if not found
                Class.forName(actionClassName);
            }

        } catch (IOException e) {
            throw new ServletException("File actions.properties not found.", e);

        } catch (ClassNotFoundException e) {
            throw new ServletException(
                "Invalid class name (" + actionClassName + ") " +
                "for action " + actionName + " in actions.properties file", e
            );

        }
    }

    /**
     * Create an action factory for the given config.
     *
     * @param config used to find action.properties file.
     */
    public static ActionFactory newInstance(ServletConfig config)
        throws ServletException
    {
        return new ActionFactory(config);
    }

    /**
     * Constructs the appropriate action object, given an action name.
     *
     * @param actionName Name of action to instantiate.
     * @return an instance of a subclass of SurjeyAction.
     */
    public Action newAction(String actionName)
        throws ServletException
    {
        log.debug("newAction("+actionName+")");

        // Lookup name of action class
        String actionClassName = actionProperties.getProperty(actionName);

        // Get default action if no action class name found
        if (actionClassName == null) {
            String defaultActionName = actionProperties.getProperty("defaultAction");
            actionClassName = actionProperties.getProperty(defaultActionName);
        }

        try {
            // Get the Class object for the action class
            Class actionClass = Class.forName(actionClassName);

            // Create the constructor for a single param of type Object
            Class[] params = { Object.class };
            Constructor constructor = actionClass.getConstructor(params);

            // Create the action object using the constructor,
            // passing 'this' for the argument
            Object[] args = { this };

            // Create the object instance
            return (Action) constructor.newInstance(args);

        } catch (Exception e) {
            log.debug(
                "Problem constructing action object " + actionClassName +
                " for action " + actionName + ".",
                e
            );
            throw new ServletException(
                "Problem constructing action object " + actionClassName +
                " for action " + actionName + ".",
                e
            );
        }
    }
}
