// Package 
package gov.va.med.esr.ui.person.beans;

// Imports
import gov.va.med.fw.util.ObjectUtils;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.esr.ui.person.action.MergeConstants;
import org.apache.commons.lang.Validate;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

/**
 * UI Row Information for the Person Merge screens.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public class PersonMergeRowInfo implements PersonMergeRowInfoMetaData
{
    public static final String MODIFIED_BY = "modifiedBy";
    public static final String MODIFIED_ON = "modifiedOn";

    private PersonMergeRowInfoMeta meta;

    // Primary object value
    private Object primaryObject = null;

    // Deprecated object value
    private Object deprecatedObject = null;

    // The entity key for the primary object
    private String primaryEntityKey = null;

    // The entity key for the deprecated object
    private String deprecatedEntityKey = null;

    // The HTML Input Type for the primary object.
    // This will be null, checkbox or radio.
    private String primaryInputType = null;

    // The HTML Input Type for the deprecated object
    // This will be null, checkbox or radio.
    private String deprecatedInputType = null;

    // The children of this row.  Only groups should contain children.
    private List children = new ArrayList();

    // The count of a group node
    private int count = -1;


    public PersonMergeRowInfo(PersonMergeRowInfoMeta meta)
    {
        Validate.notNull(meta, "PersonMergeRowInfoMeta can't be null.");
        this.meta = meta;
    }

    /**
     * Returns whether the primary input type should be unchecked when the deprecated input
     * type is checked.  The condition for this is when the primary input type exists
     * (i.e. it is not null) and the row is single select.
     *
     * @return True when the primary input type should be unchecked or false if not.
     */
    public boolean getUncheckPrimaryInputType()
    {
        if ((StringUtils.isNotEmpty(primaryInputType)) && (isSingleSelect()))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Returns whether the deprecated input type should be unchecked when the primary input
     * type is checked.  The condition for this is when the deprecated input type exists
     * (i.e. it is not null) and the row is single select.
     *
     * @return True when the deprecated input type should be unchecked or false if not.
     */
    public boolean getUncheckDeprecatedInputType()
    {
        if ((StringUtils.isNotEmpty(deprecatedInputType)) && (isSingleSelect()))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public String getPrimaryFieldKey()
    {
        if (StringUtils.isEmpty(getFieldName()))
        {
            return null;
        }
        return getFieldName() + MergeConstants.UNDERSCORE +
            (StringUtils.isEmpty(primaryEntityKey) ? MergeConstants.PRIMARY : primaryEntityKey);
    }

    public String getDeprecatedFieldKey()
    {
        if (StringUtils.isEmpty(getFieldName()))
        {
            return null;
        }
        return getFieldName() + MergeConstants.UNDERSCORE +
            (StringUtils.isEmpty(deprecatedEntityKey) ? MergeConstants.DEPRECATED :
                deprecatedEntityKey);
    }

    public Object getPrimaryObject()
    {
        return primaryObject;
    }

    public void setPrimaryObject(Object primaryObject)
    {
        this.primaryObject = primaryObject;
    }

    public Object getDeprecatedObject()
    {
        return deprecatedObject;
    }

    public void setDeprecatedObject(Object deprecatedObject)
    {
        this.deprecatedObject = deprecatedObject;
    }

    public String getPrimaryEntityKey()
    {
        return primaryEntityKey;
    }

    public String getPrimaryObjectAsString()
    {
        return primaryObject == null ? null : primaryObject.toString();
    }

    public String getDeprecatedObjectAsString()
    {
        return deprecatedObject == null ? null : deprecatedObject.toString();
    }

    public void setPrimaryEntityKey(String primaryEntityKey)
    {
        this.primaryEntityKey = primaryEntityKey;
    }

    public String getDeprecatedEntityKey()
    {
        return deprecatedEntityKey;
    }

    public void setDeprecatedEntityKey(String deprecatedEntityKey)
    {
        this.deprecatedEntityKey = deprecatedEntityKey;
    }

    public String getFieldStyle()
    {
        return meta.getFieldStyle();
    }

    public String getDataStyle()
    {
        return meta.getDataStyle();
    }

    public String getFieldMessageKey()
    {
        return meta.getFieldMessageKey();
    }

    public String getGroupFieldMessageKey()
    {
        return meta.getGroupFieldMessageKey();
    }

    public String getMergeProperty()
    {
        return meta.getMergeProperty();
    }

    public String getMergeMethodName()
    {
        return meta.getMergeMethodName();
    }

    public boolean isSelectable()
    {
        return meta.isSelectable();
    }

    public boolean isSingleSelect()
    {
        return meta.isSingleSelect();
    }

    public String getFieldName()
    {
        return meta.getFieldName();
    }

    public String getFieldNamePrefix()
    {
        return meta.getFieldNamePrefix();
    }

    public String getFieldProperty()
    {
        return meta.getFieldProperty();
    }

    public String getFieldPropertyHelperMethod()
    {
        return meta.getFieldPropertyHelperMethod();
    }

    public boolean isParentRow()
    {
        return meta.isParentRow();
    }

    public boolean getHighlightDifferences()
    {
        return meta.getHighlightDifferences();
    }

    public boolean getDisplayField()
    {
        return meta.getDisplayField();
    }

    /**
     * Returns whether the primary and deprecated objects are the same or not taking into
     * consideration the highlighDifferences flag.  If the highlightDifferences flag is
     * false, this method will always return true.  If the highlightDifferences flag is
     * true, then the result will be getDataSame.
     *
     * @return True if the objects are the same or false if not.
     */
    public boolean getObjectsSame()
    {
        if (getHighlightDifferences())
        {
            return getDataSame();
        }
        else
        {
            // Consider the objects the same if we shouldn't highlight differences
            return true;
        }
    }

    /**
     * Returns the opposite of getObjectsSame.
     *
     * @return the opposite of getObjectsSame.
     */
    public boolean getObjectsDifferent()
    {
        return !getObjectsSame();
    }

    /**
     * Returns whether the primary and deprecated objects are the same or not.
     *
     * @return True if the objects are the same or false if not.
     */
    public boolean getDataSame()
    {
        return ObjectUtils.equals(primaryObject, deprecatedObject);
    }

    /**
     * Returns the opposite of getDataSame.
     *
     * @return the opposite of getDataSame.
     */
    public boolean getDataDifferent()
    {
        return !getDataSame();
    }

    /**
     * Returns whether the direct selectable children are the same or not.
     * If no children are present, this method will return true.
     * Note that the child properties of modifiedBy or modifiedOn are skipped.
     * Highlighting is not taken into consideration in this method.
     *
     * @return True if the children are the same or false if not.
     */
    public boolean getSelectableChildrenSame()
    {
        boolean childSelectable = false;
        for (Iterator iterator = children.iterator(); iterator.hasNext();)
        {
            PersonMergeRowInfo personMergeRowInfo = (PersonMergeRowInfo)iterator.next();
            if ((StringUtils.isNotEmpty(personMergeRowInfo.getPrimaryInputType())) ||
                (StringUtils.isNotEmpty(personMergeRowInfo.getDeprecatedInputType())))
            {
                //  Note that we found a child that is selectable
                childSelectable = true;

                // Skip this field if it is an audit field.
                if (personMergeRowInfo.isAuditField())
                {
                    continue;
                }

                // If a child isn't the same, return false.
                if (!personMergeRowInfo.getDataSame())
                {
                    return false;
                }
            }
        }

        // If we found at least one child that is selectable and all children are the same,
        // then return true.  Otherwise, return false.
        if (childSelectable)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Returns whether all the recursive children (selectable or not) are the same or not.
     * If no children are present, this method will return true.
     * Note that the child properties of modifiedBy or modifiedOn are skipped.
     * Highlighting is taken into consideration in this method.
     *
     * @return True if the children are the same or false if not.
     */
    public boolean getAllChildrenSame()
    {
        return getAllChildrenSame(children);
    }

    /**
     * The implementation of the getAllChildrenSame method that performs the recursion
     * on a list of children.
     *
     * @param children The list of PersonMergeRowInfo children.
     * @return True if the children are the same or false if not.
     */
    private boolean getAllChildrenSame(List children)
    {
        boolean childrenSame = true;
        for (Iterator iterator = children.iterator(); iterator.hasNext();)
        {
            PersonMergeRowInfo personMergeRowInfo = (PersonMergeRowInfo)iterator.next();
            if (personMergeRowInfo.isParentRow())
            {
                childrenSame = getAllChildrenSame(personMergeRowInfo.children);
                if (!childrenSame)
                {
                    // The children are different so return false
                    return childrenSame;
                }
            }
            else
            {
                // Only process this child if it is not an audit field
                if (!personMergeRowInfo.isAuditField())
                {
                    childrenSame = personMergeRowInfo.getObjectsSame();
                    if (!childrenSame)
                    {
                        // The children are different so return false
                        return childrenSame;
                    }
                }
            }
        }

        // If we get here, all the children are the same so return true.
        return childrenSame;
    }

    /**
     * Returns whether this field is an audit field (i.e. the field property starts with
     * MODIFIED_ON or MODIFIED_BY.  If a field property is empty, this method will return
     * false.
     *
     * @return True if it is an audit property or false if not.
     */
    public boolean isAuditField()
    {
        if (StringUtils.isNotEmpty(getFieldProperty()))
        {
            if ((getFieldProperty().startsWith(MODIFIED_ON)) ||
                (getFieldProperty().startsWith(MODIFIED_BY)))
            {
                return true;
            }
        }
        return false;
    }

    public String getPrimaryInputType()
    {
        return primaryInputType;
    }

    public void setPrimaryInputType(String primaryInputType)
    {
        this.primaryInputType = primaryInputType;
    }

    public String getDeprecatedInputType()
    {
        return deprecatedInputType;
    }

    public void setDeprecatedInputType(String deprecatedInputType)
    {
        this.deprecatedInputType = deprecatedInputType;
    }

    /**
     * Returns whether a selection is needed for an item.  A selection is needed if the item
     * is selectable, it is a single selection, and at least either the primary or deprecated
     * input types are present.  Note that selecting parent information supercedes a child selection.
     *
     * @return True if a selection is needed for this item or false if not.
     */
    public boolean isSelectionNeeded()
    {
        return ((isSelectable()) && (isSingleSelect()) &&
            ((StringUtils.isNotEmpty(primaryInputType)) ||
             (StringUtils.isNotEmpty(deprecatedInputType))));
    }

    /**
     * Returns whether both input types are present.
     *
     * @return True if both input types are present or false if not.
     */
    public boolean areBothInputTypesPresent()
    {
        return ((StringUtils.isNotEmpty(primaryInputType)) &&
                (StringUtils.isNotEmpty(deprecatedInputType)));
    }

    /**
     * Returns whether a selection will be defaulted because no user selection is needed.
     * For example, if both the primary and deprecated items are the same, the user doesn't
     * need to actually make a selection.  We will default the selection for them (i.e. stick
     * with the primary selection).  A selection will be defaulted if the item is selectable,
     * it is a single selection, and both primary and deprecated input types are both not
     * present.
     *
     * @return True if a selection will be defaulted or false if not.
     */
    public boolean isSelectionDefaulted()
    {
        return ((isSelectable()) && (isSingleSelect()) && (StringUtils.isEmpty(primaryInputType)) &&
            (StringUtils.isEmpty(deprecatedInputType)));
    }

    public boolean getChildrenContainGroups()
    {
        for (Iterator iterator = children.iterator(); iterator.hasNext();)
        {
            PersonMergeRowInfo personMergeRowInfo = (PersonMergeRowInfo)iterator.next();
            if (personMergeRowInfo.isParentRow())
            {
                return true;
            }
        }
        return false;
    }

    public void addChild(PersonMergeRowInfo info)
    {
        children.add(info);
    }

    public void removeChild(PersonMergeRowInfo info)
    {
        children.remove(info);
    }

    public List getChildren()
    {
        return Collections.unmodifiableList(children);
    }

    public void addChildren(List children)
    {
        children.addAll(children);
    }

    public void removeAllChildren()
    {
        children.clear();
    }

    public int getCount()
    {
        return count;
    }

    public void setCount(int count)
    {
        this.count = count;
    }
}
