/***********************************************************
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.registry.v02;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.EqualityUtil;

/**
 * This class is encoded to the &lt;registry:domainName&gt; element inside the
 * &lt;registry:domain&gt; element. It represents policies for a domain name
 * label for a specific level, defined with the "level" attribute, with a
 * minimum value of "2" for the second level domain name label level. The
 * &lt;registry:domainName&gt; element contains the following child elements
 * <br>
 * <br>
 *
 * <ul>
 * <li>&lt;registry:minLength&gt; - An OPTIONAL minimum length of the domain
 * name label. Use {@link #getMinLength()} and {@link #setMinLength(Integer)} to
 * get and set the element.</li>
 * <li>&lt;registry:maxLength&gt; - An OPTIONAL maximum length of the domain
 * name label. Use {@link #getMaxLength()} and {@link #setMaxLength(Integer)} to
 * get and set the element.</li>
 * <li>&lt;registry:alphaNumStart&gt; - An OPTIONAL flag indicating whether the
 * label must start with an alphanumeric character with a default of "false".
 * Use {@link #getAlphaNumStart()} and {@link #setAlphaNumStart(Boolean)} to get
 * and set the element.</li>
 * <li>&lt;registry:alphaNumEnd&gt; - An OPTIONAL flag indicating whether the
 * label must end with an alphanumeric character with a default value of
 * "false". Use {@link #getAlphaNumEnd()} and {@link #setAlphaNumEnd(Boolean)}
 * to get and set the element.</li>
 * <li>&lt;registry:aLabelSupported&gt; - An OPTIONAL flag indicating whether
 * ASCII domain names are supported with a default value of "true". Use
 * {@link #getALabelSupported()} and {@link #setALabelSupported(Boolean)} to get
 * and set the element.</li>
 * <li>&lt;registry:uLabelSupported&gt; - An OPTIONAL flag indicating whether
 * non-ASCII domain names are supported with a default value of "false". Use
 * {@link #getULabelSupported()} and {@link #setULabelSupported(Boolean)} to get
 * and set the element.</li>
 * <li>&lt;registry:nameRegex&gt; - The OPTIONAL regular expression used to
 * validate the domain name label. Use {@link #getNameRegex()} and
 * {@link #setNameRegex(EPPRegistryRegex)} to get and set the element.</li>
 * <li>&lt;registry:reservedNames&gt; - An OPTIONAL element that defines the set
 * of reserved domain names starting from that label level. The reserved names
 * can refer to values with more than one level which is relative to the level
 * of the parent &lt;registry:domainName&gt; element. Use
 * {@link #getReservedNames()} and
 * {@link #setReservedNames(EPPRegistryReservedNames)} to get and set the
 * element.</li>
 * </ul>
 *
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomain
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryReservedNames
 */
public class EPPRegistryDomainName implements EPPCodecComponent {

  /**
   * Logger
   */
      private static Logger cat = LoggerFactory.getLogger(EPPRegistryDomainName.class);
        

  /**
   * XML local name for {@code EPPRegistryDomainName}.
   */
  public static final String ELM_LOCALNAME = "domainName";

  /**
   * XML root tag for {@code EPPRegistryDomainName}.
   */
  public static final String ELM_NAME = EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_LOCALNAME;

  /**
   * XML tag name for the {@code minLength} attribute.
   */
  final static String ELM_MIN_LENGTH = "minLength";

  /**
   * XML tag name for the {@code maxLength} attribute.
   */
  final static String ELM_MAX_LENGTH = "maxLength";

  /**
   * XML tag name for the {@code alphaNumStart} attribute.
   */
  final static String ELM_ALPHA_NUM_START = "alphaNumStart";

  /**
   * XML tag name for the {@code alphaNumEnd} attribute.
   */
  final static String ELM_ALPHA_NUM_END = "alphaNumEnd";

  /**
   * XML tag name for the {@code aLabelSupported} attribute.
   */
  final static String ELM_ALABEL_SUPPORTED = "aLabelSupported";

  /**
   * XML tag name for the {@code uLabelSupported} attribute.
   */
  final static String ELM_ULABEL_SUPPORTED = "uLabelSupported";

  /**
   * XML tag name for the {@code nameRegex} attribute.
   */
  final static String ELM_NAME_REGEX = "nameRegex";

  /**
   * XML attribute name for the {@code level} attribute.
   */
  final static String ATTR_LEVEL = "level";

  /**
   * Level of domain name. Value should be &gt;= 2
   */
  private Integer level = null;

  /**
   * Minimum number of characters in a domain name
   */
  private Integer minLength = null;

  /**
   * Maximum number of characters in a domain name
   */
  private Integer maxLength = null;

  /**
   * Whether or not to allow domain name start with an alphanumeric character
   */
  private Boolean alphaNumStart = Boolean.FALSE;

  /**
   * Whether or not to allow domain name end with an alphanumeric character
   */
  private Boolean alphaNumEnd = Boolean.FALSE;

  /**
   * Flag indicating whether ASCII domain names are supported with a default
   * value of "true".
   */
  private Boolean aLabelSupported = Boolean.TRUE;

  /**
   * Flag indicating whether non-ASCII domain names are supported with a
   * default value of "false".
   */
  private Boolean uLabelSupported = Boolean.TRUE;

  /**
   * Regular expression that the domain name must follow.
   */
  private EPPRegistryRegex nameRegex = null;

  /** Defines a set of reserved domain names */
  private EPPRegistryReservedNames reservedNames = null;

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

    Element root = aDocument.createElementNS(EPPRegistryMapFactory.NS, ELM_NAME);

    // Level
    root.setAttribute(ATTR_LEVEL, this.level.toString());

    // Min Length
    if (this.minLength != null) {
      EPPUtil.encodeString(aDocument, root, this.minLength.toString(), EPPRegistryMapFactory.NS,
            EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_MIN_LENGTH);
    }

    // Max Length
    if (this.maxLength != null) {
      EPPUtil.encodeString(aDocument, root, this.maxLength.toString(), EPPRegistryMapFactory.NS,
            EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_MAX_LENGTH);
    }

    // Alpha Num Start
    if (this.alphaNumStart == null) {
      this.alphaNumStart = Boolean.FALSE;
    }
    EPPUtil.encodeString(aDocument, root, this.alphaNumStart.toString(), EPPRegistryMapFactory.NS,
          EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_ALPHA_NUM_START);

    // Alpha Num End
    if (this.alphaNumEnd == null) {
      this.alphaNumEnd = Boolean.FALSE;
    }
    EPPUtil.encodeString(aDocument, root, this.alphaNumEnd.toString(), EPPRegistryMapFactory.NS,
          EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_ALPHA_NUM_END);

    // aLabel Supported
    if (this.aLabelSupported == null) {
      this.aLabelSupported = Boolean.TRUE;
    }
    EPPUtil.encodeString(aDocument, root, this.aLabelSupported.toString(), EPPRegistryMapFactory.NS,
          EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_ALABEL_SUPPORTED);

    // uLabel Supported
    if (this.uLabelSupported == null) {
      this.uLabelSupported = Boolean.FALSE;
    }
    EPPUtil.encodeString(aDocument, root, this.uLabelSupported.toString(), EPPRegistryMapFactory.NS,
          EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_ULABEL_SUPPORTED);

    // Name Regular Expression
    EPPUtil.encodeComp(aDocument, root, this.nameRegex);

    // Reserved Names
    if (this.reservedNames != null) {
      EPPUtil.encodeComp(aDocument, root, this.reservedNames);
    }

    return root;
  }

  /**
   * Decode the {@code EPPRegistryDomainName} attributes from the aElement DOM
   * Element tree.
   *
   * @param aElement
   *           Root DOM Element to decode {@code EPPRegistryDomainName} from.
   *
   * @exception EPPDecodeException
   *               Unable to decode aElement
   */
  @Override
  public void decode(Element aElement) throws EPPDecodeException {
    // Reserved Names
    String levelStr = null;
    try {
      levelStr = aElement.getAttribute(ATTR_LEVEL);
      this.level = Integer.valueOf(levelStr);
    }
    catch (NumberFormatException e) {
      throw new EPPDecodeException("Cannot decode level: " + levelStr + "." + e);
    }

    // Min Length
    this.minLength = EPPUtil.decodeInteger(aElement, EPPRegistryMapFactory.NS, ELM_MIN_LENGTH);

    // Max Length
    this.maxLength = EPPUtil.decodeInteger(aElement, EPPRegistryMapFactory.NS, ELM_MAX_LENGTH);

    // Alpha Num Start
    this.alphaNumStart = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS, ELM_ALPHA_NUM_START);
    if (this.alphaNumStart == null) {
      this.alphaNumStart = Boolean.FALSE;
    }

    // Alpha Num End
    this.alphaNumEnd = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS, ELM_ALPHA_NUM_END);
    if (this.alphaNumEnd == null) {
      this.alphaNumEnd = Boolean.FALSE;
    }

    // aLabel Supported
    this.aLabelSupported = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS, ELM_ALABEL_SUPPORTED);
    if (this.aLabelSupported == null) {
      this.aLabelSupported = Boolean.TRUE;
    }

    // Name Regular Expression
    this.setNameRegex((EPPRegistryRegex) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS, ELM_NAME_REGEX,
          EPPRegistryRegex.class));

    // Reserved Names
    this.reservedNames = (EPPRegistryReservedNames) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
          EPPRegistryReservedNames.ELM_NAME, EPPRegistryReservedNames.class);

    try {
      validateState();
    }
    catch (EPPCodecException e) {
      throw new EPPDecodeException("Invalid state on EPPRegistryDomainName.decode: " + e);
    }
  }

  /**
   * implements a deep {@code EPPRegistryDomainName} compare.
   *
   * @param aObject
   *           {@code EPPRegistryDomainName} instance to compare with
   *
   * @return {@code true} if this object is the same as the aObject argument;
   *         {@code false} otherwise
   */
  @Override
  public boolean equals(Object aObject) {
    if (!(aObject instanceof EPPRegistryDomainName)) {
      return false;
    }

    // Level
    EPPRegistryDomainName theComp = (EPPRegistryDomainName) aObject;
    if (!EqualityUtil.equals(this.level, theComp.level)) {
      cat.error("EPPRegistryDomainName.equals(): level not equal");
      return false;
    }

    // Min Length
    if (!EqualityUtil.equals(this.minLength, theComp.minLength)) {
      cat.error("EPPRegistryDomainName.equals(): minLength not equal");
      return false;
    }

    // Max Length
    if (!EqualityUtil.equals(this.maxLength, theComp.maxLength)) {
      cat.error("EPPRegistryDomainName.equals(): maxLength not equal");
      return false;
    }

    // Alpha Num Start
    if (!EqualityUtil.equals(this.alphaNumStart, theComp.alphaNumStart)) {
      cat.error("EPPRegistryDomainName.equals(): alphaNumStart not equal");
      return false;
    }

    // Alpha Num End
    if (!EqualityUtil.equals(this.alphaNumEnd, theComp.alphaNumEnd)) {
      cat.error("EPPRegistryDomainName.equals(): alphaNumEnd not equal");
      return false;
    }

    // aLabel Supported
    if (!EqualityUtil.equals(this.aLabelSupported, theComp.aLabelSupported)) {
      cat.error("EPPRegistryDomainName.equals(): aLabelSupported not equal");
      return false;
    }

    // uLabel Supported
    if (!EqualityUtil.equals(this.uLabelSupported, theComp.uLabelSupported)) {
      cat.error("EPPRegistryDomainName.equals(): uLabelSupported not equal");
      return false;
    }

    // Name Regular Expression
    if (!EqualityUtil.equals(this.nameRegex, theComp.nameRegex)) {
      cat.error("EPPRegistryDomainName.equals(): nameRegex not equal");
      return false;
    }

    // Reserved Names
    if (!EqualityUtil.equals(this.reservedNames, theComp.reservedNames)) {
      cat.error("EPPRegistryDomainName.equals(): reservedNames not equal");
      return false;
    }

    return true;
  }

  /**
   * Validate the state of the {@code EPPRegistryDomainName} 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
   */
  void validateState() throws EPPCodecException {
    if (this.level == null || this.level.intValue() < 2) {
      throw new EPPCodecException("level should exist and be greater than or equal to 2");
    }
    if (this.minLength != null && this.minLength.intValue() < 1) {
      throw new EPPCodecException("minLength, if specified, should be greater than 0");
    }
    if (this.maxLength != null) {
      if (this.minLength != null) {
        if (this.maxLength.intValue() < this.minLength.intValue()) {
          throw new EPPCodecException("maxLength, if specified, should be no less than minLength");
        }
      }
      else if (this.maxLength.intValue() < 1) {
        throw new EPPCodecException("maxLength, if specified, should be greater than 0");
      }
    }
  }

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

    if (this.reservedNames != null) {
      clone.reservedNames = (EPPRegistryReservedNames) this.reservedNames.clone();
    }

    return clone;
  }

  /**
   * 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.
   */
  @Override
  public String toString() {
    return EPPUtil.toString(this);
  }

  /**
   * Get the level of domain name.
   *
   * @return level of domain name. Must be &gt;= 2
   */
  public Integer getLevel() {
    return this.level;
  }

  /**
   * Set the level of domain name.
   *
   * @param level
   *           level of domain name. Must be &gt;= 2
   */
  public void setLevel(Integer level) {
    this.level = level;
  }

  /**
   * Get minimum number of characters in a domain name.
   *
   * @return minimum number of characters in a domain name
   */
  public Integer getMinLength() {
    return this.minLength;
  }

  /**
   * Set minimum number of characters in a domain name.
   *
   * @param minLength
   *           minimum number of characters in a domain name
   */
  public void setMinLength(Integer minLength) {
    this.minLength = minLength;
  }

  /**
   * Get maximum number of characters in a domain name.
   *
   * @return maximum number of characters in a domain name
   */
  public Integer getMaxLength() {
    return this.maxLength;
  }

  /**
   * Set maximum number of characters in a domain name.
   *
   * @param maxLength
   *           maximum number of characters in a domain name
   */
  public void setMaxLength(Integer maxLength) {
    this.maxLength = maxLength;
  }

  /**
   * Get whether or not to allow domain name start with an alphanumeric
   * character.
   *
   * @return {@code true} allow domain name start with an alphanumeric
   *         character. {@code false} do not allow domain name start with an
   *         alphanumeric character
   */
  public Boolean getAlphaNumStart() {
    return this.alphaNumStart;
  }

  /**
   * Set whether or not to allow domain name start with an alphanumeric
   * character.
   *
   * @param alphaNumStart
   *           {@code true} allow domain name start with an alphanumeric
   *           character. {@code false} do not allow domain name start with an
   *           alphanumeric character
   */
  public void setAlphaNumStart(Boolean alphaNumStart) {
    this.alphaNumStart = alphaNumStart;
  }

  /**
   * Get whether or not to allow domain name end with an alphanumeric
   * character.
   *
   * @return {@code true} allow domain name end with an alphanumeric character.
   *         {@code false} do not allow domain name end with an alphanumeric
   *         character
   */
  public Boolean getAlphaNumEnd() {
    return this.alphaNumEnd;
  }

  /**
   * Set whether or not to allow domain name end with an alphanumeric
   * character.
   *
   * @param alphaNumEnd
   *           {@code true} allow domain name end with an alphanumeric
   *           character. {@code false} do not allow domain name end with an
   *           alphanumeric character
   */
  public void setAlphaNumEnd(Boolean alphaNumEnd) {
    this.alphaNumEnd = alphaNumEnd;
  }

  /**
   * Get whether ASCII domain names are supported.
   *
   * @return {@code true} if ASCII domain names are supported; {@code false}
   *         otherwise.
   */
  public Boolean getALabelSupported() {
    return this.aLabelSupported;
  }

  /**
   * Set whether whether ASCII domain names are supported.
   *
   * @param aALabelSupported
   *           {@code true} ASCII domain names are supported; {@code false}
   *           otherwise.
   */
  public void setALabelSupported(Boolean aALabelSupported) {
    this.aLabelSupported = aALabelSupported;
  }

  /**
   * Get whether non-ASCII domain names are supported.
   *
   * @return {@code true} if non-ASCII domain names are supported;
   *         {@code false} otherwise.
   */
  public Boolean getULabelSupported() {
    return this.uLabelSupported;
  }

  /**
   * Set whether non-ASCII domain names are supported.
   *
   * @param aULabelSupported
   *           {@code true} non-ASCII domain names are supported; {@code false}
   *           otherwise.
   */
  public void setULabelSupported(Boolean aULabelSupported) {
    this.uLabelSupported = aULabelSupported;
  }

  /**
   * Is the domain name regular expression defined?
   *
   * @return {@code true} if the domain name regular expression is defined;
   *         {@code false} otherwise.
   */
  public boolean hasNameRegex() {
    return (this.nameRegex != null ? true : false);
  }

  /**
   * Get the regular expression that domain name must follow.
   *
   * @return Regular expression if defined; {@code null} otherwise.
   */
  public EPPRegistryRegex getNameRegex() {
    return this.nameRegex;
  }

  /**
   * Set the regular expression that domain name must follow.
   *
   * @param aNameRegex
   *           Regular expression for the domain name label. Set to
   *           {@code null} to unset it.
   */
  public void setNameRegex(EPPRegistryRegex aNameRegex) {
    if (aNameRegex != null) {
      aNameRegex.setRootName(ELM_NAME_REGEX); 
    }
    this.nameRegex = aNameRegex;
  }

  /**
   * Get the instance of {@link EPPRegistryReservedNames} that defines a set of
   * reserved domain names.
   *
   * @return instance of {@link EPPRegistryReservedNames}
   */
  public EPPRegistryReservedNames getReservedNames() {
    return this.reservedNames;
  }

  /**
   * Set the instance of {@link EPPRegistryReservedNames} that defines a set of
   * reserved domain names.
   *
   * @param reservedNames
   *           instance of {@link EPPRegistryReservedNames}
   */
  public void setReservedNames(EPPRegistryReservedNames reservedNames) {
    this.reservedNames = reservedNames;
  }

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

}
