package gov.va.med.esr.messaging.builder.message;

import gov.va.med.esr.common.model.lookup.CollectionMethod;
import gov.va.med.esr.common.model.lookup.ConfidentialAddressCategoryType;
import gov.va.med.esr.common.model.lookup.County;
import gov.va.med.esr.common.model.lookup.Gender;
import gov.va.med.esr.common.model.lookup.NameType;
import gov.va.med.esr.common.model.lookup.PhoneType;
import gov.va.med.esr.common.model.lookup.SSNType;
import gov.va.med.esr.common.model.messaging.SiteIdentity;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.party.ConfidentialAddressCategory;
import gov.va.med.esr.common.model.party.Phone;
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.Race;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.service.PersonHelperService;
import gov.va.med.fw.hl7.Segment;
import gov.va.med.fw.hl7.constants.SegmentConstants;
import gov.va.med.fw.hl7.segment.PID;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.util.builder.BuilderException;

import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;

/**
 *
 * @author Rajiv Patnaik Created on Sep 8, 2005
 * @version 1.0
 *
 * Copyright © 2005 VHA. All rights reserved
 */
public abstract class AbstractPIDBuilder extends AbstractSegmentBuilder
{

    private static final long serialVersionUID = -1370346746640850300L;
    private AddressElementFormatter addressFormatter;

    private PersonHelperService personHelperService;


    /**
     * @return Returns the addressFormatter.
     */
    public AddressElementFormatter getAddressFormatter()
    {
        return addressFormatter;
    }

    /**
     * @param addressFormatter
     *            The addressFormatter to set.
     */
    public void setAddressFormatter(AddressElementFormatter addressFormatter)
    {
        this.addressFormatter = addressFormatter;
    }

     /**
     * @return Returns the personHelperService.
     */
    public PersonHelperService getPersonHelperService()
    {
        return personHelperService;
    }
    /**
     * @param personHelperService The personHelperService to set.
     */
    public void setPersonHelperService(PersonHelperService personHelperService)
    {
        this.personHelperService = personHelperService;
    }

    /**
     * Method that gets alias name.
     *
     * @param person
     *            The Person Object.
     * @return alias name.
     */
    protected String getAlias(Person person)
    {
        String alias = null;

        Set names = person.getNames();
        Name aliasName = null;

        if (names != null)
        {
            Iterator iterNames = names.iterator();
            while (iterNames.hasNext())
            {
                Name theName = (Name) iterNames.next();
                if ((theName != null) && (theName.getType() != null))
                {
                    if (NameType.ALIAS_NAME.getName().equals(
                            theName.getType().getCode()))
                    {
                        aliasName = theName;
                    }
                }
            }
        }

        if (aliasName != null)
        {
            alias = ElementFormatter.formatName(aliasName);
        }

        return alias;
    }

    protected void setRace(Person person, PID segment)
    {

    	StringBuffer raceBuffer = new StringBuffer();
    	Set races = person.getRaces();

    	if (races == null)
    	{
    		segment.setRace(SegmentConstants.DEFAULT_VALUE);
    		return;
    	}

    	Iterator raceIter = races.iterator();
    	boolean isFirst = true;
    	while( raceIter.hasNext())
    	{
    		Race race = (Race)raceIter.next();

    		if (! isFirst)
    		{
    			raceBuffer.append("|");
    		}
    		isFirst = false;

    		//2028-9-SLF~~0005~2028-9~~CDC
    		raceBuffer.append(race.getRaceType().getCode());
    		raceBuffer.append("-");
    		raceBuffer.append(getCollectionMethodCode(race.getCollectionMethod()));
    		raceBuffer.append("~~005");
            raceBuffer.append("~");
    		raceBuffer.append(race.getRaceType().getCode());
    		raceBuffer.append("~~CDC");
    	}
    	segment.setRace(raceBuffer.toString());
    }

    private String getCollectionMethodCode(CollectionMethod cm) {
		String cmCode = "SLF";
		try {
			if (cm == null) {
				cm = this.getLookupService().getCollectionMethodByCode(CollectionMethod.CODE_SLF.getCode());
			}
		}  catch (ServiceException e) {
			// ignore and use default
		}
		if (cm != null){
			cmCode = cm.getCode();
		}
    	return cmCode;
    }

    protected void setEthnicity(Person person, PID segment)
    {

    	Ethnicity ethnicity = person.getEthnicity();
    	if ( ethnicity == null )
    	{
    		segment.setEthnicity(SegmentConstants.DEFAULT_VALUE);
    		return;
    	}

    	StringBuffer ethBuffer = new StringBuffer();

    	//2186-5-SLF~~0189~2186-5~~CDC
    	ethBuffer.append(ethnicity.getEthnicityType().getCode());
    	ethBuffer.append("-");
    	ethBuffer.append(getCollectionMethodCode(ethnicity.getCollectionMethod()));
    	ethBuffer.append("~~0189");
    	ethBuffer.append("~");
    	ethBuffer.append(ethnicity.getEthnicityType().getCode());
    	ethBuffer.append("~~CDC");

      segment.setEthnicity(ethBuffer.toString());
    }

    /**
     * Sets the National ICN, SSN,  DFN and ClaimFolder # on PID 3
     *
     * @param person
     * @param siteIdentity
     * @param segment
     */
    protected String getPatientIdentifierList(Person person, SiteIdentity siteIdentity)
    {
        StringBuffer patientIdentifierList = new StringBuffer();

        //Build National ICN
        String VPIDKey = person.getVPIDEntityKey() == null ? null : person
                .getVPIDEntityKey().getVPID();
        String icn = personHelperService.getICNChecksum(VPIDKey);

        patientIdentifierList.append(((icn != null) ? icn : StringUtils.EMPTY )  + "~~~"
                + SegmentConstants.ASSIGNING_AUTHORITY_USVHA + "&&"
                + SegmentConstants.ASSIGNING_AUTHORITY_HL7_TABLE
                + "~" + SegmentConstants.IDENTIFIER_TYPE_NI
                + "~" + SegmentConstants.ASSIGNING_LOCATION_VA_FACILITY + "&"
                + SegmentConstants.ASSIGNING_LOCATION_ICN + "&" +
                SegmentConstants.ASSIGNING_LOCATION_LOCAL_CODING_SCHEME);

        //Build SSN
        String ssnText = person.getOfficialSsn() == null ? StringUtils.EMPTY : person.getOfficialSsn().getSsnText();

        patientIdentifierList.append("|"+ (ssnText != null ? ssnText : StringUtils.EMPTY) + "~~~"
                + SegmentConstants.ASSIGNING_AUTHORITY_USSSA + "&&"
                + SegmentConstants.ASSIGNING_AUTHORITY_HL7_TABLE
                + "~" + SegmentConstants.IDENTIFIER_TYPE_SS
                + "~" + SegmentConstants.ASSIGNING_LOCATION_VA_FACILITY + "&"
                + siteIdentity.getVaFacility().getStationNumber()+ "&" +
                SegmentConstants.ASSIGNING_LOCATION_LOCAL_CODING_SCHEME);

        //Build DFN
        patientIdentifierList.append("|"+ (siteIdentity.getDfn() != null ? siteIdentity.getDfn() : StringUtils.EMPTY) + "~~~"
                + SegmentConstants.ASSIGNING_AUTHORITY_USVHA + "&&"
                + SegmentConstants.ASSIGNING_AUTHORITY_HL7_TABLE
                + "~" + SegmentConstants.IDENTIFIER_TYPE_PI
                + "~" + SegmentConstants.ASSIGNING_LOCATION_VA_FACILITY + "&"
                + siteIdentity.getVaFacility().getStationNumber()+ "&" +
                SegmentConstants.ASSIGNING_LOCATION_LOCAL_CODING_SCHEME);

        //Build Claim Folder #
        patientIdentifierList.append("|"+ (person.getClaimFolderNumber() != null ? person.getClaimFolderNumber() :StringUtils.EMPTY) + "~~~"
                + SegmentConstants.ASSIGNING_AUTHORITY_USVBA + "&&"
                + SegmentConstants.ASSIGNING_AUTHORITY_HL7_TABLE
                + "~" + SegmentConstants.IDENTIFIER_TYPE_PN
                + "~" + SegmentConstants.ASSIGNING_LOCATION_VA_FACILITY + "&"
                + siteIdentity.getVaFacility().getStationNumber()+ "&" +
                SegmentConstants.ASSIGNING_LOCATION_LOCAL_CODING_SCHEME);

        return patientIdentifierList.toString();

    }

    // Added for CCR 10154
    protected String buildConfidentialAddresses(Person person, Address confAddr) throws BuilderException {
    	StringBuffer sb = new StringBuffer();
		Set confAddrCats = person.getConfidentialAddressCategories();
		Iterator itr = confAddrCats.iterator();
		ConfidentialAddressCategory cat;
		ConfidentialAddressCategoryType catt;

		while (itr.hasNext()) {
			if (sb.length() > 0) {
				sb.append("|");
			}
			cat = (ConfidentialAddressCategory)itr.next();
			//set confidential address only
            sb.append(addressFormatter.formatConfidentialAddress(confAddr, cat.getType().getCode()));
		}

    	return sb.toString();
    }


    /**
     * Method that sets address to the segment.
     *
     * @param person
     *            The Person Object.
     * @param segment
     *            The PID segment.
     * @throws BuilderException
     */
    protected void setAddress(Person person, PID segment)
            throws BuilderException
    {
        Address address = person.getPermanentAddress();
        //CCR 9842: The Z05 triggered out of ESR is not including the confidential address or phone number in the PID.
        Address confAddr = person.getConfidentialAddress();
        //Set Address
        if (address == null)
        {
        	if (confAddr == null)
        	{
        		segment.setPatientAddress(null); //both null
        	} else
        	{
        		//set confidential address only
        	    // modified for CCR 10154
                segment.setPatientAddress(addressFormatter.formatConfidentialAddress(confAddr,
                		buildConfidentialAddresses(person, confAddr)));
        	}
        } else
        {
        	if (confAddr == null)
        	{
        		segment.setPatientAddress(addressFormatter.formatAddress(address)); //set permanent address only

        	} else
        	{
        		// CCR11352 -- confAddr may be not null, but the formatted string may be empty, so check the
        		// resulting formatted string first
        		String formattedConfAddr = buildConfidentialAddresses(person, confAddr);


        		//both not null, set both
        	    // modified for CCR 10154
        		segment.setPatientAddress(
        			StringUtils.isEmpty(formattedConfAddr)?
        			addressFormatter.formatAddress(address) :
        			addressFormatter.formatAddress(address)+"|"+formattedConfAddr );
        	}
        }
        //set the county code PID 12
        //email from Terry Moore 10/08/2009: PID.12 is only for the permanent address county

        if (address == null)
        {
        	segment.setCountyCode(null);
        } else
        {
	        County county = null;
			try
			{
				county = getLookupService().getCountyByAddress(address);
			} catch (ServiceException e)
			{
				//Ignore the exception, invalid zip code or it is a postal code
			}
			segment.setCountyCode(county == null ? StringUtils.EMPTY : county.getCountyNumber());
        }
    }

    /**
     * Method that sets phones and emails to the segment.
     *
     * @param person
     *            The Person Object.
     * @param segment
     *            The PID segment.
     * @throws BuilderException
     */
    protected void setPhonesAndEmails(Person person, PID segment)
            throws BuilderException
    {
    	//CCR11569
    	// String formattedElemnt = addressFormatter.formatPhonesAndEmail(person
    	//         .getPhones(), person.getEmails());

     	String formattedElemnt = addressFormatter.formatPhonesAndEmail(filtratePhones(person.getPhones()), person.getEmails());

        segment.setHomePhoneNumber(formattedElemnt);
    }

    protected void setBusinessPhone(Person person, PID segment)
    {
        Set phones = person.getPhones();

        Phone workPhone = Phone.getPhoneOfType(phones, PhoneType.CODE_BUSINESS
                .getName());

        //Set Work Phone Number
        if (workPhone == null)
        {
            segment.setBusinessPhoneNumber(null);
        } else
        {
            segment.setBusinessPhoneNumber(workPhone.getPhoneNumber());
        }
    }

    /**
     * If official SSN exists then the segment will be set with that value If no
     * official however Other exists then set the SSN segment equal to this
     * value
     *
     * @param Person
     * @param PID
     */
    protected void setSSN(Person person, PID segment)
    {
        Set ssns = person.getSsns();
        //First check if an offical SSN exists
        SSN ssn = SSN.getSSNOfType(ssns, SSNType.CODE_ACTIVE.getName());

        //If not then get the first SSN of typ "Other"
        if (ssn == null)
        {
            ssn = SSN.getSSNOfType(ssns, SSNType.CODE_ALIAS.getName());
        }

        String segmentSsn = ssn != null ? ssn.getSsnText() : null;

        segment.setSSN(segmentSsn);

    }

    /**
     * Method that builds gender
     *
     * @param gender
     *            The Gender Object.
     * @return gender code.
     */
    protected String buildGender(Gender gender) throws BuilderException
    {
        return super.build(getGenderBuilder(), gender);
    }

    protected void setDefaultValues(Segment segment)
    {
        PID pid = (PID)segment;

        pid.setPatientName(SegmentConstants.DEFAULT_VALUE);
        pid.setDOB(SegmentConstants.DEFAULT_VALUE);
        pid.setSex(SegmentConstants.DEFAULT_VALUE);
        pid.setSSN(SegmentConstants.DEFAULT_VALUE);
    }
    //CCR11569 if there are more than one phone numbers for home/work phone, then pick the one with
    //the most recent Last Update Date and put that one in the Z05.

    private Set filtratePhones(Set phones)
    {
    	Set filtratedPhones = new HashSet();
    	Phone homePhone = null;
    	Phone workPhone = null;
    	Phone mobilePhone = null;
    	Set subPhones = new HashSet();
    	Iterator iter = phones.iterator();
    	 while( iter.hasNext() ) {
             Phone incoming = (Phone)iter.next();
             //CCR13723, add additional null checks to avoid NPE on .after(changeDate=null)
             if(incoming.getType().getCode().equals(PhoneType.CODE_HOME.getCode()) && (homePhone == null || homePhone.getChangeDate() == null ||
            		 (incoming.getChangeDate()!= null && incoming.getChangeDate().after(homePhone.getChangeDate()))))
             {
            	 homePhone = incoming;
             }
             if(incoming.getType().getCode().equals(PhoneType.CODE_BUSINESS.getCode())&& (workPhone == null || workPhone.getChangeDate() == null ||
            		 (incoming.getChangeDate()!= null && incoming.getChangeDate().after(workPhone.getChangeDate()))))
             {
            	 workPhone = incoming;
             }
             if(incoming.getType().getCode().equals(PhoneType.CODE_MOBILE.getCode())&& (mobilePhone == null || mobilePhone.getChangeDate() == null ||
            		 (incoming.getChangeDate()!= null && incoming.getChangeDate().after(mobilePhone.getChangeDate()))))
             {
            	 mobilePhone = incoming;
             }
             if(!incoming.getType().getCode().equals(PhoneType.CODE_HOME.getCode()) && !incoming.getType().getCode().equals(PhoneType.CODE_BUSINESS.getCode())
            		 && !incoming.getType().getCode().equals(PhoneType.CODE_MOBILE.getCode()))
            	 subPhones.add(incoming);
         }
    	 if(homePhone!=null)
    		 filtratedPhones.add(homePhone);
    	 if(workPhone != null)
    		 filtratedPhones.add(workPhone);
    	 if(mobilePhone != null)
    		 filtratedPhones.add(mobilePhone);

    	 filtratedPhones.addAll(subPhones);

    	 return filtratedPhones;

 }
}