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

// Java classes
import java.util.Date;
import java.util.Set;

import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.rule.RuleDataAware;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.rule.RuleException;

import gov.va.med.esr.common.model.lookup.AssociationType;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.rule.AssociationInput;
import gov.va.med.esr.common.rule.data.AssociationInputData;
import gov.va.med.esr.common.rule.service.MatchRuleService;
import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.service.external.demographics.DemographicsChangeEvent;


/**
 * This class expects that an Association will be provided in the incoming position.
 * 
 * @author Carlos Ruiz
 * @version 1.0
 */
public class AssociationInputParameter extends BaseParameter implements AssociationInput {

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

   /**
    * An instance of association
    */
   private Association association = null;

   // An instance of association on file
   private Association associationOnFile = null;

   private String fName = null;

   private String lName = null;

   private String organizationName = null;

   private String associationTypeCode = null;

   private String relationship = null;

   private Date lastUpdatedDate = null;

   private Date onFileLastUpdatedDate = null;

   private Name representativeName = null;

   private Date deactivationDate = null;
   
   private Date dateOfBirth = null;
    
   /**
    * A default constructor
    */
   public AssociationInputParameter() {
      super();
   }

   public EntityKey getEntityKey() {
      if( this.getExternalSystemsInputData() != null ) {
         // This is for external systems context
         DemographicsChangeEvent demographicsChangeEvent = this
               .getExternalSystemsInputData().getDemographicsChangeEvent();
         gov.va.med.esr.service.external.person.EntityKey key = ( demographicsChangeEvent != null ) ? demographicsChangeEvent
               .getEntityKey()
               : null;
         return key != null ? CommonEntityKeyFactory.createAssociationEntityKey(key
               .getKey()) : null;
      }

      return ( this.getPristineAssociation() != null ) ? this.getPristineAssociation()
            .getEntityKey() : null;
   }

   public String getRelationshipType() {
      if( this.relationship == null ) {
         this.relationship = ( this.getAssociation() != null ) ? this.getAssociation()
               .getRelationship() : null;
      }
      return StringUtils.isNotEmpty(relationship) ? relationship : null;
   }

    public Date getVeteranDateOfBirth() {
        if ( dateOfBirth == null ) {
            this.dateOfBirth = this.getDateOfBirth(this.getIncomingPerson());
        }
        return this.dateOfBirth;
    }

    public Date getDeactivationDate() {
        if( this.deactivationDate == null ) {
            this.deactivationDate = ( this.getAssociation() != null ) ? this.getAssociation()
                  .getDeactivationDate() : null;
         }
        return this.deactivationDate;
    }
    
    public Date getpristineDeactivationDate() {
        return ( this.getPristineAssociation() != null ) ? this.getPristineAssociation()
              .getDeactivationDate() : null;
     }
    
   public String getPristineRelationshipType() {
      String pRelationship = ( this.getPristineAssociation() != null ) ? this
            .getPristineAssociation().getRelationship() : null;
      return StringUtils.isNotEmpty(pRelationship) ? pRelationship : null;
   }

   public String getAssociationType() {
      if( associationTypeCode == null ) {
         AssociationType type = ( this.getAssociation() != null ) ? this.getAssociation()
               .getType() : null;
         this.associationTypeCode = type != null ? type.getCode() : null;
      }
      return StringUtils.isNotEmpty(associationTypeCode) ? associationTypeCode : null;
   }

   public String getOrganizationName() {
      if( organizationName == null ) {
         organizationName = ( this.getAssociation() != null ) ? this.getAssociation()
               .getOrganizationName() : null;
      }
      return StringUtils.isNotEmpty(organizationName) ? organizationName : null;
   }

   public String getPristineOrganizationName() {
      String pOrgName = ( this.getPristineAssociation() != null ) ? this
            .getPristineAssociation().getOrganizationName() : null;
      return StringUtils.isNotEmpty(pOrgName) ? pOrgName : null;
   }

   public boolean isNameProvided() {
      Name name = this.getName();
      return ( name != null && ( StringUtils.isNotEmpty(name.getFamilyName()) || StringUtils
            .isNotEmpty(name.getGivenName()) ) ) ? true : false;
   }

   public Name getName() {
      if( representativeName == null ) {
         this.representativeName = ( this.getAssociation() != null ) ? this
               .getAssociation().getRepresentativeName() : null;
      }
      return this.representativeName;
   }

   public Name getPristineName() {
      return ( this.getPristineAssociation() != null ) ? this.getPristineAssociation()
            .getRepresentativeName() : null;
   }

   // / Setter/getter methods for association address.

   public String getAddressLineOne() {
       Address addr = this.getAddress();
      return (addr != null)?addr.getLine1():null;
   }

   public String getPristineAddressLineOne() {
       Address addr = this.getPristineAddress();
      return (addr!=null)?addr.getLine1():null;
   }

   public String getAddressLineTwo() {
       Address addr = this.getAddress();
      return (addr != null)?addr.getLine2():null;
   }

   public String getPristineAddressLineTwo() {
       Address addr = this.getPristineAddress();
      return (addr!=null)?addr.getLine2():null;
   }

   public String getAddressLineThree() {
       Address addr = this.getAddress();
      return (addr != null)?addr.getLine3():null;
   }

   public String getPristineAddressLineThree() {
       Address addr = this.getPristineAddress();
      return (addr != null)?addr.getLine3():null;
   }

   public String getCity() {
       Address addr = this.getAddress();
      return (addr != null)?addr.getCity():null;
   }

   public String getPristineCity() {
       Address addr = this.getPristineAddress();
      return (addr != null)?addr.getCity():null;
   }

   public String getCounty() {
       Address addr = this.getAddress();
      return (addr != null)?addr.getCounty():null;
   }

   public String getPristineCounty() {
       Address addr = this.getPristineAddress();
      return (addr != null)?addr.getCounty():null;
   }

   public String getAddressStateCode() {
      Address address = this.getAddress();
      return ( address != null && StringUtils.isNotEmpty(address.getState()) ) ? address
            .getState() : null;
   }

   public String getPristineAddressStateCode() {
      Address address = this.getPristineAddress();
      return ( address != null && StringUtils.isNotEmpty(address.getState()) ) ? address
            .getState() : null;
   }

   public String getAddressZipCode() {
      return this.getAddress() != null ? this.getAddress().getZipCode() : null;
   }

   public String getPristineAddressZipCode() {
      return this.getPristineAddress() != null ? this.getPristineAddress().getZipCode()
            : null;
   }

   public String getAddressZipPlus4() {
      return this.getAddress() != null ? this.getAddress().getZipPlus4() : null;
   }

   public String getPristineAddressZipPlus4() {
      return this.getPristineAddress() != null ? this.getPristineAddress().getZipPlus4()
            : null;
   }

   public String getPrimaryPhone() {
      String phone = ( this.getAssociation() != null ) ? this.getAssociation()
            .getPrimaryPhone() : null;
      return ( StringUtils.isNotEmpty(phone) ) ? phone : null;
   }

   public String getPristinePrimaryPhone() {
      String phone = ( this.getPristineAssociation() != null ) ? this
            .getPristineAssociation().getPrimaryPhone() : null;
      return ( StringUtils.isNotEmpty(phone) ) ? phone : null;
   }

   public String getAssociationFirstName() {
      if( fName == null ) {
         this.fName = ( this.getName() != null ) ? this.getName().getGivenName() : null;
      }
      return StringUtils.isNotEmpty(fName) ? fName : null;
   }

   public String getAssociationLastName() {
      if( lName == null ) {
         this.lName = ( this.getName() != null ) ? this.getName().getFamilyName() : null;
      }
      return StringUtils.isNotEmpty(lName) ? lName : null;
   }

   public Date getAssociationDateTimeLastUpdated() {
      if( lastUpdatedDate == null ) {
         this.lastUpdatedDate = ( this.getAssociation() != null ) ? this.getAssociation()
               .getLastUpdateDate() : null;
      }
      return lastUpdatedDate;
   }

   /**
    * @see gov.va.med.esr.common.rule.AssociationInput#getAssociationDateTimeLastUpdatedOnFile()
    */
   public Date getAssociationDateTimeLastUpdatedOnFile() {
      if( onFileLastUpdatedDate == null ) {
         // Get current set of associations and incoming association
         Association incoming = this.getAssociation();
         Set current = this.getPristinePerson().getAssociations();
         MatchRuleService mrs = this.getMergeRuleService().getMatchRuleService();

         // Find a match association to get last updated date
         Association association = (Association)mrs
               .findMatchingElement(incoming, current);
         this.onFileLastUpdatedDate = ( association != null ) ? association
               .getLastUpdateDate() : null;
      }
      return onFileLastUpdatedDate;
   }

   /**
    * Compares incoming association type with the veteran's record on file. If a matching
    * type is found returns true, false otherwise
    * 
    * @see gov.va.med.esr.common.rule.AssociationInput#isAssociationOnFile(gov.va.med.esr.common.model.lookup.AssociationType)
    */
   public boolean isAssociationOnFile() {
      boolean newRecord = false;
      Association incoming = this.getAssociation();
      if( this.isUpdateFromGUI() ) {
         Association onFileAssociation = ( incoming.getEntityKey() != null ) ? this
               .getPristinePerson().getAssociationByEntityKey(incoming.getEntityKey())
               : null;
         newRecord = ( onFileAssociation != null ) ? true : false;
      }
      else {
         newRecord = ( this.getResultAssociation() != null );
      }
      if( logger.isDebugEnabled() ) {
         logger.debug("Incoming data from GUI ? " + this.isUpdateFromGUI());
         logger.debug("Incoming association " + incoming.toString());
         logger.debug("Is a record on file ? " + ( !newRecord ));
      }
      return newRecord;
   }
   
   public boolean isVAGuardianExist() {
       String type = AssociationType.CODE_GUARDIAN_VA.getCode();
       Association vaGuardian = Association.getAssociationOfType( getPristinePerson().getAssociations(), type );
       return (vaGuardian != null);
   }
   
   public void updateAssociation( String type ) throws RuleException {

      Association updated = Association.getAssociationOfType( getResultPerson().getAssociations(), type );
      Association incoming = Association.getAssociationOfType( getIncomingPerson().getAssociations(), type );
      
      if( updated != null && incoming != null ) {
         try {
            this.getMergeRuleService().mergeAssociation(incoming, updated);
         }
         catch( ServiceException e ) {
            if( this.logger.isDebugEnabled() ) {
               this.logger.debug("Failed to merge an association of typr " + type, e);
            }
            throw new RuleException("Failed to merge an association of type " + type, e);
         }
      }
   }

   public void deactivateAssociationByType(  ) throws RuleException {
       
       Association incoming = this.getAssociation();
       //     CCR10081 -- add guard against NullPointer Exceptions
       if (incoming != null && incoming.getType ()!= null ) {
    	   String type = incoming.getType().getCode();
       
    	   Association updated = Association.getAssociationOfType( getResultPerson().getAssociations(), type );
       
    	   if( updated != null) {
              // 3.1 CCR9947 Use the current date w/timestamp as the deactivation date
              updated.setDeactivationDate(new Date());
    	   }
       }
    }
   
   /*
    * Used as an action to add association to the veteran's record
    * 
    * @see gov.va.med.esr.common.rule.parameter.AssociationInput#acceptAssociationTypeForTheVeteran()
    */
   public void acceptAssociationTypeForTheVeteran() {
      try {
         Association incoming = this.getAssociation();
         Association resultAssociation = null;
         if( this.isUpdateFromGUI() ) {
            resultAssociation = ( incoming.getEntityKey() != null ) ? this
                  .getResultPerson().getAssociationByEntityKey(incoming.getEntityKey())
                  : null;
         }
         else {
            resultAssociation = this.getResultAssociation();
         }
         if( resultAssociation != null ) {
            if( logger.isDebugEnabled() ) {
               logger.debug("Accept and merge association " + incoming.toString());
            }
            this.getMergeRuleService().mergeAssociation(incoming, resultAssociation);
         }
         else {
             addNewAssociation();
         }
      }
      catch( ServiceException e ) {
         if( this.logger.isDebugEnabled() ) {
            this.logger.debug("Failed to merge an association", e);
         }
         throw new RuntimeException("Failed to merge an association", e);
      }
   }

   public void addNewAssociation() {
       try {
           if( logger.isDebugEnabled() ) {
               logger.debug("Accept and add a new association "
                     + this.getAssociation()!=null ? this.getAssociation().toString() : "Null");
            }
             // create a new association, merge and add
             Association newAssociation = new Association();
             this.getMergeRuleService().mergeAssociation(this.getAssociation(),
                   newAssociation);
             this.getResultPerson().addAssociation(newAssociation);
       }
       catch( ServiceException e ) {
          if( this.logger.isDebugEnabled() ) {
             this.logger.debug("Failed to add an association", e);
          }
          throw new RuntimeException("Failed to add an association", e);
       }
    }
   
   // Private Methods

   /**
    * Gets the incoming association address.
    */
   private Address getAddress() {
      return ( this.getAssociation() != null ) ? this.getAssociation().getAddress()
            : null;
   }

   /**
    * Gets the on file assocition address.
    */
   private Address getPristineAddress() {
     //TODO changed null value to new Address() as a quick fix.
     Address pristineAddress=new Address();
    if(this.getPristineAssociation() != null && (this.getPristineAssociation().getAddress()!=null)) 
    {
      pristineAddress=this.getPristineAssociation().getAddress();       
    }
    return pristineAddress;
//      return ( this.getPristineAssociation() != null ) ? this.getPristineAssociation()
//            .getAddress() : new Address();
   }

   /**
    * Gets the incoming assocition.
    */
   private Association getAssociation() {
      if( association == null ) {
         this.association = ( this.getAssociationInputData() != null ) ? this
               .getAssociationInputData().getIncomingAssociation() : null;
      }
      return this.association;
   }

   public Association getResultAssociation() {
      // Get current set of associations and incoming association
      Association incoming = this.getAssociation();
      Set result = this.getResultPerson().getAssociations();
      MatchRuleService mrs = this.getMergeRuleService().getMatchRuleService();

      // Find a match association on file
      return (Association)mrs.findMatchingElement(incoming, result);
   }

   /**
    * Gets the association on file.
    */
   public Association getPristineAssociation() {
      if( associationOnFile == null ) {
         // Get current set of associations and incoming association
         Association incoming = this.getAssociation();
         Set current = this.getPristinePerson().getAssociations();
         MatchRuleService mrs = this.getMergeRuleService().getMatchRuleService();

         // Find a match association on file
         this.associationOnFile = (Association)mrs.findMatchingElement(incoming, current);
      }
      return this.associationOnFile;
   }

   private AssociationInputData getAssociationInputData() {
      RuleDataAware ruleDataAware = this.getRuleDataAware();
      if( ruleDataAware instanceof AssociationInputData ) {
         return (AssociationInputData)ruleDataAware;
      }
      return null;
   }
}