/*****************************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ****************************************************************************************/

package gov.va.med.esr.common.model.cases;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.builder.ToStringBuilder;

import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.lookup.FunctionalGroup;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.lookup.WkfCaseRequestType;
import gov.va.med.esr.common.model.lookup.WkfCaseStatusType;
import gov.va.med.esr.common.model.lookup.WkfCaseType;
import gov.va.med.esr.common.model.lookup.WkfIssueType;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;

/**
 * A business event that requires staff to perform work. Workflow Items may be handled by
 * the system automatically or may become a case that a staff member will work.
 */

public class WorkflowCase extends AbstractKeyedEntity implements Serializable {

   /**
    * An instance of serialVersionUID
    */
   private static final long serialVersionUID = -8421943638044133958L;

   public static final String UNASSIGNED_TO = "Unassigned";

   public static final int ERROR_MESSAGE_LENGTH = 250;

   private FunctionalGroup groupType = null;

   private WkfCaseType caseType = null;

   private WkfIssueType issueType = null;

   private Date dueDate = null;

   private WkfCaseRequestType requestReason = null;

   private Set internalAssignments = null;

   private Set internalComments = null;

   private Set internalStatuses = null;

   private VAFacility requestSource = null;

   private Date requestReceivedDate = null;

   private WorkflowPerson person = null;

   private Set historicalAssignments = null;

   private Set historicalStatuses = null;

   private String errorMessage = null;

   public WorkflowCase() {
      super();
   }

   /**
    * @return Returns the workflowCaseID.
    */
   public BigDecimal getWorkflowCaseID() {
      return (BigDecimal)( ( getEntityKey() == null ) ? null : getEntityKey()
            .getKeyValue() );
   }

   public WkfIssueType getIssueType() {
      return issueType;
   }

   public BigDecimal getPersonID() {
      return (BigDecimal)( ( person == null ) ? null : person.getEntityKey()
            .getKeyValue() );
   }

   public FunctionalGroup getGroupType() {
      return this.groupType;
   }

   public void setGroupType(FunctionalGroup groupType) {
      this.groupType = groupType;
   }

   public void setIssueType(WkfIssueType issueType) {
      this.issueType = issueType;
   }

   /**
    * Get the WkfCaseType attribute
    */
   public WkfCaseType getCaseType() {
      return ( caseType );
   }

   public Date getDueDate() {
      return ( dueDate );
   }

   public WkfCaseRequestType getRequestReason() {
      return ( requestReason );
   }

   public VAFacility getRequestSource() {
      return ( requestSource );
   }

   public Date getRequestReceivedDate() {
      return ( requestReceivedDate );
   }

   /**
    * Get PersonEntityKey attribute
    */
   public PersonEntityKey getPersonEntityKey() {
      return CommonEntityKeyFactory.createPersonIdEntityKey(getPersonID());
   }

   public String getName() {
      return ( person != null ) ? person.getName() : null;
   }

   public String getSsn() {
      return ( person != null ) ? person.getSsn() : null;
   }

   public VPIDEntityKey getVPIDEntityKey() {
      return ( person != null ) ? person.getVPIDEntityKey() : null;
   }

   public Set getComments() {
      return Collections.unmodifiableSet(getInternalComments());
   }

   public void setCaseType(WkfCaseType newWkfCaseType) {
      caseType = newWkfCaseType;
   }

   public void setDueDate(Date newDueDate) {
      dueDate = newDueDate;
   }

   public void setRequestReason(WkfCaseRequestType newRequestReason) {
      requestReason = newRequestReason;
   }

   public void setRequestSource(VAFacility newRequestSource) {
      requestSource = newRequestSource;
   }

   /**
    * Set the RequestReceivedDate attribute
    */
   public void setRequestReceivedDate(Date newRequestReceivedDate) {
      requestReceivedDate = newRequestReceivedDate;
   }

   /**
    * Add an element to the vector of Comments
    */
   public void addComments(Comment comment) {
      getInternalComments().add(comment);
      comment.setWorkflowCase(this);
   }

   /**
    * A utility method to allow determination of whether a workflow item's due date has
    * expired.
    * 
    * @return boolean representing whether the due date has expired
    */
   public boolean isOverdue() {
      return ( dueDate == null ) ? false : dueDate.before(DateUtils.getCurrentDate());
   }

   /**
    * Get Current Status
    */
   public Status getStatus() {

      Set statuses = getInternalStatuses();
      if( statuses.size() > 0 ) {
         return (Status)statuses.iterator().next();
      }
      return null;
   }

   /**
    * Set current status
    * 
    * @param status
    */
   public void setStatus(Status status) {
      getInternalStatuses().clear();

      if( status != null ) {
         status.setWorkflowCase(this);
         getInternalStatuses().add(status);
      }
   }

   /**
    * Return the assignment - expecting a single assignment in the set
    */
   public Assignment getAssignment() {
      Set assignments = getInternalAssignments();
      if( assignments.size() > 0 ) {
         return (Assignment)assignments.iterator().next();
      }
      return null;
   }

   public void setAssignment(Assignment assignment) {
      getInternalAssignments().clear();

      if( assignment != null ) {
         assignment.setWorkflowCase(this);
         getInternalAssignments().add(assignment);
      }
   }

   public WorkflowPerson getPerson() {
      return person;
   }

   /*
    * (non-Javadoc)
    * 
    * @see gov.va.med.fw.model.AbstractKeyedEntity#buildToString(org.apache.commons.lang.builder.ToStringBuilder)
    */
   public void buildToString(ToStringBuilder builder) {
      super.buildToString(builder);

      builder.append("WorkflowCase.workflowCaseID", this.getWorkflowCaseID());
      builder.append("WorkflowCase.WkfCaseType", this.caseType);
      builder.append("WorkflowCase.dueDate", this.dueDate);
      builder.append("WorkflowCase.requestReason", this.requestReason);

      if( this.internalComments != null )
         builder.append("WorkflowCase.comments", this.internalComments.toString());

      if( this.internalStatuses != null )
         builder.append("WorkflowCase.statuses", this.internalStatuses.toString());

      if( this.internalAssignments != null )
         builder.append("WorkflowCase.assignments", this.internalAssignments.toString());
   }

   public void setPerson(WorkflowPerson person) {
      this.person = person;
   }

   public Set getAssignmentHistory() {
      return Collections.unmodifiableSet(getHistoricalAssignments());
   }

   public List getAssignmentHistoryList() {
      List list = new ArrayList(getAssignmentHistory());
      Collections.sort(list);
      // Remove the duplicates
      if( list.size() > 0 ) {
         List uniqueList = new ArrayList();

         // Add only valid assignements - skip the initial null and Unassigned from the
         // list
         int index = 0;
         String assignedTo = null;
         for( int i = 0; i < list.size(); i++ ) {
            index++;
            AssignmentHistory assignmentHistory = (AssignmentHistory)list.get(i);
            assignedTo = assignmentHistory.getAssignedTo();
            if( StringUtils.isEmpty(assignedTo) || UNASSIGNED_TO.equals(assignedTo) ) {
               continue; // process next record
            }
            else {
               uniqueList.add(assignmentHistory);
               break;
            }
         }

         for( int i = index; i < list.size(); i++ ) {
            AssignmentHistory assignmentHistory = (AssignmentHistory)list.get(i);
            String newAssignedTo = assignmentHistory.getAssignedTo();
            // Ignore records with null assignement
            if( StringUtils.isEmpty(newAssignedTo) || UNASSIGNED_TO.equals(newAssignedTo) ) {
               continue; // process next record
            }
            if( assignedTo != null && newAssignedTo != null ) {
               // Ignore duplicate rows
               if( assignedTo.equals(newAssignedTo) ) {
                  continue;
               }
               else {
                  assignedTo = newAssignedTo;
                  uniqueList.add(assignmentHistory);
               }
            }
         }
         // reverse the list to display from latest to old
         return reverse(uniqueList);
      }
      return list;
   }

   public Set getStatusHistory() {
      return Collections.unmodifiableSet(getHistoricalStatuses());
   }

   public List getStatusHistoryList() {
      List list = new ArrayList(getStatusHistory());
      Collections.sort(list);
      // Remove the duplicates
      if( list.size() > 1 ) {
         List uniqueList = new ArrayList();
         uniqueList.add(list.get(0));
         WkfCaseStatusType statusType = ( (StatusHistory)list.get(0) ).getStatusValue();
         for( int i = 1; i < list.size(); i++ ) {
            WkfCaseStatusType newStatusType = ( (StatusHistory)list.get(i) )
                  .getStatusValue();
            if( statusType != null && newStatusType != null ) {
               if( statusType.getCode().equals(newStatusType.getCode()) ) {
                  continue;
               }
               else {
                  statusType = newStatusType;
                  uniqueList.add(list.get(i));
               }
            }
         }
         // reverse the list to display latest to old
         return reverse(uniqueList);
      }
      return reverse(list);
   }

   public List getCommentsHistoryList() {
      List list = new ArrayList(getComments());
      Collections.sort(list);
      // Remove the duplicates
      return reverse(list);
   }

   public boolean isClosed() {
      Status status = getStatus();
      if( status != null
            && WkfCaseStatusType.CLOSED.getCode().equals(
                  status.getStatusValue().getCode()) ) {
         return true;
      }
      return false;
   }

   public boolean isOpen() {
      return isClosed() ? false : true;
   }

   /**
    * @return Returns the errorMessage.
    */
   public String getErrorMessage() {
      return this.errorMessage;
   }

   /**
    * @param errorMessage
    *           The errorMessage to set.
    */
   public void setErrorMessage(String errorMessage) {
      if( errorMessage != null && errorMessage.length() > ERROR_MESSAGE_LENGTH ) {
         this.errorMessage = errorMessage.substring(0, ERROR_MESSAGE_LENGTH);
      }
      else {
         this.errorMessage = errorMessage;
      }
   }

   /**
    * Get the Comments attribute
    */
   private Set getInternalComments() {
      if( internalComments == null )
         internalComments = new HashSet();
      return ( internalComments );
   }

   /**
    * Set the Comments attribute
    */
   private void setInternalComments(Set internalComments) {
      this.internalComments = internalComments;
   }

   /**
    * Get the Statuses
    */
   private Set getInternalStatuses() {
      if( internalStatuses == null )
         internalStatuses = new HashSet();
      return ( internalStatuses );
   }

   /**
    * Set the Statuses attribute
    */
   private void setInternalStatuses(Set newStatuses) {
      internalStatuses = newStatuses;
   }

   /**
    * Get the Assignments
    */
   private Set getInternalAssignments() {
      if( internalAssignments == null )
         internalAssignments = new HashSet();
      return ( internalAssignments );
   }

   /**
    * Set the Assignments attribute
    */
   private void setInternalAssignments(Set internalAssignments) {
      this.internalAssignments = internalAssignments;
   }

   /**
    * Get Assignment History
    * 
    * @return
    */
   private Set getHistoricalAssignments() {
      if( historicalAssignments == null )
         historicalAssignments = new HashSet();
      return ( historicalAssignments );
   }

   /**
    * Get Status History
    * 
    * @return
    */
   private Set getHistoricalStatuses() {
      if( historicalStatuses == null )
         historicalStatuses = new HashSet();
      return ( historicalStatuses );
   }

   private void setHistoricalAssignments(Set historicalAssignments) {
      this.historicalAssignments = historicalAssignments;
   }

   private void setHistoricalStatuses(Set historicalStatuses) {
      this.historicalStatuses = historicalStatuses;
   }

   /**
    * Reverse order the list contents
    * 
    * @param sourceList
    * @return
    */
   private List reverse(List sourceList) {
      if( sourceList != null & sourceList.size() > 1 ) {

         int size = sourceList.size();
         int n = sourceList.size() / 2;
         for( int i = 0; i < n; i++ ) {
            Object temp = sourceList.get(i);
            int last = size - ( i + 1 );
            sourceList.set(i, sourceList.get(last));
            sourceList.set(last, temp);
         }
      }
      return sourceList;
   }

   /**
    * @see gov.va.med.fw.model.AbstractKeyedEntity#finalize()
    */
   protected void finalize() throws Throwable {
      super.finalize();
      this.setHistoricalAssignments( null );
      setInternalComments( null );
      setHistoricalStatuses( null );
      setInternalStatuses( null );
      setInternalAssignments( null );
   }
}
