/***********************************************************
Copyright (C) 2018 VeriSign, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-0107  USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/
package com.verisign.epp.codec.org;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.verisign.epp.codec.gen.EPPCodecComponent;
import com.verisign.epp.codec.gen.EPPCodecException;
import com.verisign.epp.codec.gen.EPPDecodeException;
import com.verisign.epp.codec.gen.EPPEncodeException;
import com.verisign.epp.codec.gen.EPPUtil;
import com.verisign.epp.util.EPPCatFactory;
import com.verisign.epp.util.EqualityUtil;

/**
 * Represents a org address. Every org has associated postal address
 * information. A postal address contains OPTIONAL street information, city
 * information, OPTIONAL state/province information, an OPTIONAL postal code,
 * and a country identifier as described in [ISO11180]. Address information MAY
 * be provided in both a subset of UTF-8 [RFC2279] that can be represented in
 * 7-bit ASCII [US-ASCII] and unrestricted UTF-8. A org address is defined
 * as the following:<br>
 * <br>
 * A &lt;org:addr&gt; element that contains address information associated
 * with the org. A &lt;org:addr&gt; element contains the following
 * child elements:<br>
 * <br>
 * 
 * <ul>
 * <li>
 * OPTIONAL &lt;org:street&gt; elements (up to a maximum of three) that
 * contain the org's street address. Use {@link #getStreets()} and
 * {@link #setStreets(List)} to get and set the elements.</li>
 * <li>
 * A &lt;org:city&gt; element that contains the org's city. Use
 * {@link #getCity()} and {@link #setCity(String)} to get and set the element.</li>
 * <li>
 * A &lt;org:sp&gt; element that contains the org's state or province.
 * This element is OPTIONAL for addressing schemes that do not require a state
 * or province name. Use {@link #getStateProvince()} and
 * {@link #setStateProvince(String)} to get and set the element.</li>
 * <li>
 * An OPTIONAL &lt;org:pc&gt; element that contains the org's postal
 * code. Use {@link #getPostalCode()} and {@link #setPostalCode(String)} to get
 * and set the element.</li>
 * <li>
 * A &lt;org:cc&gt; element that contains the two-character identifier
 * representing with the org's country. Use {@link #getCountry()} and
 * {@link #setCountry(String)} to get and set the element.</li>
 * </ul>
 */
public class EPPOrgAddress implements EPPCodecComponent {

	/**
	 * Log4j category for logging
	 */
	private static Logger cat = Logger.getLogger(EPPOrgAddress.class
			.getName(), EPPCatFactory.getInstance().getFactory());

	/**
	 * XML local name for <code>EPPOrgDisclose</code>.
	 */
	public static final String ELM_LOCALNAME = "addr";

	/**
	 * XML root tag for <code>EPPOrgPostalDefinition</code>.
	 */
	public static final String ELM_NAME = EPPOrgMapFactory.NS_PREFIX + ":"
			+ ELM_LOCALNAME;

	/**
	 * XML tag name for an streets attribute.
	 */
	private final static String ELM_STREET = "street";

	/**
	 * XML tag name for an city attribute.
	 */
	private final static String ELM_CITY = "city";

	/**
	 * XML tag name for an stateProvince attribute.
	 */
	private final static String ELM_STATE_PROVINCE = "sp";

	/**
	 * XML tag name for an postalCode attribute.
	 */
	private final static String ELM_POSTAL_CODE = "pc";

	/**
	 * XML tag name for an country attribute.
	 */
	private final static String ELM_COUNTRY = "cc";

	/**
	 * XML tag name for an streets attribute.
	 */
	private final static int MAX_STREET = 3;

	/**
	 * Org street, which is a <code>List</code> of up to 3
	 * <code>String</code>'s representing street line 1, line 2, and line 3.
	 */
	private List<String> streets = new ArrayList<String>();

	/**
	 * Org city.
	 */
	private String city;

	/**
	 * Org state/province.
	 */
	private String stateProvince;

	/**
	 * Org postal code
	 */
	private String postalCode;

	/**
	 * Contact country
	 */
	private String country;

	/**
	 * Default constructor for <code>EPPOrgAddress</code>. All the the
	 * attributes default to <code>null</code>. Must call required setter
	 * methods before invoking {@link #encode(Document)}, which include:<br>
	 * <br>
	 * 
	 * <ul>
	 * <li>
	 * City - {@link #setCity(String)}</li>
	 * <li>
	 * Country - {@link #setCountry(String)}</li>
	 * </ul>
	 */
	public EPPOrgAddress() {
	}

	/**
	 * Constructor for <code>EPPOrgAddress</code> all of the required
	 * attributes as parameters.
	 *
	 * @param aCity
	 *            Org street
	 * @param aCountry
	 *            Org country
	 */
	public EPPOrgAddress(String aCity, String aCountry) {
		this.city = aCity;
		this.country = aCountry;
	}

	/**
	 * Constructor for <code>EPPOrgAddress</code> all of the attributes as
	 * parameters.
	 *
	 * @param aStreets
	 *            <code>List&lt;String&gt;</code> collection of streets (up to
	 *            maximum three)
	 * @param aCity
	 *            Org street
	 * @param aStateProvince
	 *            Org state/province
	 * @param aPostalCode
	 *            Org postal code
	 * @param aCountry
	 *            Org country
	 */
	public EPPOrgAddress(List<String> aStreets, String aCity,
			String aStateProvince, String aPostalCode, String aCountry) {
		this.streets = aStreets;
		this.city = aCity;
		this.stateProvince = aStateProvince;
		this.postalCode = aPostalCode;
		this.country = aCountry;
	}

	/**
	 * Is there any street lines set?
	 * 
	 * @return <code>true</code> if there is at least one street line set.
	 */
	public boolean hasStreets() {
		if (this.streets != null && !this.streets.isEmpty()) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Add a street line to the street.  This will add <code>aStreetLine</code> 
	 * to the list of street lines.
	 * 
	 * @param aStreetLine Street line to add to the street
	 */
	public void addStreet(String aStreetLine) {
		this.streets.add(aStreetLine);
	}
	
	/**
	 * Gets the org street(s).
	 *
	 * @return street(s) as a <code>List&lt;String&gt;</code> of streets (up to
	 *         maximum three).
	 */
	public List<String> getStreets() {
		return streets;
	}

	/**
	 * Sets the org streets attribute with a
	 * <code>List&gt;String&gt;</code>, where each element represents a line of
	 * the street address.
	 *
	 * @param aStreets
	 *            Up to 3 street elements
	 */
	public void setStreets(List<String> aStreets) {
		this.streets = aStreets;
	}

	/**
	 * Sets the org streets with only one line. Only a one element
	 * <code>List&lt;String&gt;</code> will be returned on a call to
	 * {@link #getStreets()} after calling this method.
	 *
	 * @param aStreet
	 *            Org street.
	 */
	public void setStreet(String aStreet) {
		this.streets = new ArrayList<String>();
		this.streets.add(aStreet);
	}

	/**
	 * Sets the org street with two street lines.
	 *
	 * @param aStreet1
	 *            First street line
	 * @param aStreet2
	 *            Second street line
	 */
	public void setStreets(String aStreet1, String aStreet2) {
		this.streets = new ArrayList<String>();

		this.streets.add(aStreet1);
		this.streets.add(aStreet2);
	}

	/**
	 * Sets the org street with three street lines.
	 *
	 * @param aStreet1
	 *            First street line
	 * @param aStreet2
	 *            Second street line
	 * @param aStreet3
	 *            Third street line
	 */
	public void setStreets(String aStreet1, String aStreet2, String aStreet3) {
		this.streets = new ArrayList<String>();

		this.streets.add(aStreet1);
		this.streets.add(aStreet2);
		this.streets.add(aStreet3);
	}

	/**
	 * Gets the org city.
	 *
	 * @return city. <code>String</code> if defined; <code>null</code>
	 *         otherwise.
	 */
	public String getCity() {
		return this.city;
	}

	/**
	 * Sets the org city.
	 *
	 * @param aCity
	 *            org city
	 */
	public void setCity(String aCity) {
		this.city = aCity;
	}

	/**
	 * Gets the org state/province.
	 *
	 * @return state/province. <code>String</code> if defined; <code>null</code>
	 *         otherwise.
	 */
	public String getStateProvince() {
		return this.stateProvince;
	}

	/**
	 * Sets the org state/province.
	 *
	 * @param aStateProvince
	 *            org state/province
	 */
	public void setStateProvince(String aStateProvince) {
		this.stateProvince = aStateProvince;
	}

	/**
	 * Gets the org postal code
	 *
	 * @return postal code <code>String</code> if defined; <code>null</code>
	 *         otherwise.
	 */
	public String getPostalCode() {
		return this.postalCode;
	}

	/**
	 * Sets the org postal code
	 *
	 * @param aPostalCode
	 *            org postal code
	 */
	public void setPostalCode(String aPostalCode) {
		this.postalCode = aPostalCode;
	}

	/**
	 * Gets the org country.
	 *
	 * @return org country <code>String</code> if defined;
	 *         <code>null</code> otherwise.
	 */
	public String getCountry() {
		return this.country;
	}

	/**
	 * Sets the org country.
	 *
	 * @param aCountry
	 *            org country
	 */
	public void setCountry(String aCountry) {
		this.country = aCountry;
	}

	/**
	 * Validate the state of the <code>EPPOrgAddress</code> instance. A
	 * valid state means that all of the required attributes have been set. If
	 * validateState returns without an exception, the state is valid. If the
	 * state is not valid, the EPPCodecException will contain a description of
	 * the error. throws EPPCodecException State error. This will contain the
	 * name of the attribute that is not valid.
	 *
	 * @throws EPPCodecException
	 *             On validation error
	 */
	void validateState() throws EPPCodecException {

		if (this.streets != null) {
			if (this.streets.contains(null))
				throw new EPPCodecException(
						"street lines cannot be set to null");
		}

		if ((this.streets != null) && !this.streets.isEmpty()
				&& (this.streets.size() > MAX_STREET)) {
			throw new EPPCodecException("street lines exceed the maximum");
		}

		if (this.city == null) {
			throw new EPPCodecException("city required attribute is not set");
		}

		if (this.country == null) {
			throw new EPPCodecException("country required attribute is not set");
		}
	}

	/**
	 * Encode a DOM Element tree from the attributes of the
	 * <code>EPPOrgAddress</code> instance.
	 *
	 * @param aDocument
	 *            DOM Document that is being built. Used as an Element factory.
	 *
	 * @return Root DOM Element representing the <code>EPPOrgAddress</code>
	 *         instance.
	 *
	 * @exception EPPEncodeException
	 *                Unable to encode <code>EPPOrgAddress</code> instance.
	 */
	public Element encode(Document aDocument) throws EPPEncodeException {
		try {
			validateState();
		}
		catch (EPPCodecException e) {
			throw new EPPEncodeException(
					"Invalid state on EPPOrgAddress.encode: " + e);
		}

		// Create root element
		Element root = aDocument.createElementNS(EPPOrgMapFactory.NS,
				ELM_NAME);

		// Street
		EPPUtil.encodeStringList(aDocument, root, this.streets,
				EPPOrgMapFactory.NS, EPPOrgMapFactory.NS_PREFIX + ":"
						+ ELM_STREET);

		// City
		EPPUtil.encodeString(aDocument, root, this.city,
				EPPOrgMapFactory.NS, EPPOrgMapFactory.NS_PREFIX + ":"
						+ ELM_CITY);

		// State/Province
		EPPUtil.encodeString(aDocument, root, this.stateProvince,
				EPPOrgMapFactory.NS, EPPOrgMapFactory.NS_PREFIX + ":"
						+ ELM_STATE_PROVINCE);

		// Postal Code
		EPPUtil.encodeString(aDocument, root, this.postalCode,
				EPPOrgMapFactory.NS, EPPOrgMapFactory.NS_PREFIX + ":"
						+ ELM_POSTAL_CODE);

		// Country
		EPPUtil.encodeString(aDocument, root, this.country,
				EPPOrgMapFactory.NS, EPPOrgMapFactory.NS_PREFIX + ":"
						+ ELM_COUNTRY);

		return root;
	}

	/**
	 * Decode the <code>EPPOrgAddress</code> attributes from the aElement
	 * DOM Element tree.
	 *
	 * @param aElement
	 *            Root DOM Element to decode <code>EPPOrgAddress</code>
	 *            from.
	 *
	 * @exception EPPDecodeException
	 *                Unable to decode aElement.
	 */
	public void decode(Element aElement) throws EPPDecodeException {

		// Street
		this.streets = (List<String>) EPPUtil.decodeList(aElement,
				EPPOrgMapFactory.NS, ELM_STREET);

		// City
		this.city = EPPUtil.decodeString(aElement, EPPOrgMapFactory.NS,
				ELM_CITY);

		// State/Province
		this.stateProvince = EPPUtil.decodeString(aElement,
				EPPOrgMapFactory.NS, ELM_STATE_PROVINCE);

		// Postal Code
		this.postalCode = EPPUtil.decodeString(aElement,
				EPPOrgMapFactory.NS, ELM_POSTAL_CODE);

		// Country
		this.country = EPPUtil.decodeString(aElement, EPPOrgMapFactory.NS,
				ELM_COUNTRY);
	}

	/**
	 * implements a deep <code>EPPOrgAddress</code> compare.
	 *
	 * @param aObject
	 *            <code>EPPOrgAddress</code> instance to compare with
	 *
	 * @return <code>true</code> of <code>aObject</code> is equal to instance;
	 *         <code>false</code> otherwise.
	 */
	public boolean equals(Object aObject) {

		if (!(aObject instanceof EPPOrgAddress)) {
			return false;
		}

		EPPOrgAddress other = (EPPOrgAddress) aObject;

		// Street
		if (!EPPUtil.equalLists(this.streets, other.streets)) {
			cat.error("EPPOrgAddress.equals(): streets not equal");
			return false;
		}

		// City
		if (!EqualityUtil.equals(this.city, other.city)) {
			cat.error("EPPOrgAddress.equals(): city not equal");
			return false;
		}

		// State/Province
		if (!EqualityUtil.equals(this.stateProvince, other.stateProvince)) {
			cat.error("EPPOrgAddress.equals(): stateProvince not equal");
			return false;
		}

		// Postal Code
		if (!EqualityUtil.equals(this.postalCode, other.postalCode)) {
			cat.error("EPPOrgAddress.equals(): postalCode not equal");
			return false;
		}

		// Country
		if (!EqualityUtil.equals(this.country, other.country)) {
			cat.error("EPPOrgAddress.equals(): country not equal");
			return false;
		}

		return true;
	}

	/**
	 * Clone <code>EPPOrgAddress</code>.
	 *
	 * @return clone of <code>EPPOrgAddress</code>
	 *
	 * @exception CloneNotSupportedException
	 *                standard Object.clone exception
	 */
	public Object clone() throws CloneNotSupportedException {
		EPPOrgAddress clone = null;

		clone = (EPPOrgAddress) super.clone();

		if (this.hasStreets()) {
			clone.streets = (List) ((ArrayList) this.streets).clone();
		}

		return clone;
	}

	/**
	 * Implementation of <code>Object.toString</code>, which will result in an
	 * indented XML <code>String</code> representation of the concrete
	 * <code>EPPCodecComponent</code>.
	 *
	 * @return Indented XML <code>String</code> if successful;
	 *         <code>ERROR</code> otherwise.
	 */
	public String toString() {
		return EPPUtil.toString(this);
	}
	
	/**
	 * Returns the XML namespace associated with the <code>EPPCodecComponent</code>.
	 * 
	 * @return XML namespace for the <code>EPPCodecComponent</code>. 
	 */
	@Override
	public String getNamespace() {
		return EPPOrgMapFactory.NS;
	}
	
}
