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

// Java Classes
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import gov.va.med.fw.hl7.InvalidMessageException;
import gov.va.med.fw.hl7.HL7Message;
import gov.va.med.fw.hl7.segment.ZEM;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.util.builder.Builder;
import gov.va.med.fw.util.builder.BuilderException;

import gov.va.med.esr.common.builder.entity.metaData.PersonMetaDataFromPID;
import gov.va.med.esr.common.model.ee.PrisonerOfWar;
import gov.va.med.esr.common.model.lookup.EmergencyResponse;
import gov.va.med.esr.common.model.lookup.Gender;
import gov.va.med.esr.common.model.lookup.Indicator;
import gov.va.med.esr.common.model.lookup.MaritalStatus;
import gov.va.med.esr.common.model.lookup.NameType;
import gov.va.med.esr.common.model.lookup.Relationship;
import gov.va.med.esr.common.model.lookup.Religion;
import gov.va.med.esr.common.model.person.BirthRecord;
import gov.va.med.esr.common.model.person.DeathRecord;
import gov.va.med.esr.common.model.person.EmergencyResponseIndicator;
import gov.va.med.esr.common.model.person.Ethnicity;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PreferredFacility;
import gov.va.med.esr.common.model.person.Race;
import gov.va.med.esr.common.model.person.Relation;
import gov.va.med.esr.common.model.person.SSN;

/**
 * Class to build a person from a message.
 * 
 * @author Vu Le
 * @version 1.0
 */
public class PersonBuilder extends EntityBuilder
{
    /**
     * An instance of serialVersionUID
     */
    private static final long serialVersionUID = -8951207515778923723L;

    private Builder birthRecordBuilder;

    private Builder deathRecordBuilder;

    private Builder emergencyResponseBuilder;

     private Builder genderBuilder;

    private Builder maritalStatusBuilder;

    private Builder nameBuilder;

    private Builder nameTypeBuilder;

    private Builder ssnBuilder;
    
    private Builder relationshipBuilder;

    private Builder raceBuilder;
    
    private Builder ethnicityBuilder;
    
    private Builder religionBuilder;

    /**
     * Default contructor.
     */
    public PersonBuilder()
    {
        super();
    }

    public Builder getBirthRecordBuilder()
    {
        return this.birthRecordBuilder;
    }

    public void setBirthRecordBuilder(Builder birthRecordBuilder)
    {
        this.birthRecordBuilder = birthRecordBuilder;
    }

    public Builder getDeathRecordBuilder()
    {
        return this.deathRecordBuilder;
    }

    public void setDeathRecordBuilder(Builder deathRecordBuilder)
    {
        this.deathRecordBuilder = deathRecordBuilder;
    }

    /**
     * @return Returns the emergencyResponseBuilder.
     */
    public Builder getEmergencyResponseBuilder()
    {
        return emergencyResponseBuilder;
    }
    /**
     * @param emergencyResponseBuilder The emergencyResponseBuilder to set.
     */
    public void setEmergencyResponseBuilder(Builder emergencyResponseBuilder)
    {
        this.emergencyResponseBuilder = emergencyResponseBuilder;
    }
    public Builder getGenderBuilder()
    {
        return this.genderBuilder;
    }

    public void setGenderBuilder(Builder genderBuilder)
    {
        this.genderBuilder = genderBuilder;
    }

    public Builder getMaritalStatusBuilder()
    {
        return this.maritalStatusBuilder;
    }

    public void setMaritalStatusBuilder(Builder maritalStatusBuilder)
    {
        this.maritalStatusBuilder = maritalStatusBuilder;
    }

    public Builder getNameBuilder()
    {
        return this.nameBuilder;
    }

    public void setNameBuilder(Builder nameBuilder)
    {
        this.nameBuilder = nameBuilder;
    }

    public Builder getNameTypeBuilder()
    {
        return this.nameTypeBuilder;
    }

    public void setNameTypeBuilder(Builder nameTypeBuilder)
    {
        this.nameTypeBuilder = nameTypeBuilder;
    }

    /**
     * @return Returns the ssnBuilder.
     */
    public Builder getSsnBuilder()
    {
        return ssnBuilder;
    }

    /**
     * @param ssnBuilder
     *            The ssnBuilder to set.
     */
    public void setSsnBuilder(Builder ssnBuilder)
    {
        this.ssnBuilder = ssnBuilder;
    }

    public Builder getRelationshipBuilder() {
        return relationshipBuilder;
    }

    public void setRelationshipBuilder(Builder relationshipBuilder) {
        this.relationshipBuilder = relationshipBuilder;
    }
    
    public Builder getEthnicityBuilder() {
        return ethnicityBuilder;
    }

    public void setEthnicityBuilder(Builder ethnicityBuilder) {
        this.ethnicityBuilder = ethnicityBuilder;
    }

    public Builder getRaceBuilder() {
        return raceBuilder;
    }

    public void setRaceBuilder(Builder raceBuilder) {
        this.raceBuilder = raceBuilder;
    }

    public Builder getReligionBuilder() {
        return religionBuilder;
    }

    public void setReligionBuilder(Builder religionBuilder) {
        this.religionBuilder = religionBuilder;
    }

   public Person build(Person input, HL7Message message)
            throws BuilderException, InvalidMessageException
    {

        ZEM zem = null;
        List zems = message.getZEMSegments();
        for (Iterator i = zems.iterator(); (zem == null) && i.hasNext();)
        {
            ZEM temp = (ZEM) i.next();
            if ("1".equals(temp.getSetID()))
            {
                zem = temp;
            }
        }
        // Create a person demographic meta data
        PersonMetaData metaData = new PersonMetaDataFromPID(message
                .getPIDSegment(), message.getZPDSegment(), zem, message
                .getZENSegment(), message.getZELSegment(), message
                .getMSHSegment(), message.getZMHSegments(), message
                .getZIOSegment());
        metaData.setEntity(input);

        return this.build(metaData);
    }

    public Person build(PersonMetaData metaData) throws BuilderException
    {
        Person input = metaData.getEntity();
        Person output = (input == null) ? new Person() : input;

        this.transfer(output, metaData);

        return this.shouldKeep(output) ? output : null;
    }

    private BirthRecord buildBirthRecord(BirthRecord input,
            BirthRecordMetaData metaData) throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        }
        metaData.setEntity(input);
        return (BirthRecord) this.birthRecordBuilder.build(metaData);

    }

    private DeathRecord buildDeathRecord(DeathRecord input,
            DeathRecordMetaData metaData) throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        }
        metaData.setEntity(input);
        return (DeathRecord) this.deathRecordBuilder.build(metaData);
    }


    private Gender buildGender(Gender input, String code)
            throws BuilderException
    {
        return (Gender) super.build(this.genderBuilder, input, code);
    }

    private MaritalStatus buildMaritalStatus(MaritalStatus input, String code)
            throws BuilderException
    {
        return (MaritalStatus) super.build(this.maritalStatusBuilder, input,
                code);
    }

    private Name buildName(Name input, NameMetaData metaData)
            throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        }
        metaData.setEntity(input);
        return (Name) this.nameBuilder.build(metaData);

    }

    private void buildNames(Person input, NameMetaData[] metaData)
            throws BuilderException
    {
        Set toAdd = new HashSet();
        Set toRemove = new HashSet(input.getNames());

        for (int index = 0; index < ((metaData == null) ? 0 : metaData.length); index++)
        {
            this.processName(toAdd, metaData[index]);
        }

        for (Iterator i = toRemove.iterator(); i.hasNext();)
        {
            input.removeName((Name) i.next());
        }

        for (Iterator i = toAdd.iterator(); i.hasNext();)
        {
            input.addName((Name) i.next());
        }
    }

    private NameMetaData getLegalNameMetaData(NameMetaData[] namesMetaData) {
         
        for (int index = 0; index < ((namesMetaData == null) ? 0 : namesMetaData.length); index++)
        {
            if (NameType.LEGAL_NAME.getCode().equals(namesMetaData[index].getType()))
                return namesMetaData[index];
        }
        return null;
    }
    
    private boolean shouldKeep(Person obj)
    {
        return ((obj.getEntityKey() != null));
    }

    private void processName(Set toAdd, NameMetaData metaData)
            throws BuilderException
    {

        Name value = this.buildName(null, metaData);

        if (value != null)
        {
            toAdd.add(value);
        }
    }

    /**
     * This builds only the mother and father with only the name populated
     * @param input
     * @param metaData
     * @param relationship
     * @throws BuilderException
     */
    private void buildRelationWNameOnly(Person input, NameMetaData metaData, String relationshipCode)
        throws BuilderException
   {
        Name nameInput = null;
        Relation relation = input.getRelation(relationshipCode);
        if (relation != null)
            nameInput = relation.getName();

        // START of CCR 10300 - there is an undocumented requirement to delete Father/Mother name. The current
        // implementation was only adding names with no ability to remove. Doing it here reduces
        // side-effects to other type of names and simply removes the Relation, which obviously
        // does not exist or was wrong.
        if (relation != null && metaData != null && "".equals(metaData.getFamilyName()) ) {
        	input.removeRelation(relation);
        	return;
        }
        if (relation != null && metaData != null && relation.getName() != null && relation.getName().getGivenName() != null 
        		&&  metaData.getGivenName() == null) {
        	relation.getName().setGivenName(null);
        }        
        if (relation != null && metaData != null && relation.getName() != null && relation.getName().getMiddleName() != null 
        		&&  metaData.getMiddleName() == null) {
        	relation.getName().setMiddleName(null);
        }
        // END of CCR 10300
        
        Name value = this.buildName(nameInput, metaData);

        if (value != null)
        {
          // create/set mother's name & father's name
          if (relation == null) {
              relation = new Relation();
              Relationship relationship = (Relationship) super.build(this.relationshipBuilder, null,                  
                      relationshipCode);
              relation.setRelationship(relationship);
              relation.setName(value);
          }
          input.setRelation(relationshipCode, relation);
        }

    }
    
    /**
     * @param input
     * @param ssns
     * @throws BuilderException
     */
    private void buildSsns(Person input, SSNMetaData[] metaData)
            throws BuilderException
    {
        Set toAdd = new HashSet();
        Set toRemove = new HashSet(input.getSsns());

        for (int index = 0; index < ((metaData == null) ? 0 : metaData.length); index++)
        {
            this.processSsn(toAdd, metaData[index]);
        }

        for (Iterator i = toRemove.iterator(); i.hasNext();)
        {
            input.removeSsn((SSN) i.next());
        }

        for (Iterator i = toAdd.iterator(); i.hasNext();)
        {
            input.addSsn((SSN) i.next());
        }

    }

    private void processSsn(Set toAdd,
            SSNMetaData metaData) throws BuilderException
    {
        SSN value = this.buildSsn(null, metaData);
        if (value != null)
        {
                toAdd.add(value);
        }
    }

    private SSN buildSsn(SSN input, SSNMetaData metaData)
            throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        } else
        {
            metaData.setEntity(input);
            return (SSN) this.ssnBuilder.build(metaData);
        }
    }

    private void transfer(Person input, PersonMetaData metaData)
            throws BuilderException
    {
        input.setBirthRecord(this.buildBirthRecord(input.getBirthRecord(),
                metaData.getBirthRecord()));
        
        input.setDeathRecord(this.buildDeathRecord(input.getDeathRecord(),
                metaData.getDeathRecord()));
        
        
        input.setGender(this.buildGender(input.getGender(), metaData
                .getGender()));
        
        input.setMaritalStatus(this.buildMaritalStatus(
                input.getMaritalStatus(), metaData.getMaritalStatus()));

        /* CCR 10640 -- preferred facility */
        this.buildPreferredFacility(input, metaData);
        
        PrisonerOfWar pow = new PrisonerOfWar();
        Indicator powIndicator = (Indicator)super.build(getIndicatorBuilder(),pow.getPowIndicator(),metaData.getWasPrisonerOfWar());
        if(powIndicator != null) {
            pow.setPowIndicator(powIndicator);
            input.setPrisonerOfWar(pow); 
        }
        
        input.setVeteran(super.build(input.isVeteran(), metaData.getVeteran()));

        this.buildNames(input, metaData.getNames());
        
        // Mother & father's name
        this.buildRelationWNameOnly(input, metaData.getMotherName(), Relationship.CODE_MOTHER.getCode());
        this.buildRelationWNameOnly(input, metaData.getFatherName(), Relationship.CODE_FATHER.getCode());
          
        /* Degree is taken from the Legal name */
        NameMetaData legalName = getLegalNameMetaData(metaData.getNames());      
        if (legalName!= null) {
            input.setDegree(legalName.getDegree());
        }
        
       this.buildSsns(input, metaData.getSsns());

        input.setAppointmentRequestDate(super.build(input
                .getAppointmentRequestDate(), metaData
                .getAppointmentRequestDate()));

        input.setAppointmentRequestResponse(super.build(input
                .getAppointmentRequestResponse(), metaData
                .getAppointmentRequestOn1010EZ()));
        
        this.buildEmergencyIndicator(input, metaData);
            
        // RP 08/17 - Note: No need to set the NationalICN from message for
        // the incoming message because it does not mean anything to us.
        

        this.buildRaces(input, metaData.getRaces());
        
        input.setEthnicity(this.buildEthnicity(input.getEthnicity(),
                metaData.getEthnicity()));
        
        input.setReligion(this.buildReligion(
                input.getReligion(), metaData.getReligion()));

        input.setMothersMaidenName(super.build(
                input.getMothersMaidenName(), metaData.getMothersMaidenName()));
    }

    /**
     * @param input
     * @param metaData
     * @throws BuilderException
     */
    private void buildEmergencyIndicator(Person input, PersonMetaData metaData) throws BuilderException
    {
        EmergencyResponseIndicator emergencyResponseIndicator = new EmergencyResponseIndicator();
        EmergencyResponse emergencyResponse = (EmergencyResponse) super.build(
                emergencyResponseBuilder, emergencyResponseIndicator
                        .getEmergencyResponse(), metaData
                        .getEmergencyResponseType());
        
        if(emergencyResponse != null)
        {
            emergencyResponseIndicator.setEmergencyResponse(emergencyResponse);
            input.addEmergencyResponseIndicator(emergencyResponseIndicator);
        }
    }

    /**
     * @param input
     * @param races
     * @throws BuilderException
     */
    private void buildRaces(Person input, RaceMetaData[] metaData)
            throws BuilderException
    {
        Set toAdd = new HashSet();
        Set toRemove = new HashSet(input.getRaces());

        for (int index = 0; index < ((metaData == null) ? 0 : metaData.length); index++)
        {
            this.processRace(toAdd, metaData[index]);
        }

        for (Iterator i = toRemove.iterator(); i.hasNext();)
        {
            input.removeRace((Race) i.next());
        }

        for (Iterator i = toAdd.iterator(); i.hasNext();)
        {
            input.addRace((Race) i.next());
        }

    }

    private void processRace(Set toAdd,
            RaceMetaData metaData) throws BuilderException
    {
        Race value = this.buildRace(null, metaData);
        if (value != null)
        {
                toAdd.add(value);
        }
    }

    private Race buildRace(Race input, RaceMetaData metaData)
            throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        } else
        {
            metaData.setEntity(input);
            return (Race) this.raceBuilder.build(metaData);
        }
    }
    
    /**
     * @param input
     * @param metaData
     * @throws BuilderException
     */
    private Ethnicity buildEthnicity(Ethnicity input,
            EthnicityMetaData metaData) throws BuilderException
    {
        if (metaData == null)
        {
            return null;
        }
        metaData.setEntity(input);
        return (Ethnicity) this.ethnicityBuilder.build(metaData);

    }

    /**
     * 
     * @param input
     * @param code
     * @return
     * @throws BuilderException
     */
    private Religion buildReligion(Religion input, String code)
    throws BuilderException
    {
        return (Religion) super.build(this.religionBuilder, input,
        code);
    }

    /**
     * @param input
     * @param metaData
     * @throws BuilderException
     */
    private void buildPreferredFacility(Person input, PersonMetaData metaData) throws BuilderException
    {
    	input.removeAllPreferredFacilities();
    	if (! StringUtils.isEmpty(metaData.getPreferredFacility())) {
    		PreferredFacility pf = new PreferredFacility();
    		pf.setFacility(super.build(pf.getFacility(),
    		                metaData.getPreferredFacility()));
    		input.addPreferredFacility(pf);
    	}	
    }
 }