/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.fw.rule;

// Java classes
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.TimeZone;
import java.util.List;

// Library classes
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;

// Framework classes
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.model.AbstractEntity;

/**
 * A base rule wrapper class that is used as an input to a framework rule
 * service. A framework rule service class insert a rule wrapper as an input
 * parameter to the specific rule flow.
 * 
 * @author DNS   LEV
 * @version 1.0
 */
public abstract class AbstractRuleParameter extends AbstractComponent implements
        RuleParameter {

    private static final long serialVersionUID = -1370346746640850300L;

    /**
     * An instance of a ruleFlowName
     */
    private RuleFlowNameAware ruleFlowNameAware = null;

    /**
     * An instance of ruleParameterName
     */
    private RuleParameterNameAware ruleParameterNameAware = null;

    /**
     * An instance of ruleMethod
     */
    private RuleMethod ruleMethod = null;

    /**
     * An instance of ruleSession
     */
    private RuleSession ruleSession = null;

    /**
     * An instance of ruleData
     */
    private RuleDataAware ruleDataAware = null;

    /**
     * Creates a rule wrapper that encapsulates data to be used during a rule
     * calculation.
     */
    protected AbstractRuleParameter() {
        super();
        this.initialize();
    }

    /**
     * @return
     */
    public AbstractEntity getIncomingData() {
        return ruleDataAware.getIncomingData();
    }

    /**
     * @return
     */
    public AbstractEntity getPristineData() {
        return ruleDataAware.getPristineData();
    }

    /**
     * @return
     */
    public AbstractEntity getResultData() {
        return ruleDataAware.getResultData();
    }

    /**
     * @param incoming
     */
    public void setIncomingData(AbstractEntity incoming) {
        ruleDataAware.setIncomingData(incoming);
    }

    /**
     * @param pristine
     */
    public void setPristineData(AbstractEntity pristine) {
        ruleDataAware.setPristineData(pristine);
    }

    /**
     * @param result
     */
    public void setResultData(AbstractEntity result) {
        ruleDataAware.setResultData(result);
    }

    /**
     * Determines if a date to check is between two other dates (inclusive).
     * 
     * @param dateToCheck
     *            The date to check
     * @param lowerDate
     *            The lower date to check against. If null is passed, no lower
     *            bound will be used.
     * @param upperDate
     *            The upper date to check against. If null is passed, no upper
     *            bound will be used.
     * @return True if the date to check is between the other two dates or false
     *         if not.
     */
    public boolean isDateBetween(Date dateToCheck, Date lowerDate,
            Date upperDate) {
        return ruleMethod.isDateBetween(dateToCheck, lowerDate, upperDate);
    }

    public BigDecimal percentageOf(BigDecimal a, double percentage) {
        return ruleMethod.percentageOf(a, percentage);
    }

    public boolean isLessThan(BigDecimal value, BigDecimal upper){
        return ruleMethod.isLessThan(value, upper);
    }
    /**
     * Checks if two dates are of same day
     * 
     * @return True if a dates are same day. false otherwise
     */
    public boolean isSameDay(Date date1, Date date2) {
        return ruleMethod.isSameDay(date1, date2);
    }

    /**
     * @param value
     * @param lower
     * @return
     */
    public boolean isAfter(Date value, Date lower) {
        return ruleMethod.isAfter(value, lower);
    }

    public boolean isNull(Object obj) {
        return ruleMethod.isNull(obj);
    }

    /**
     * @param value
     * @param upper
     * @return
     */
    public boolean isBefore(Date value, Date upper) {
        return ruleMethod.isBefore(value, upper);
    }

    public BigDecimal add(BigDecimal a, BigDecimal b) {
        return ruleMethod.add(a, b);
    }

    /**
     * @param arg1
     * @param arg2
     * @param field
     * @return
     * @throws RuleException
     */
    public boolean isCalendarEqual(Calendar arg1, Calendar arg2, int field)
            throws RuleException {
        return ruleMethod.isCalendarEqual(arg1, arg2, field);
    }

    /**
     * @param collection
     * @return
     */
    public boolean isEmpty(Collection collection) {
        return ruleMethod.isEmpty(collection);
    }

    /**
     * @param arg1
     * @param arg2
     * @return
     */
    public boolean isEqual(Object arg1, Object arg2) {
        return ruleMethod.isEqual(arg1, arg2);
    }

    /**
     * @param value
     * @param lower
     * @return
     */
    public boolean isGreaterThan(Integer value, int lower) {
        return ruleMethod.isGreaterThan(value, lower);
    }
    
    /**
     * @param value
     * @param lower
     * @return
     */
    public boolean isGreaterThan(BigDecimal value, Integer lower) {
        return ruleMethod.isGreaterThan(value, lower);
    }

    /**
     * @param value
     * @param lower
     * @return
     */
    public boolean isGreaterThan(Integer value, Integer lower) {
        return ruleMethod.isGreaterThan(value, lower);
    }
    
    /**
     * @param value
     * @param lower
     * @return
     */
    public boolean isGreaterThan(Integer value, BigDecimal lower) {
        return ruleMethod.isGreaterThan(value, lower);
    }
    
    /**
     * @param value
     * @param lower
     * @param upper
     * @return
     */
    public boolean isInRange(Integer value, int lower, int upper) {
        return ruleMethod.isInRange(value, lower, upper);
    }

    /**
     * @param value
     * @param lower
     * @param upper
     * @return
     */
    public boolean isInRange(BigDecimal value, double lower, double upper) {
        return ruleMethod.isInRange(value, lower, upper);
    }

    /**
     * @param value
     * @param upper
     * @return
     */
    public boolean isLessThan(Integer value, int upper) {
        return ruleMethod.isLessThan(value, upper);
    }

    /**
     * @param value
     * @param pattern
     * @return
     */
    public boolean isPartOf(String value, String pattern) {
        return ruleMethod.isPartOf(value, pattern);
    }

    /**
     * @param date
     * @return
     */
    public Calendar toCalendar(Date date) {
        return ruleMethod.toCalendar(date);
    }

    /**
     * @param date
     * @param timezone
     * @return
     */
    public Calendar toCalendar(Date date, TimeZone timezone) {
        return ruleMethod.toCalendar(date, timezone);
    }

    /**
     * @param value
     * @return
     */
    public Integer toInteger(Number value) {
        return ruleMethod.toInteger(value);
    }

    /**
     * @return
     */
    public RuleExceptionHandler getExceptionHandler() {
        return ruleSession.getExceptionHandler();
    }

    /**
     * @return
     */
    public RuleState getState() {
        return ruleSession.getState();
    }

    /**
     * @return
     */
    public boolean isContinueOnFail() {
        return ruleSession.isContinueOnFail();
    }

    /**
     * @return
     */
    public boolean isDebugEnabled() {
        return ruleSession.isDebugEnabled();
    }

    /**
     * @param flag
     */
    public void setContinueOnFail(boolean flag) {
        ruleSession.setContinueOnFail(flag);
    }

    /**
     * @param handler
     */
    public void setExceptionHandler(RuleExceptionHandler handler) {
        ruleSession.setExceptionHandler(handler);
    }

    /**
     * @param state
     */
    public void setState(RuleState state) {
        ruleSession.setState(state);
    }

    /**
     * @return Returns the ruleData.
     */
    public RuleDataAware getRuleDataAware() {
        return ruleDataAware;
    }

    /**
     * @param ruleData
     *            The ruleData to set.
     */
    public void setRuleDataAware(RuleDataAware ruleData) {
        this.ruleDataAware = ruleData;
    }

    /**
     * @return Returns the ruleMethod.
     */
    public RuleMethod getRuleMethod() {
        return ruleMethod;
    }

    /**
     * @param ruleMethod
     *            The ruleMethod to set.
     */
    public void setRuleMethod(RuleMethod ruleMethod) {
        this.ruleMethod = ruleMethod;
    }

    /**
     * @return Returns the ruleParameterName.
     */
    public RuleParameterNameAware getRuleParameterNameAware() {
        return ruleParameterNameAware;
    }

    /**
     * @param ruleParameterName
     *            The ruleParameterName to set.
     */
    public void setRuleParameterNameAware(
            RuleParameterNameAware ruleParameterName) {
        this.ruleParameterNameAware = ruleParameterName;
    }

    /**
     * @return Returns the ruleSession.
     */
    public RuleSession getRuleSession() {
        return ruleSession;
    }

    /**
     * @param ruleSession
     *            The ruleSession to set.
     */
    public void setRuleSession(RuleSession ruleSession) {
        this.ruleSession = ruleSession;
    }

    /**
     * @return Returns the ruleFlowNameAware.
     */
    public RuleFlowNameAware getRuleFlowNameAware() {
        return ruleFlowNameAware;
    }

    /**
     * @param ruleFlowNameAware
     *            The ruleFlowNameAware to set.
     */
    public void setRuleFlowNameAware(RuleFlowNameAware ruleFlowNameAware) {
        this.ruleFlowNameAware = ruleFlowNameAware;
    }

    /**
     * @return
     */
    public String getRuleFlowName() {
        return ruleFlowNameAware.getRuleFlowName();
    }

    /**
     * @param name
     */
    public void setRuleFlowName(String name) {
        ruleFlowNameAware.setRuleFlowName(name);
    }

    /**
     * @return
     */
    public String getInputName() {
        String name = ruleParameterNameAware.getInputName();
        return (name == null ? getDefaultInputName() : name);
    }

    /**
     * @return
     */
    public String getOutputName() {
        return ruleParameterNameAware.getOutputName();
    }

    /**
     * @param name
     */
    public void setInputName(String name) {
        ruleParameterNameAware.setInputName(name);
    }

    /**
     * @param name
     */
    public void setOutputName(String name) {
        ruleParameterNameAware.setOutputName(name);
    }

    /**
     * @see gov.va.med.fw.rule.RuleMethod#getCurrentDate()
     */
    public Date getCurrentDate() {
        return this.ruleMethod.getCurrentDate();
    }

    /**
     * @see gov.va.med.fw.rule.RuleMethod#getLoggedInUser()
     */
    public String getLoggedInUser() {
        return this.ruleMethod.getLoggedInUser();
    }

    /**
     * @see gov.va.med.fw.rule.RuleMethod#getSize(java.util.Collection)
     */
    public int getSize(Collection collection) {
        return this.ruleMethod.getSize(collection);
    }

    /**
     * @param arg1
     * @param arg2
     * @return
     */
    public boolean isEqual(BigDecimal arg1, int arg2) {
        return this.ruleMethod.isEqual(arg1, arg2);
    }

    /**
     * @param value
     * @return
     * @throws ParseException
     */
    public Date toDate(String value) throws ParseException {
        return this.ruleMethod.toDate(value);
    }

    /**
     * @param value
     * @param pattern
     * @return
     * @throws ParseException
     */
    public Date toDate(String value, int pattern) throws ParseException {
        return this.ruleMethod.toDate(value, pattern);
    }

    /**
     * @param value
     * @return
     */
    public Integer toInteger(int value) {
        return this.ruleMethod.toInteger(value);
    }

    /**
     * @see gov.va.med.fw.rule.RuleMethod#isGreaterThan(java.math.BigDecimal,
     *      java.math.BigDecimal)
     */
    public boolean isGreaterThan(BigDecimal value, BigDecimal lower) {
        return ruleMethod.isGreaterThan(value, lower);
    }

    /**
     * Initialize rule parameter
     */
    protected void initialize() {

        this.setRuleDataAware(new SimpleRuleDataAware());
        this.setRuleMethod(new SimpleRuleMethod());
        this.setRuleFlowNameAware(new SimpleRuleFlowNameAware());
        this.setRuleParameterNameAware(new SimpleRuleParameterNameAware());
        this.setRuleSession(new SimpleRuleSession());

        // Set a rule state with appropriate rule classes
        RuleState state = this.getRuleSession().getState();
        state.setRuleFlowNameAware(this.getRuleFlowNameAware());
        state.setRuleParameter(this);
    }

    /**
     * @return
     */
    protected String getDefaultInputName() {

        String name = ClassUtils.getShortClassName(getClass());
        List interfaces = ClassUtils.getAllInterfaces(getClass());
        if (interfaces != null && interfaces.size() > 0) {
            name = ClassUtils.getShortClassName((Class) interfaces.get(0));
        }
        return StringUtils.uncapitalize(name);
    }
}