/***********************************************************
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 java.util.ArrayList;
import java.util.List;

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;

/**
 * Represents the host object policy information per RFC 5732. The
 * &lt;registry:host&gt; element contains the following child elements:<br>
 * <br>
 * <ul>
 * <li>&lt;registry:internal&gt; - Defines the minimum and maximum number of IP
 * addresses supported for an internal host. Use {@link #getInternal()} and
 * {@link #setInternal(EPPRegistryInternalHost)} to get and set the element.
 * </li>
 * <li>&lt;registry:external&gt; - Defines the policies for external hosts. Use
 * {@link #getExternal()} and {@link #setExternal(EPPRegistryExternalHost)} to
 * get and set the element.</li>
 * <li>&lt;registry:nameRegex&gt; - The OPTIONAL regular expression used to
 * validate the host name value. Use {@link #getNameRegex()} and
 * {@link #setNameRegex(EPPRegistryRegex)} to get and set the element.</li>
 * <li>&lt;registry:maxCheckHost&gt; - The OPTIONAL maximum number of host names
 * (&lt;domain:name&gt; elements) that can be included in a host check command
 * defined in RFC 5732. Use {@link #getMaxCheckHost()} and
 * {@link #setMaxCheckHost(Integer)} to get and set the element.</li>
 * <li>&lt;registry:supportedStatus&gt; - The OPTIONAL set of supported host
 * statuses defined in RFC 5732. Use {@link #getSupportedStatus()} and
 * {@link #setSupportedStatus(EPPRegistrySupportedStatus)} to get and set the
 * element.</li>
 * <li>&lt;registry:invalidIP&gt; - Zero or more &lt;registry:invalidIP&gt;
 * elements that defines the URI of an externally defined list of invalid IP
 * addresses. The IP addresses referenced by the list of
 * &lt;registry:invalidIP&gt; elements should be combined and normalized by the
 * client to define the complete set of invalid IP addresses. Use
 * {@link #getInvalidIPs()} and {@link #setInvalidIPs(List)} to get and set
 * these elements.</li>
 * </ul>
 *
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryInternalHost
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryExternalHost
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryRegex
 * @see com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus
 */

public class EPPRegistryHost implements EPPCodecComponent {

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

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

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

  /**
   * XML Element Name of {@code nameRegex} attribute.
   */
  public static final String ELM_NAME_REGEX = "nameRegex";

  /**
   * XML Element Name of {@code maxCheckHost} attribute.
   */
  public static final String ELM_MAX_CHECK = "maxCheckHost";

  /**
   * XML Element Name of {@code authInfoRegex} attribute.
   */
  public final static String ELM_AUTH_INFO_REGEX = "authInfoRegex";

  /**
   * XML Element Name of {@code invalidIPs} attribute.
   */
  public final static String ELM_INVALID_IP = "invalidIP";

  /**
   * Defines the internal host attributes
   */
  private EPPRegistryInternalHost internal = null;

  /**
   * Defines the external host attributes
   */
  private EPPRegistryExternalHost external = null;

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

  /**
   * The OPTIONAL maximum number of host names (&lt;domain:name&gt; elements) that
   * can be included in a host check command defined in RFC 5732
   */
  private Integer maxCheckHost = null;

  /**
   * Set of supported host statuses defined in RFC 5732
   */
  private EPPRegistrySupportedStatus supportedStatus = null;

  /**
   * {@code List} of invalid IP address URI {@code String} values.
   */
  private List<String> invalidIPs = new ArrayList<String>();

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

    // Internal
    EPPUtil.encodeComp(aDocument, root, this.internal);

    // External
    EPPUtil.encodeComp(aDocument, root, this.external);

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

    // Max Check
    if (this.hasMaxCheckHost()) {
      EPPUtil.encodeString(aDocument, root, this.maxCheckHost.toString(), EPPRegistryMapFactory.NS,
            EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_MAX_CHECK);
    }

    // Supported Status
    if (this.supportedStatus != null) {
      EPPUtil.encodeComp(aDocument, root, this.supportedStatus);
    }

    // Invalid IPs
    EPPUtil.encodeList(aDocument, root, this.invalidIPs, EPPRegistryMapFactory.NS,
          EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_INVALID_IP);

    return root;
  }

  /**
   * Decode the {@code EPPRegistryHost} attributes from the aElement DOM
   * Element tree.
   *
   * @param aElement
   *           Root DOM Element to decode {@code EPPRegistryHost} from.
   *
   * @exception EPPDecodeException
   *               Unable to decode aElement
   */
  @Override
  public void decode(Element aElement) throws EPPDecodeException {
    // Internal
    this.internal = (EPPRegistryInternalHost) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
          EPPRegistryInternalHost.ELM_NAME, EPPRegistryInternalHost.class);

    // External
    this.external = (EPPRegistryExternalHost) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
          EPPRegistryExternalHost.ELM_NAME, EPPRegistryExternalHost.class);

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

    // Max Check
    this.maxCheckHost = EPPUtil.decodeInteger(aElement, EPPRegistryMapFactory.NS, ELM_MAX_CHECK);

    // Supported Status
    this.supportedStatus = (EPPRegistrySupportedStatus) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
          EPPRegistrySupportedStatus.ELM_NAME, EPPRegistrySupportedStatus.class);

    // Invalid IPs
    this.invalidIPs = EPPUtil.decodeList(aElement, EPPRegistryMapFactory.NS, ELM_INVALID_IP);
  }

  /**
   * Validate the state of the {@code EPPRegistryHost} 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
   *            Validation error
   */
  void validateState() throws EPPCodecException {
    if (this.internal == null) {
      throw new EPPCodecException("internal required element is not set");
    }
    if (this.external == null) {
      throw new EPPCodecException("external required element is not set");
    }
  }

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

    // Internal
    if (this.internal != null) {
      clone.internal = (EPPRegistryInternalHost) this.internal.clone();
    }

    // External
    if (this.external != null) {
      clone.external = (EPPRegistryExternalHost) this.external.clone();
    }

    // Supported Status
    if (this.supportedStatus != null) {
      clone.supportedStatus = (EPPRegistrySupportedStatus) this.supportedStatus.clone();
    }

    // Invalid IPs
    if (this.invalidIPs != null) {
      clone.invalidIPs = (List) ((ArrayList) this.invalidIPs).clone();
    }

    return clone;
  }

  /**
   * implements a deep {@code EPPRegistryHost} compare.
   *
   * @param aObject
   *           {@code EPPRegistryHost} 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 EPPRegistryHost)) {
      return false;
    }

    EPPRegistryHost theComp = (EPPRegistryHost) aObject;

    // Internal
    if (!EqualityUtil.equals(this.internal, theComp.internal)) {
      cat.error("EPPRegistryHost.equals(): internal not equal");
      return false;
    }

    // External
    if (!EqualityUtil.equals(this.external, theComp.external)) {
      cat.error("EPPRegistryHost.equals(): external not equal");
      return false;
    }

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

    // Max Check
    if (!EqualityUtil.equals(this.maxCheckHost, theComp.maxCheckHost)) {
      cat.error("EPPRegistryHost.equals(): maxCheckHost not equal");
      return false;
    }

    // Supported Status
    if (!EqualityUtil.equals(this.supportedStatus, theComp.supportedStatus)) {
      cat.error("EPPRegistryHost.equals(): supportedStatus not equal");
      return false;
    }

    // Invalid IPs
    if (!((this.invalidIPs == null) ? (theComp.invalidIPs == null)
          : EPPUtil.equalLists(this.invalidIPs, theComp.invalidIPs))) {
      cat.error("EPPRegistryHost.equals(): invalidIPs 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.
   */
  @Override
  public String toString() {
    return EPPUtil.toString(this);
  }

  /**
   * Gets the internal host attributes.
   *
   * @return the internal host attributes
   */
  public EPPRegistryInternalHost getInternal() {
    return this.internal;
  }

  /**
   * Sets the internal host attributes.
   *
   * @param aInternal
   *           the internal host attributes
   */
  public void setInternal(EPPRegistryInternalHost aInternal) {
    this.internal = aInternal;
  }

  /**
   * Gets the external host attributes.
   *
   * @return the external host attributes
   */
  public EPPRegistryExternalHost getExternal() {
    return this.external;
  }

  /**
   * Sets the external host attributes.
   *
   * @param aExternal
   *           the external host attributes
   */
  public void setExternal(EPPRegistryExternalHost aExternal) {
    this.external = aExternal;
  }

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

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

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

  /**
   * Is the maximum number of host names defined?
   *
   * @return {@code true} if the maximum number of host names is defined;
   *         {@code false} otherwise.
   */
  public boolean hasMaxCheckHost() {
    return (this.maxCheckHost != null ? true : false);
  }

  /**
   * Gets maximum number of host names that can be included in a host check
   * command.
   *
   * @return maximum number of host names that can be included in a host check
   *         command if defined; {@code null} otherwise.
   */
  public Integer getMaxCheckHost() {
    return this.maxCheckHost;
  }

  /**
   * Sets maximum number of host names that can be included in a host check
   * command.
   *
   * @param aMaxCheckHost
   *           maximum number of host names that can be included in a host
   *           check command. Set to {@code null} to unset the value.
   */
  public void setMaxCheckHost(Integer aMaxCheckHost) {
    this.maxCheckHost = aMaxCheckHost;
  }

  /**
   * Gets a set of supported host statuses defined in RFC 5732.
   *
   * @return set of supported host statuses defined in RFC 5732
   */
  public EPPRegistrySupportedStatus getSupportedStatus() {
    return this.supportedStatus;
  }

  /**
   * Sets a set of supported host statuses defined in RFC 5732.
   *
   * @param aSupportedStatus
   *           set of supported host statuses defined in RFC 5732
   */
  public void setSupportedStatus(EPPRegistrySupportedStatus aSupportedStatus) {
    this.supportedStatus = aSupportedStatus;
  }

  /**
   * Is the invalid IPs defined?
   *
   * @return {@code true} if the invalid IPs is defined; {@code false}
   *         otherwise.
   */
  public boolean hasInvalidIPs() {
    return (this.invalidIPs != null && !this.invalidIPs.isEmpty() ? true : false);
  }

  /**
   * Gets the invalid IP address URIs.
   *
   * @return {@code List} of invalid IP address URIs
   */
  public List<String> getInvalidIPs() {
    return this.invalidIPs;
  }

  /**
   * Sets the invalid IP address URIs.
   *
   * @param aInvalidIPs
   *           {@code List} of invaild IP address URI {@code String} values.
   */
  public void setInvalidIPs(List<String> aInvalidIPs) {
    if (aInvalidIPs == null) {
      this.invalidIPs = new ArrayList<String>();
    }
    else {
      this.invalidIPs = aInvalidIPs;
    }
  }

  /**
   * Adds one invalid IP address URI to the list.
   *
   * @param aInvalidIP
   *           supported algorithm as described in section 2.1.3 of RFC 4034
   */
  public void addInvalidIP(String aInvalidIP) {
    if (this.invalidIPs == null) {
      this.invalidIPs = new ArrayList<String>();
    }
    this.invalidIPs.add(aInvalidIP);
  }

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

}
