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

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import gov.va.med.fw.util.StringUtils;
import org.apache.commons.lang.Validate;

import gov.va.med.fw.hl7.constants.SegmentConstants;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.builder.Builder;
import gov.va.med.fw.util.builder.BuilderException;

import gov.va.med.esr.common.builder.entity.lookup.PartiallyMappedLookupBuilder;
import gov.va.med.esr.common.model.lookup.County;
import gov.va.med.esr.common.model.lookup.State;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.party.Email;
import gov.va.med.esr.common.model.party.Phone;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonHelperService;

/**
 * 
 * @author Rajiv Patnaik Created on Nov 3, 2005
 * @version 1.0
 * 
 * Copyright © 2005 VHA. All rights reserved
 */
public class AddressElementFormatter
{

    private Builder invalidAddressTypeBuilder;
    
    private LookupService lookupService;
    
    private PersonHelperService personHelperService;
 
    private Builder telecomUseCodeBuilder;

    private Builder telecomEquipmentTypeBuilder;

    private Builder confidentialAddressCategoryTypeBuilder;


	/**
     * @return Returns the lookupService.
     */
    public LookupService getLookupService()
    {
        return lookupService;
    }

    /**
     * @param lookupService
     *            The lookupService to set.
     */
    public void setLookupService(LookupService lookupService)
    {
        this.lookupService = lookupService;
    }

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

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

    /**
     * @return Returns the telecomUseCodeBuilder.
     */
    public Builder getTelecomUseCodeBuilder()
    {
        return telecomUseCodeBuilder;
    }

    /**
     * @param telecomUseCodeBuilder
     *            The telecomUseCodeBuilder to set.
     */
    public void setTelecomUseCodeBuilder(Builder telecomUseCodeBuilder)
    {
        this.telecomUseCodeBuilder = telecomUseCodeBuilder;
    }
    
    /**
     * @return Returns the invalidAddressTypeBuilder.
     */
    public Builder getInvalidAddressTypeBuilder()
    {
        return invalidAddressTypeBuilder;
    }
    /**
     * @param invalidAddressTypeBuilder The invalidAddressTypeBuilder to set.
     */
    public void setInvalidAddressTypeBuilder(Builder invalidAddressTypeBuilder)
    {
        this.invalidAddressTypeBuilder = invalidAddressTypeBuilder;
    }
    
       
    /**
     * Format a confidential address.  
     * @param address The address to format
     * @param confidentialCatCode The confidential address category type code. This is a number between
     * 1 and 5.  It is converted to a String in the form VACxx.  This mapping is defined in the builders.xml
     * file.
     * Added for CCR 10154
     * @return The formatted confidential address.
     * @throws BuilderException
     */
    public String formatConfidentialAddress(Address address, String confidentialCatCode) throws BuilderException{
        Validate.notNull(address, "address is required (cannot be null).");

        StringBuffer formattedAddress = formatAddressPart1(address);
    	
        //Component 7
        //If bad reason code present, lookup  corresponding address type. Else use the address type in        
        if (address.getBadAddressReason() != null)
        {
            formattedAddress.append("~"
                    + (invalidAddressTypeBuilder.build(address
                            .getBadAddressReason().getCode())));
        } else
        {
        	// Look up the confidentailCatCode using the confidentailAddressCategoryTypeBuilder codeMap.
        	// The confidentailCatCode is a value in the codeMap so look up this value and get the
        	// corresponding key to put in the message.
        	String value = "";
         	Map codeMap = ((PartiallyMappedLookupBuilder)confidentialAddressCategoryTypeBuilder).getCodeMap();
        	if (codeMap.containsValue(confidentialCatCode)) {
        		Set set = codeMap.entrySet();
        		Iterator itr = set.iterator();
        		while (itr.hasNext()) {
        			Map.Entry entry = (Map.Entry)itr.next();
        			if (entry.getValue().equals(confidentialCatCode)) {
        				value = (String)entry.getKey();
        				break;
        			}
        		}
        	}
            formattedAddress.append("~" + (address.getType() != null ? value : StringUtils.EMPTY));
        }

        formatAddressPart2(address, formattedAddress, true);
    	return formattedAddress.toString();
    }
    
    // modified for CCR 10154
    public StringBuffer formatAddressPart1(Address address) throws BuilderException {
        // start with first 5 components
        StringBuffer  formattedAddress = new StringBuffer(formatSimpleAddress(address));

        //Component 6
        formattedAddress.append("~" + ((address.getCountry()) != null ? address.getCountry() : StringUtils.EMPTY)); //
   	
        return formattedAddress;
    }

    // modified for CCR 10154 
    // modified for CCR10580 -- added start date and end date for confidential address
    public void formatAddressPart2(Address address, StringBuffer formattedAddress, boolean isIncludeDateRange) {
        //Component 8
        formattedAddress.append("~" + ((address.getLine3() != null) ? address.getLine3() : StringUtils.EMPTY));
        
        //County code. Component 9
        String countyCode = null;
        try {
            countyCode = getCountyCode(address);            
        } catch(Exception ex) {
            //Invalid zip code, ignore
        }
        formattedAddress.append("~" + ((countyCode != null) ? countyCode : StringUtils.EMPTY));
        
        // Only include the date range when formatting confidential address
        if (isIncludeDateRange && (address.getStartDate() != null || address.getEndDate() != null)) {	        
	        // Component 10 and 11 [Not Used]     
	        formattedAddress.append("~~~");
	        
	        // CCR10580 -- add start date and end date(Component 12) for confidential address
	        // ESR can now receive and send all categories of Confidential Addresses.
	        /* The PID segment in the ORUZ05 that ESR sends to the sites does NOT include 
	         * the Start Date and End Date for Confidential Addresses.*/
	        if (address.getStartDate() != null) {
	        	formattedAddress.append(DateFormatter.formatDate(address.getStartDate()));
	        }
	        if (address.getEndDate() != null) {
	        	formattedAddress.append("&" + DateFormatter.formatDate(address.getEndDate()));
	        }
        }
    }
    
    /**
     * Formats address for the PID segment
     * 
     * @param address
     * @return
     * @throws BuilderException
     */
    public String formatAddress(Address address) throws BuilderException
    {
        Validate.notNull(address, "address is required (cannot be null).");
	    // modified for CCR 10154
        StringBuffer  formattedAddress = formatAddressPart1(address);
      
        //Component 7
        //If bad reason code present, lookup  corresponding address type. Else use the address type in        
        if (address.getBadAddressReason() != null)
        {
            formattedAddress.append("~"
                    + (invalidAddressTypeBuilder.build(address
                            .getBadAddressReason().getCode())));
        } else
        {
            formattedAddress.append("~" + (address.getType() != null ? address.getType().getCode() : StringUtils.EMPTY));
        }

	    // modified for CCR 10154        
        formatAddressPart2(address, formattedAddress, false);

        return formattedAddress.toString();
    }

    /**
     * CCR9963
     * Formats simple address (AD) for the ZCT and ZEM segment
     * 
     * @param address
     * @return
     * @throws BuilderException
     */
    public String formatSimpleAddress(Address address) throws BuilderException
    {
        Validate.notNull(address, "address is required (cannot be null).");

        StringBuffer  formattedAddress = new StringBuffer();

        //Find the State or Province Code
        State state = null;
        try
        {
            state = personHelperService.getStateFromCodeOrName(address.getState());
        } catch (ServiceException e)
        {
            throw new BuilderException("Could not get state from state code " +address.getState(), e);
        }
        
        String stateCode = (state == null) ? null : state.getCode();        
        //If state is null, get the province code. If none present, null will
        // be set
        String stateOrProvinceCode = stateCode == null ? address.getProvince()
                : stateCode;

        //Find the Zip or Postal Code. If zip is null get Zip Code, else get
        // the Postal Code. If none present, null will be set
        String zipOrPostalCode = address.getZipCode() != null ? address
                .getZipCode() : address.getPostalCode();

        
        
        //Component 1 
        formattedAddress.append((address.getLine1() != null) ? address.getLine1() : StringUtils.EMPTY);
        //Component 2
        formattedAddress.append("~" + ((address.getLine2() != null) ? address.getLine2() : StringUtils.EMPTY));
        //Component 3
        formattedAddress.append("~" + ((address.getCity() != null) ? address.getCity() : StringUtils.EMPTY));
        //Component 4
        formattedAddress.append("~" + ((stateOrProvinceCode != null) ? stateOrProvinceCode : StringUtils.EMPTY));
        //Component 5
        formattedAddress.append("~" + ((zipOrPostalCode != null) ? zipOrPostalCode : StringUtils.EMPTY));

        if ((address.getZipPlus4() != null) && (!StringUtils.equals(address.getZipPlus4(),StringUtils.EMPTY))) 
        {
            formattedAddress.append("-" + address.getZipPlus4());
        }
        
        //The start date and end date(Component 12) are used in incoming messages when
        //address type = Confidential Communications -Enrollment.

        return formattedAddress.toString();
    }

    /**
     * @param phones
     * @return
     * @throws BuilderException
     */
    public String formatPhonesAndEmail(Set phones, Set emails)
            throws BuilderException
    {
        StringBuffer formattedElement = new StringBuffer();
        if (phones != null && !phones.isEmpty() )
        { 
        	
            int i = 0;
            for (Iterator iter = phones.iterator(); iter.hasNext();)
            {
                
                Phone phone = (Phone) iter.next();
  
                String telecomUseCode = 
                	(String)telecomUseCodeBuilder.build(phone.getType().getCode());                 
                
                String telecomEquipmentType = (String)telecomEquipmentTypeBuilder.build(phone.getType().getCode());
                
                //Only share the phone numbers for which there is a telecom use code
                if(telecomUseCode != null)
                {
	            	//Add a repeat element "|" starting from the second phone
	                formattedElement.append((i == 0) ? "" : "|");
	
	                formattedElement.append(phone.getPhoneNumber()).append("~").
	                	append(telecomUseCode).append("~").append(telecomEquipmentType);
                }
                i++;
            }
        }

        if (emails != null && !emails.isEmpty())
        {
            //There is currently support for only one email
            Email email = (Email) emails.iterator().next();

            if (phones != null && !phones.isEmpty() ) {
            	formattedElement.append("|");
            }
            formattedElement.append("~").append(SegmentConstants.TELECOM_USE_CODE_EMAIL)
                    .append("~").append(SegmentConstants.TELECOM_EQUIPMENT_EMAIL).append("~")
                    .append(email.getAddress());

        }

        return formattedElement.toString();
    }

    private String getCountyCode(Address address)
            throws BuilderException
    {

        try
        {
        	County county = lookupService.getCountyByAddress(address);
        	return county == null ? null : county.getCountyNumber();
        } catch (ServiceException e)
        {
            throw new BuilderException(
                    "Exception looking up County by Address: " + address, e);
        }

    }
    
    public Builder getConfidentialAddressCategoryTypeBuilder() {
		return confidentialAddressCategoryTypeBuilder;
	}

	public void setConfidentialAddressCategoryTypeBuilder(
			Builder confidentialAddressCategoryTypeBuilder) {
		this.confidentialAddressCategoryTypeBuilder = confidentialAddressCategoryTypeBuilder;
	}
    
}

