/***********************************************************
Copyright (C) 2012 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-1307 USA
 ***********************************************************/
package com.verisign.epp.codec.launch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.verisign.epp.codec.gen.EPPCodecComponent;
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.EqualityUtil;

/**
 * Launch application status constants that are returned by
 * {@link EPPLaunchInfData#getStatus()} or set with
 * {@link EPPLaunchInfData#setStatus(EPPLaunchStatus)}.
 */
public class EPPLaunchStatus implements EPPCodecComponent {

  /**
   * Category for logging
   */
      private static Logger cat = LoggerFactory.getLogger(EPPLaunchStatus.class);
        

  /**
   * The initial state of a newly-created application object. The application
   * requires validation, but the validation process has not yet completed.
   */
  public static final String STATUS_PENDING_VALIDATION = "pendingValidation";

  /**
   * The application meets relevant registry rules.
   */
  public static final String STATUS_VALIDATED = "validated";

  /**
   * The application does not validate according to registry rules.
   */
  public static final String STATUS_INVALID = "invalid";

  /**
   * The allocation of the application is pending based on the results of some
   * out-of-band process (for example, an auction).
   */
  public static final String STATUS_PENDING_ALLOCATION = "pendingAllocation";

  /**
   * One of two possible end states of an application object; the object
   * corresponding to the application has been provisioned.
   */
  public static final String STATUS_ALLOCATED = "allocated";

  /**
   * The other possible end state; the object was not provisioned.
   */
  public static final String STATUS_REJECTED = "rejected";

  /**
   * A custom status that is defined using the "name" attribute.
   */
  public static final String STATUS_CUSTOM = "custom";
  
  /** 
   * Default lang value 
   **/
  private static final String DEFAULT_LANG = "en";
  

  /**
   * Constant for the status local name
   */
  public static final String ELM_LOCALNAME = "status";

  /**
   * Constant for the status qualified name (prefix and local name)
   */
  public static final String ELM_NAME = EPPLaunchExtFactory.NS_PREFIX + ":" + ELM_LOCALNAME;

  /**
   * Status attribute key
   */
  public static final String ATTR_STATUS = "s";

  /**
   * OPTIONAL attribute name that can define a sub-status or the full name of
   * the status when the &lt;launch:status&gt; element has the "custom" value.
   */
  public static final String ATTR_NAME = "name";

  /**
   * XML local name for the name lang attribute.
   */
  private static final String ATTR_LANG = "lang";

  /**
   * Launch status name using using of the {@code STATUS} constants.
   */
  private String status;

  /**
   * OPTIONAL "name" attribute that can define a sub-status or the full name of
   * the status when the &lt;launch:status&gt; element has the "custom" value.
   */
  private String name;

  /**
   * human-readable text that describes the rationale for the status applied to
   * the object.
   */
  private String description = null;

  /**
   * OPTIONAL language of the description.
   */
  private String descriptionLang = null;

  /**
   * Default constructor. The status value MUST be set using the
   * {@link #setStatus(String)} method.
   */
  public EPPLaunchStatus() {
  }

  /**
   * Create {@code EPPLaunchStatus} instance with a defined status value.
   * 
   * @param aStatus
   *           Status value using one of the {@code STATUS} constants.
   */
  public EPPLaunchStatus(String aStatus) {
    this.status = aStatus;
  }

  /**
   * Create {@code EPPLaunchStatus} instance with a defined status value and
   * with the status name value.
   * 
   * @param aStatus
   *           Status value using one of the {@code STATUS} constants.
   * @param aName
   *           Name of sub-status or full name of status when {@code aStatus}
   *           is set to {@code STATUS_CUSTOM}.
   */
  public EPPLaunchStatus(String aStatus, String aName) {
    this.status = aStatus;
    this.name = aName;
  }

  /**
   * Create {@code EPPLaunchStatus} instance with all attributes.
   * 
   * @param aStatus
   *           Status value using one of the {@code STATUS} constants.
   * @param aName
   *           Name of sub-status or full name of status when {@code aStatus}
   *           is set to {@code STATUS_CUSTOM}.
   * @param aDescription
   *           human-readable text that describes the rationale for the status
   *           applied to the object.
   * @param aDescriptionLang
   *           The OPTIONAL "lang" attribute, as defined in [RFC5646], MAY be
   *           present to identify the language if the negotiated value is
   *           something other than the default value of "en" (English).
   */
  public EPPLaunchStatus(String aStatus, String aName, String aDescription, String aDescriptionLang) {
    this.status = aStatus;
    this.name = aName;
    this.description = aDescription;
    this.setDescriptionLang(aDescriptionLang);
  }

  /**
   * Gets the status value, which should match one of the {@code STATUS}
   * constants.
   * 
   * @return Status value
   */
  public String getStatus() {
    return this.status;
  }

  /**
   * Sets the status value.
   * 
   * @param aStatus
   *           Status value, which should be one of the {@code STATUS}
   *           constants.
   */
  public void setStatus(String aStatus) {
    this.status = aStatus;
  }

  /**
   * Gets the OPTIONAL status name or sub-status name.
   * 
   * @return The status name or sub-status name if defined; {@code null}
   *         otherwise.
   */
  public String getName() {
    return this.name;
  }

  /**
   * Sets the OPTIONAL status name or sub-status name.
   * 
   * @param aName
   *           Status name
   */
  public void setName(String aName) {
    this.name = aName;
  }

  /**
   * Is the description defined?
   *
   * @return {@code true} if the description is defined; {@code false}
   *         otherwise.
   */
  public boolean hasDescription() {
    return (this.description != null ? true : false);
  }

  /**
   * Gets the free-form description of the rationale for the status.
   * 
   * @return The free-form description of the rationale for the status if
   *         defined; {@code null} otherwise.
   */
  public String getDescription() {
    return this.description;
  }

  /**
   * Sets the free-form description of the rationale for the status.
   * 
   * @param aDescription
   *           Free-form description of the rationale for the status.
   */
  public void setDescription(String aDescription) {
    this.description = aDescription;
  }

  /**
   * Is the description language defined with a non-default value??
   * 
   * @return <code>true</code> if the description language is defined;
   *         <code>false</code> otherwise.
   */
  public boolean hasDescriptionLang() {
    return (!this.descriptionLang.equals(DEFAULT_LANG) ? true : false);
  }

  /**
   * Gets the description language value.
   * 
   * @return Description language if defined; <code>DEFAULT_LANG</code>
   *         otherwise.
   */
  public String getDescriptionLang() {
    return this.descriptionLang;
  }

  /**
   * Sets the description language value.
   * 
   * @param aDescriptionLang
   *           Description language for reason value.
   */
  public void setDescriptionLang(String aDescriptionLang) {
    if (aDescriptionLang == null) {
      this.descriptionLang = DEFAULT_LANG;
    }
    else {
      this.descriptionLang = aDescriptionLang;
    }
  }

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

    clone = (EPPLaunchStatus) super.clone();

    return clone;
  }

  /**
   * Decode the {@code EPPLaunchStatus} element aElement DOM Element tree.
   * 
   * @param aElement
   *           - Root DOM Element to decode {@code EPPLaunchStatus} from.
   * 
   * @exception EPPDecodeException
   *               Unable to decode aElement
   */
  public void decode(Element aElement) throws EPPDecodeException {
    // status
    this.status = aElement.getAttribute(ATTR_STATUS);

    // name
    String theName = aElement.getAttribute(ATTR_NAME);
    if (theName != null && !theName.isEmpty()) {
      this.name = theName;
    }
    else {
      this.name = null;
    }

    // description
    Node textNode = aElement.getFirstChild();
    if (textNode != null) {
      this.description = textNode.getNodeValue();

      this.setDescriptionLang(EPPUtil.decodeStringAttr(aElement, ATTR_LANG));
    }

  }

  /**
   * Encode a DOM Element tree from the attributes of the
   * {@code EPPLaunchStatus} instance.
   * 
   * @param aDocument
   *           - DOM Document that is being built. Used as an Element factory.
   * 
   * @return Element - Root DOM Element representing the
   *         {@code EPPLaunchStatus} instance.
   * 
   * @exception EPPEncodeException
   *               - Unable to encode {@code EPPLaunchStatus} instance.
   */
  public Element encode(Document aDocument) throws EPPEncodeException {

    if (aDocument == null) {
      throw new EPPEncodeException("aDocument is null" + " on in EPPLaunchStatus.encode(Document)");
    }
    if (this.status == null) {
      throw new EPPEncodeException("status is null" + " on in EPPLaunchStatus.encode(Document)");
    }

    // status
    Element root = aDocument.createElementNS(EPPLaunchExtFactory.NS, ELM_NAME);
    root.setAttribute(ATTR_STATUS, this.status);

    // name
    if (this.name != null) {
      root.setAttribute(ATTR_NAME, this.name);
    }

    // description
    if (this.description != null) {
      root.appendChild(aDocument.createTextNode(this.description));

      if (this.hasDescriptionLang()) {
        root.setAttribute(ATTR_LANG, this.descriptionLang);
      }
    }

    return root;
  }

  /**
   * implements a deep {@code EPPLaunchStatus} compare.
   * 
   * @param aObject
   *           {@code EPPLaunchStatus} instance to compare with
   * 
   * @return {@code true} if equal; {@code false} otherwise
   */
  public boolean equals(Object aObject) {
    if (!(aObject instanceof EPPLaunchStatus)) {
      return false;
    }

    EPPLaunchStatus other = (EPPLaunchStatus) aObject;

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

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

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

    // description language
    if (!EqualityUtil.equals(this.descriptionLang, other.descriptionLang)) {
      cat.error("EPPLaunchStatus.equals(): descriptionLang not equal");
      return false;
    }

    return true;
  }

  /**
   * Implementation of {@code Object.toString}, which will result in an
   * indented XML {@code String} representation of the concrete
   * {@code EPPCodecComponent}.
   * 
   * @return Indented XML {@code String} if successful; {@code ERROR}
   *         otherwise.
   */
  public String toString() {
    return EPPUtil.toString(this);
  }

  /**
   * Returns the XML namespace associated with the {@code EPPCodecComponent}.
   * 
   * @return XML namespace for the {@code EPPCodecComponent}.
   */
  @Override
  public String getNamespace() {
    return EPPLaunchExtFactory.NS;
  }
}
