package com.softghana.pettycash.modules.actions;

/* Java stuff */
import java.util.ArrayList;
import java.util.Iterator;

/* Velocity stuff */
import org.apache.velocity.context.Context;

/* Turbine stuff */
import org.apache.turbine.util.Log;
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.actions.VelocityAction;

/* Intake stuff */
import org.apache.turbine.services.intake.IntakeTool;
import org.apache.turbine.services.intake.model.*;

/**
 * Inherit from this class to provide basic validation capabilities
 * for data input / editing actions. Supports simple success / failure
 * target screens and branches to these screens depending on results
 * of the validation process. However, if you wish to implement more
 * fancy control flow, you can simply ignore the functionality.
 *
 * @author <a href="mailto:caught_one@yahoo.com">Guido Sohne</a>
 */

public abstract class ValidatingAction extends VelocityAction
{
    /** the context of this action */
    protected Context context;
    
    /** run time environment for this action */
    protected RunData data;

    /** screen template for invalid data */
    private String failureScreen;

    /** screen template for valid data */
    private String successScreen;

    /** status message for invalid data */
    private String failureMessage;

    /** status message for valid data */
    private String successMessage;

    /** interface to intake tool */
    public IntakeTool intakeTool;

    /** always branch to success screen */
    boolean unconditional_branch = false;

    /** always use success status message */
    boolean unconditional_status = false;

    /** either branch to success or to failure screen */
    boolean conditional_branch   = false;

    /** either use success or use failure status message */
    boolean conditional_status   = false;

    /** are all groups valid ? */
    boolean is_all_valid = false;


    /**
     * Initializes the validation action
     * @param runData 
     * @param context
     */
    public void init(RunData runData, Context context)
    {
        this.context = context;
        data = runData;

        // should we derive this string from TR.properties ?        
        // or will the intake tool always be called intake ?
        intakeTool = (IntakeTool) context.get("intake");                                                 }

    /**
     * Set screen to proceed to if validation fails
     */
    private void setFailureScreen(String newFailureScreen)
    {
        failureScreen = newFailureScreen;
    }

    /**
     * Get screen to proceed to if validation fails
     */
    private String getFailureScreen()
    {
        return failureScreen;
    }

    /** 
     * Set screen to proceed to if validation succeeds
     */
    private void setSuccessScreen(String newSuccessScreen)
    {
        successScreen = newSuccessScreen;
    }

    /** 
     * Get screen to proceed to if validation succeeds
     */
    private String getSuccessScreen()
    {
        return successScreen;
    }

    /**
     * Set message to display if validation fails
     */
    private void setFailureMessage(String newFailureMessage)
    {
        failureMessage = newFailureMessage;
    }

    /**
     * Get message to display if validation fails
     */
    private String getFailureMessage()
    {
        return failureMessage;
    }

    /**
     * Set message to display if validation succeeds
     */
    private void setSuccessMessage(String newSuccessMessage)
    {
        successMessage = newSuccessMessage;
    }

    /**
     * Get message to display if validation succeeds
     */
    private String getSuccessMessage()
    {
        return successMessage;
    }

    /**
     * Sets messages that make available status of validation
     * to screens that are reached after the action event 
     * returns from processing the event that was triggered
     */
    public void setStatusMessages(String newFailureMessage, 
                                  String newSuccessMessage)
    {
        conditional_status = true;

        setSuccessMessage(newSuccessMessage);
        setFailureMessage(newFailureMessage);
    }

    /**
     * Set message that makes available the status of validation
     * to the screen that is reached after the action event
     * returns from processing the event that was triggered.
     * The same message will be used whether or not the input
     * data was valid.
     */
    public void setStatusMessage(String newStatusMessage)
    {
        unconditional_status = true;

        setSuccessMessage(newStatusMessage);
    }

    /**
     * Rudimentary control flow for validation success / failure
     * Branches to appropriate screen depending on validation result
     */
    public void setTargetScreens(String newSuccessScreen, String newFailureScreen)
    {
        conditional_branch = true;

        setSuccessScreen(newSuccessScreen);
        setFailureScreen(newFailureScreen);
    }

    /**
     * Use this when you don't need to branch for success / failure
     */
    public void setTargetScreen(String newTargetScreen)
    {
        unconditional_branch = true;

        setSuccessScreen(newTargetScreen);
    }


    /**
     * Initialize internal state per action event for
     * other validation methods to use, dispatch the
     * action event and finally branch to success or
     * failure templates if these have been defined.
     */
    public void executeEvents(RunData runData, Context context)
        throws Exception
    {
        /* looks like a */

        /* read */
        init(runData, context); // initialize any required internal 
                                // state from RunData and Context

        /* read */
        setDefaultTargetScreens(); // get branch targets from template 
                                   // if they exist

        /* eval */
        super.executeEvents(data, context); // dispatch action events 
                                            // to appropriate handler

        /* eval */
        whichTargetScreen(); // branch if necessary. target 
                             // depends on validation status

        /* print */
        whatStatusMessage(); // provide status messages

        /* can loop in caller and cycle until you get the right output
         * (statusMessage) so you can retry your validation in caller
         * for example, numbers with $ , . etc such as $0.02 cents can
         * be recognized by the caller as 0.02 and try to correct or
         * otherwise transform input
         */
    }

    /**
     * Check to see if all the data presented is indeed
     * valid. 
     *
     * @returns a <code>boolean</code> value
     */
    public boolean isAllValid()
        throws Exception
    {
        if (intakeTool != null && intakeTool.isAllValid())
            is_all_valid = true;
        else
            is_all_valid = false;

        return is_all_valid;
    }

    /**
     * Sets properties of object to contents of the Intake
     * group. Object must be of subtype of group
     * That is, for Department group, object
     * must be a Department object!
     *
     * @return the <code>Object</code> that was passed
     * as an argument
     */ 
    public Object retrieve(String groupName, Object obj)
        throws Exception
    {
        Group group = intakeTool.get(groupName, IntakeTool.DEFAULT_KEY);

        if(group != null && obj != null)
            group.setProperties(obj);

        return obj;
    }

    /** 
     * Convenience method to set the next screen
     * after validation.
     */
    public void redo(String screen)
    {
        setTemplate(data, screen);
    }

    /** 
     * Convenience method to set the output 
     * message after validation.
     */
    public void status(String message)
    {
        data.setMessage(message);
    }

    /**
     * Places the contents of an object into the
     * Intake group named <code>groupName</code>.
     * Useful prior to returning to a template
     * for further editing after validation.
     */
    public void populate(String groupName, Object obj)
        throws Exception
    {
        Group group = intakeTool.get(groupName, IntakeTool.DEFAULT_KEY);

        if(group != null && obj != null)
            group.getProperties(obj);
    }

    /**
     * Enable access to intake tool
     */
    public IntakeTool getIntakeTool()
    {
        return intakeTool;
    }

    /** 
     * If there is a nextTemplate parameter in the form triggering
     * this action event, this method sets the success screen to
     * the value of the nextTemplate paramter and sets the failure
     * screen to the value that was passed to $link.setPage()
     */
    public void setDefaultTargetScreens()
        throws Exception
    {
        String success = data.getParameters().getString("nextTemplate");
        String failure = data.getScreenTemplate();

        setTargetScreens(success, failure);
    }

    /**
     * Determines which target to branch to depending on whether 
     * input data is valid or not.
     */
    public String whichTargetScreen()
        throws Exception
    {
        String screen = null;

        if(isAllValid() || unconditional_branch)
        {
            if(successScreen != null)
            { 
                redo(successScreen); 
                screen = failureScreen;
        } 
        else
        {
            if(!isAllValid() && conditional_branch)
            {
                if(failureScreen != null)
                {
                    redo(failureScreen);
                    screen = failureScreen;
                }
            }
        }

        return screen;
    }

    /**
     * Determines what status message to set depending on whether
     * input data is valid or not.
     */
    public String whatStatusMessage()
        throws Exception
    {        
        String message = null;

        if(isAllValid() || unconditional_status)
        {
            if(successMessage != null)
            {
                status(successMessage);
                message = successMessage;
            }
        }
        else
        {
            if (!isAllValid() && conditional_status)
            {
                if(failureMessage != null)
                {
                    status(failureMessage);
                    message = failureMessage;
                }
            }
        }

        return screen;
    }
}

