package gov.va.med.esr.common.model.party;

import gov.va.med.esr.common.model.lookup.AddressType;
import gov.va.med.esr.common.model.lookup.Country;

import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.util.StringUtils;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.beanutils.PropertyUtils;

import java.util.ArrayList;

/**
 * Simple Address Object to contain common address attributes.
 *
 * @author DNS   MANSOG
 */
public class SimpleAddress extends AbstractKeyedEntity implements Comparable
{
    // ***** Note that if properties are changed, be sure to update the properties in the
    // COMPARABLE_LIST initialization.
    private static final long serialVersionUID = -657131108996597435L;
    private AddressType type;
    private String internalLine1;
    private String internalLine2;
    private String internalLine3;
    private String internalCity;
    private String internalCounty;
    private String internalState;
    private String internalProvince;
    private String internalZipCode;
    private String internalZipPlus4;
    private String internalPostalCode;
    private String internalCountry;
    private Country countryObject;

    private static ArrayList COMPARE_LIST;

    static
    {
        // Comparable criteria in a "mailing oriented" order.
        COMPARE_LIST = new ArrayList();
        COMPARE_LIST.add("internalCountry");
        COMPARE_LIST.add("internalState");
        COMPARE_LIST.add("internalCity");
        COMPARE_LIST.add("internalProvince");
        COMPARE_LIST.add("internalLine1");
        COMPARE_LIST.add("internalLine2");
        COMPARE_LIST.add("internalLine3");
        COMPARE_LIST.add("internalCounty");
        COMPARE_LIST.add("internalZipCode");
        COMPARE_LIST.add("internalZipPlus4");
        COMPARE_LIST.add("internalPostalCode");
    }

    /**
     * Default Constructor.
     */
    public SimpleAddress()
    {
        super();
    }

    /**
     * Returns whether this is a US Address or not.
     *
     * @return True if a US Address or false if not.
     */
    public boolean isUSAddress()
    {
        return Country.isUSAddress(internalCountry);
    }

    public boolean isEmpty()
    {
        return ((StringUtils.isBlank(getLine1())) &&
            (StringUtils.isBlank(getLine2())) &&
            (StringUtils.isBlank(getLine3())) &&
            (StringUtils.isBlank(getCity())) &&
            (StringUtils.isBlank(getCounty())) &&
            (StringUtils.isBlank(getState())) &&
            (StringUtils.isBlank(getProvince())) &&
            (StringUtils.isBlank(getZipCode())) &&
            (StringUtils.isBlank(getZipPlus4())) &&
            (StringUtils.isBlank(getPostalCode())) &&
            (StringUtils.isBlank(getCountry())));
    }
    public boolean isResidential()
    {
        return type != null && AddressType.CODE_RESIDENTIAL_ADDRESS.getCode().equals(type.getCode());
    }
    public boolean isPermanent()
    {
        return type != null && AddressType.CODE_PERMANENT_ADDRESS.getCode().equals(type.getCode());
    }

    public boolean isTemporary()
    {
        return type != null && AddressType.CODE_TEMPORARY_CORRESPONDENCE_ADDRESS.getCode().equals(type.getCode());
    }

    public boolean isConfidential()
    {
        return type != null && AddressType.CODE_CONFIDENTIAL_ADDRESS.getCode().equals(type.getCode());
    }

    /**
     * @see Object#equals(Object)
     */
    public boolean equals(Object object)
    {
        return compareTo(object) == 0;
    }

    /**
     * Comparision method useful for sorting Addresses based on a mailing oriented focus.
     *
     * @param object The other SimpleAddress to compare
     *
     * @return 0, 1, or -1. (see Object.compareTo).
     */
    public int compareTo(Object object)
    {
        // Typecast the other address
        SimpleAddress otherAddress = (SimpleAddress)object;

        try
        {
            // Loop through all the comparable properties and check each one
            for (int i = 0; i < COMPARE_LIST.size(); i++)
            {
                // Get the values for this property from this and the other Address.
                // If the properties are equal, move onto the next property.  Otherwise,
                // return the comparison.
                String property = (String)COMPARE_LIST.get(i);
                String thisValue = (String)PropertyUtils.getProperty(this, property);
                String otherValue = (String)PropertyUtils.getProperty(otherAddress, property);
                if (!StringUtils.isEmpty(thisValue))
                {
                    if (!StringUtils.isEmpty(otherValue))
                    {
                        int comparisonValue = thisValue.compareTo(otherValue);
                        if (comparisonValue != 0)
                        {
                            // Both fields are populated so return which one is greater or less than
                            // the other.
                            return comparisonValue;
                        }
                        // These two fields are equal so loop and try the next field.
                    }
                    else
                    {
                        // Consider our field greater than the other field
                        return 1;
                    }
                }
                else
                {
                    if (!StringUtils.isEmpty(otherValue))
                    {
                        // Consider our field less than the other field
                        return -1;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }

        // All fields matches so return that they are equal
        return 0;
    }

    public AddressType getType()
    {
        return this.type;
    }

    public void setType(AddressType addressType)
    {
        this.type = addressType;
    }

    public String getLine1()
    {
    	return StringUtils.replace((this.internalLine1 != null ? StringEscapeUtils.escapeXml(internalLine1.toUpperCase()) : null), "AMP;", "");
    }

    public void setLine1(String line1)
    {
        if (line1 != null)
        {
            line1 = line1.toUpperCase();
        }
        this.internalLine1 = line1;
    }

    public String getLine2()
    {
    	return StringUtils.replace((this.internalLine2 != null ? StringEscapeUtils.escapeXml(internalLine2.toUpperCase()) : null), "AMP;", "");
    }

    public void setLine2(String line2)
    {
        if (line2 != null)
        {
            line2 = line2.toUpperCase();
        }
        this.internalLine2 = line2;
    }

    public String getLine3()
    {
    	return StringUtils.replace((this.internalLine3 != null ? StringEscapeUtils.escapeXml(internalLine3.toUpperCase()) : null), "AMP;", "");
    }

    public void setLine3(String line3)
    {
        if (line3 != null)
        {
            line3 = line3.toUpperCase();
        }
        this.internalLine3 = line3;
    }

    public String getCity()
    {
    	return StringUtils.replace((this.internalCity != null ? StringEscapeUtils.escapeXml(internalCity.toUpperCase()) : null), "AMP;", "");
    }

    public void setCity(String city)
    {
        if (city != null)
        {
            city = city.toUpperCase();
        }
        this.internalCity = city;
    }

    public String getCountry()
    {
        return internalCountry != null ? StringEscapeUtils.escapeXml(internalCountry.toUpperCase()) : null;
    }

    public void setCountry(String country)
    {
        this.internalCountry = country;
    }

    /**
     * @return The Country lookup object based on the country code value in getCountry method
     */
    public Country getCountryObject()
    {
        return this.countryObject;
    }

    /* Hibernate use */
    public void setCountryObject(Country countryObject)
    {
        this.countryObject = countryObject;
    }

    public String getCounty()
    {
    	return StringUtils.replace((internalCounty != null ? StringEscapeUtils.escapeXml(internalCounty.toUpperCase()) : null), "AMP;", "");

    }

    public void setCounty(String county)
    {

    	if (county != null)
        {
    		county = county.toUpperCase();
        }
        this.internalCounty = county;

    }

    public String getPostalCode()
    {
        return internalPostalCode != null ? StringEscapeUtils.escapeXml(internalPostalCode.toUpperCase()) : null;
    }

    public void setPostalCode(String postalCode)
    {
        if (postalCode != null)
        {
            postalCode = postalCode.toUpperCase();
        }
        this.internalPostalCode = postalCode;
    }

    public String getProvince()
    {
    	return StringUtils.replace((internalProvince != null ? internalProvince.toUpperCase() : null), "AMP;", "");
    }

    public void setProvince(String province)
    {
        if (province != null)
        {
            province = province.toUpperCase();
        }
        this.internalProvince = province;
    }

    public String getState()
    {
    	return StringUtils.replace((internalState != null ? StringEscapeUtils.escapeXml(internalState.toUpperCase()) : null), "AMP;", "");
    }

    public void setState(String state)
    {
    	if (state != null)
        {
            state = state.toUpperCase();
        }
        this.internalState = state;
    }

    public String getZipCode()
    {
        return internalZipCode != null ? StringEscapeUtils.escapeXml(internalZipCode.toUpperCase()) : null;
    }

    public void setZipCode(String zipCode)
    {
        if (zipCode != null)
        {
            zipCode = zipCode.toUpperCase();
        }
        this.internalZipCode = zipCode;
    }

    public String getZipPlus4()
    {
        return internalZipPlus4 != null ? StringEscapeUtils.escapeXml(internalZipPlus4.toUpperCase()) : null;
    }

    public void setZipPlus4(String zipPlus4)
    {
        if (zipPlus4 != null)
        {
            zipPlus4 = zipPlus4.toUpperCase();
        }
        this.internalZipPlus4 = zipPlus4;
    }

    /**
     * Returns a formatted version of this Address
     *
     * @return The formatted Address
     */
    public String getFormattedAddress()
    {
        // Create the result buffer
        StringBuffer resultBuffer = new StringBuffer();

        // Append the first 3 lines
        appendAddressPieceOnNewLine(resultBuffer, getLine1());
        appendAddressPieceOnNewLine(resultBuffer, getLine2());
        appendAddressPieceOnNewLine(resultBuffer, getLine3());

        // Grab the state/province and zip/postal code fields
        String stateProvince = null;
        String zipPostalCode = null;
        if (Country.isUSAddress(getCountry()))
        {
            stateProvince = getState();
            zipPostalCode = getZipCode();
            if ((StringUtils.isNotEmpty(zipPostalCode)) && (StringUtils.isNotEmpty(getZipPlus4())))
            {
                zipPostalCode = zipPostalCode + "-" + getZipPlus4();
            }
        }
        else
        {
            stateProvince = getProvince();
            zipPostalCode = getPostalCode();
        }

        // Create a separate city segment
        StringBuffer citySegmentBuffer = new StringBuffer();

        // Add the city
        appendAddressPieceOnNewLine(citySegmentBuffer, getCity());

        // Add the optional comma after the city
        if ((citySegmentBuffer.length() > 0) &&
            ((StringUtils.isNotEmpty(stateProvince)) || (StringUtils.isNotEmpty(zipPostalCode))))
        {
            // Add a comma after the city if it is present and at least the state/province
            // or zip/postal code is present
            citySegmentBuffer.append(", ");
        }

        // Add the state/province
        if (StringUtils.isNotEmpty(stateProvince))
        {
            citySegmentBuffer.append(stateProvince);

            // If we have a zip/postal code, add a space after the state/province
            if (StringUtils.isNotEmpty(zipPostalCode))
            {
                citySegmentBuffer.append(" ");
            }
        }

        // Add the zip/postal code
        if (StringUtils.isNotEmpty(zipPostalCode))
        {
            citySegmentBuffer.append(zipPostalCode);
        }

        // Append the city segment to the result buffer
        appendAddressPieceOnNewLine(resultBuffer, citySegmentBuffer.toString());

        // Append the country
        if (getCountryObject() != null)
        {
            appendAddressPieceOnNewLine(resultBuffer, getCountryObject().getShortName());
        }

        // Return the formatted result
        return resultBuffer.toString();
    }

    /**
     * Appends a piece of an Address and adds a line break if needed
     *
     * @param buffer The address buffer
     * @param piece The piece to add
     */
    public void appendAddressPieceOnNewLine(StringBuffer buffer, String piece)
    {
        // If there is nothing to append, just return
        if (StringUtils.isEmpty(piece))
        {
            return;
        }

        // If something was already added to the buffer, add a new line
        if ((buffer != null) && (buffer.length() > 0))
        {
            buffer.append("\n");
        }

        // Add this piece of the address
        buffer.append(piece);
    }

    public String getInternalLine1() {
    	return internalLine1;
    }

    private void setInternalLine1(String internalLine1) {
        this.internalLine1 = internalLine1;
    }

    public String getInternalLine2() {
        return internalLine2;
    }

    private void setInternalLine2(String internalLine2) {
        this.internalLine2 = internalLine2;
    }

    public String getInternalLine3() {
        return internalLine3;
    }

    private void setInternalLine3(String internalLine3) {
        this.internalLine3 = internalLine3;
    }

    public String getInternalCity() {
        return internalCity;
    }

    private void setInternalCity(String internalCity) {
        this.internalCity = internalCity;
    }

    public String getInternalCounty() {
    	return internalCounty;
    }

    private void setInternalCounty(String internalCounty) {
        this.internalCounty = internalCounty;
    }

    public String getInternalState() {
        return internalState;
    }

    private void setInternalState(String internalState) {
        this.internalState = internalState;
    }

    public String getInternalZipCode() {
        return internalZipCode;
    }

    private void setInternalZipCode(String internalZipCode) {
        this.internalZipCode = internalZipCode;
    }

    public String getInternalZipPlus4() {
        return internalZipPlus4;
    }

    private void setInternalZipPlus4(String internalZipPlus4) {
        this.internalZipPlus4 = internalZipPlus4;
    }

    public String getInternalProvince() {
        return internalProvince;
    }

    private void setInternalProvince(String internalProvince) {
        this.internalProvince = internalProvince;
    }

    public String getInternalCountry() {
        return internalCountry;
    }

    private void setInternalCountry(String internalCountry) {
        this.internalCountry = internalCountry;
    }

    public String getInternalPostalCode() {
        return internalPostalCode;
    }

    private void setInternalPostalCode(String internalPostalCode) {
        this.internalPostalCode = internalPostalCode;
    }

    /*
     *
     * (non-Javadoc)
     *
     * @see gov.va.med.fw.model.AbstractKeyedEntity#buildToString(org.apache.commons.lang.builder.ToStringBuilder)
     */
    protected void buildToString(ToStringBuilder builder)
    {
        super.buildToString(builder);
        builder.append("addressType", this.type);
        builder.append("line1", this.internalLine1);
        builder.append("line2", this.internalLine2);
        builder.append("line3", this.internalLine3);
        builder.append("city", this.internalCity);
        builder.append("state", this.internalState);
        builder.append("province", this.internalProvince);
        builder.append("postalCode", this.internalPostalCode);
        builder.append("zipCode", this.internalZipCode);
        builder.append("zipPlus4", this.internalZipPlus4);
        builder.append("county", this.internalCounty);
        builder.append("country", this.internalCountry);
    }
}