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

import gov.va.med.fw.service.ServiceException;
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.model.lookup.AddressChangeSource;
import gov.va.med.esr.common.model.lookup.AddressType;
import gov.va.med.esr.common.model.lookup.BadAddressReason;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.service.LookupService;

public class AddressBuilder extends EntityBuilder
{
    /**
     * An instance of serialVersionUID
     */
    private static final long serialVersionUID = 713793576044907489L;

    /* Address Type - Bad Undeliverable Mailing Address */
    private static final String CODE_BAD_ADDRESS_UNDELIVERABLE = "VAB1";

    /* Address type - Bad Homeless Mailing Address */
    private static final String CODE_BAD_ADDRESS_HOMELESS = "VAB2";

    /* Address Type - Bad Mailing Address - Other */
    private static final String CODE_BAD_ADDRESS_OTHER = "VAB3";

    /* Address Type - Bad Mailing Address - Not Found*/
    private static final String CODE_BAD_ADDRESS_NOT_FOUND = "VAB4";

    /* Zip Code 09033 is associated with a military post office.  The City assigned is APO and the state AE */
    private static final String STATE_AE = "AE";

    private Builder badAddressReasonBuilder;

    private Builder changeSourceBuilder;

    private Builder typeBuilder;

    private LookupService lookupService;

    public AddressBuilder()
    {
        super();
    }

    /**
     * @return Returns the badAddressReasonBuilder.
     */
    public Builder getBadAddressReasonBuilder()
    {
        return badAddressReasonBuilder;
    }

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

    public Builder getChangeSourceBuilder()
    {
        return this.changeSourceBuilder;
    }

    public void setChangeSourceBuilder(Builder changeSourceBuilder)
    {
        this.changeSourceBuilder = changeSourceBuilder;
    }


    public Builder getTypeBuilder()
    {
        return this.typeBuilder;
    }

    public void setTypeBuilder(Builder typeBuilder)
    {
        this.typeBuilder = typeBuilder;
    }

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

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

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

        this.transfer(output, metaData);

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

    private AddressChangeSource buildChangeSource(AddressChangeSource input,
            String code) throws BuilderException
    {
        return (AddressChangeSource) super.build(this.changeSourceBuilder,
                input, code);
    }

    private void buildStateOrProvince(Address input, String state,
            String county, String province) throws BuilderException
    {
        if (province == null)
        {
            //input.setState(super.build(input.getState(), state));
            try
            {
                /*
                 * state code values are too numerous (> 100) to check against
                 * XSD, so be prepared to handle UnknownLookupCodeException
                 */
                input.setState(getStateByStateCode(state));
            } catch (ServiceException e)
            {
                throw new BuilderException("Invalid State Code: " + state, e);
            }

            input.setProvince(null);
        } else
        {
            input.setState(null);
            input.setProvince(super.build(input.getProvince(), province));
        }

        // ignore County value if there is no state!
        if ((input.getState() != null) && !(input.getState().equals(STATE_AE)))
        {
            //input.setCounty(super.build(input.getCounty(), county));
            try
            {
                /*
                 * county code values are too numerous (> 3000) to check against
                 * XSD, so be prepared to handle UnknownLookupCodeException
                 */
                input.setCounty(getCountyByCountyCodeAndStateCode(county, input
                        .getState()));
            } catch (ServiceException e)
            {
                throw new BuilderException(
                        "Invalid County/State Code combination: county["
                                + county + "] and state[" + input.getState()
                                + "]", e);
            }
        }
    }

    private String getStateByStateCode(String stateCode)
            throws ServiceException
    {
        // this is more just verifying that the State Code is valid
        return (StringUtils.isBlank(stateCode)) ? null : this.lookupService
                .getStateByCode(stateCode).getCode();
    }

    private String getCountyByCountyCodeAndStateCode(String countyCode,
            String stateCode) throws ServiceException
    {
        // note County Code + State Code is concatened as "code"
        return (StringUtils.isBlank(countyCode)) ? null : this.lookupService
                .getCountyByCode(countyCode + stateCode).getName();
    }

    private AddressType buildType(AddressType input, String code)
            throws BuilderException
    {
        return (AddressType) super.build(this.typeBuilder, input, code);
    }

    private void buildZipOrPostalCode(Address input, String zipCode,
            String zipPlus4, String postalCode) throws BuilderException
    {
        if (postalCode == null)
        {
            try
            {
                input.setZipCode(super.build(input.getZipCode(), zipCode));
                input.setZipPlus4(super.build(input.getZipPlus4(), zipPlus4));
                input.setPostalCode(null);
            } catch (BuilderException e)
            {
                input.setZipCode(null);
                input.setZipPlus4(null);
                input.setPostalCode(super.build(input.getPostalCode(), zipCode
                        + ((zipPlus4 == null) ? "" : zipPlus4)));
            }
        } else
        {
            input.setZipCode(null);
            input.setZipPlus4(null);
            input.setPostalCode(super.build(input.getPostalCode(), postalCode));
        }
    }

    private boolean shouldKeep(Address obj)
    {
        return  true;
    }

    private void transfer(Address input, AddressMetaData metaData)
            throws BuilderException
    {
        input
                .setEndDate(super.build(input.getEndDate(), metaData
                        .getEndDate()));
        input.setStartDate(super.build(input.getStartDate(), metaData
                .getStartDate()));

        input.setChangeDate(super.build(input.getChangeDate(), metaData
                .getChangeDate()));
        input.setChangeSite(super.build(input.getChangeSite(), metaData
                .getChangeSite()));

        /* Confidential Address does not have a change source. Supposed to get
         * the Source type from the Change facility.(CR 3846).  This will NOT always
         * map to an AddressChangeSource so to alleviate that, it has been
         * confirmed from Laurie S. that "stuffing" it with VAMC will suffice.
         */
        String changeSource = metaData.getChangeSource();
        if (changeSource == null)
        {
            changeSource = AddressChangeSource.CODE_VAMC.getCode();
        }
        input.setChangeSource(this.buildChangeSource(input.getChangeSource(),
                changeSource));

        input.setCity(super.build(input.getCity(), metaData.getCity()));
        input
                .setCountry(super.build(input.getCountry(), metaData
                        .getCountry()));
        input.setLine1(super.build(input.getLine1(), metaData.getLine1()));
        input.setLine2(super.build(input.getLine2(), metaData.getLine2()));
        input.setLine3(super.build(input.getLine3(), metaData.getLine3()));

        input.setType(this.buildType(input.getType(), metaData.getType()));
        input.setBadAddressReason(this.buildBadaddressreason(input
                .getBadAddressReason(), metaData.getType()));

        //A Phone number can exist for a temporary address
        //This will not be a Phone object, just a String to be used for display
        //purposes
        input.setPhoneNumber(super.build(input.getPhoneNumber(), metaData
                .getTemporaryAddressPhoneNumber()));

        this.buildStateOrProvince(input, metaData.getState(), metaData
                .getCounty(), metaData.getProvince());
        this.buildZipOrPostalCode(input, metaData.getZipCode(), metaData
                .getZipPlus4(), metaData.getPostalCode());

        //set active/inactive flag on the temp address zta specific data
        Boolean isAddressActive = super.build(new Boolean(input.isActive()), metaData.getStatus());
        if(isAddressActive != null)
            input.setActive(isAddressActive.booleanValue());

    }

    /**
     * Address Type in message = VAB1 - Set Bad address reason to
     * "UNDELIVERABLE" Address Type in messge = VAB2 - Set Bad address reason to
     * "HOMELESS" Address Type in messge = VAB3 - Set Bad address reason to
     * "OTHER"
     *
     *
     * @param badAddressReason
     * @param type
     * @return
     * @throws BuilderException
     */
    private BadAddressReason buildBadaddressreason(BadAddressReason input,
            String code) throws BuilderException
    {
        BadAddressReason badAddressReason = null;
        //Build a BadAddressReason only if the address type from message is one
        // of these types below
        if (code != null
                && (code.equals(CODE_BAD_ADDRESS_UNDELIVERABLE)
                        || code.equals(CODE_BAD_ADDRESS_HOMELESS)
                        || code.equals(CODE_BAD_ADDRESS_OTHER)
                        || code.equals(CODE_BAD_ADDRESS_NOT_FOUND)))
        {
            badAddressReason = (BadAddressReason) super.build(
                    this.badAddressReasonBuilder, input, code);
        }

        return badAddressReason;
    }

}