/***********************************************************
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.EPPDecodeException;
import com.verisign.epp.codec.gen.EPPEncodeException;
import com.verisign.epp.codec.gen.EPPInfoCmd;
import com.verisign.epp.codec.gen.EPPUtil;
import com.verisign.epp.util.EqualityUtil;

/**
 * Represents an EPP Registry &lt;info&gt; command that is used to retrieve
 * information associated with a registry. The &lt;registry:info&gt; element
 * MUST contain one of the following child elements:<br>
 * <br>
 *
 * <ul>
 * <li>A &lt;registry:all&gt; empty element that specifies whether or not to
 * query a list of all supported zone objects by the server. Use {@code isAll}
 * and {@code setAll} to get and set the element.</li>
 * <li>A &lt;registry:name&gt; element that contains the fully qualified zone
 * object name for which information is requested. Use {@code getName} and
 * {@code setName} to get and set the element.</li>
 * <li>A &lt;registry:system&gt; Element that is empty and that indicates that
 * the registry system attributes, like maximum connections and timeouts, are
 * queried. Use {@code isSystem} and {@code setSystem} to get and set the
 * element.</li>
 * </ul>
 *
 * A valid {@code EPPRegistryInfoCmd} must contains one and only one of the
 * above elements. <br>
 *
 * <br>
 * {@code EPPRegistryInfoResp} is the concrete {@code EPPReponse} associated
 * with {@code EPPRegistryInfoResp}. <br>
 * <br>
 *
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryInfoResp
 */
public class EPPRegistryInfoCmd extends EPPInfoCmd {

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

  /**
   * Possible values for the {@code mode} attribute, which defines the mode to
   * query.
   */
  public static enum Mode {
    /**
     * Queries for all of the client accessible and/or available zone objects
     * with a summary set of attributes per zone object.
     */
    all,

    /**
     * Queries for a full set of attributes for a single zone object.
     */
    name,

    /**
     * Queries for the registry system attributes.
     */
    system;
  }

  /**
   * Possible values for the {@code scope} attribute.
   */
  public static enum Scope {

    /**
     * Indicate the zones that are accessible to the client.
     */
    accessible,

    /**
     * Indicate the zones that are not accessible to the client but available
     * on the server.
     */
    available,

    /**
     * Indicate both accessible and available zones
     */
    both;
  }

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

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

  /**
   * XML Element Name for the {@code all} attribute.
   */
  private final static String ELM_REGISTRY_ALL = "all";

  /**
   * XML Element Name for the {@code system} attribute.
   */
  private final static String ELM_REGISTRY_SYSTEM = "system";

  /**
   * XML attribute name for the {@code scope} attribute.
   */
  public static final String ATTR_SCOPE = "scope";

  /**
   * The query mode
   */
  private Mode mode = null;

  /**
   * Scope of zones returned with the &lt;registry:all&gt; option.
   */
  private Scope scope = Scope.accessible;

  /**
   * Fully qualified name of zone object to get information on. Cannot be used
   * together with {@code all}. If {@code system} is {@code true} or
   * {@code all} is {@code true}, then this attribute must be set to
   * {@code false}..
   */
  private EPPRegistryZoneName name = null;

  /**
   * {@code EPPRegistryInfoCmd} default constructor. The {@code mode} must be
   * set using {@link #setMode(Mode)} prior to invoking
   * {@link #encode(Document)}.
   */
  public EPPRegistryInfoCmd() {
  }

  /**
   * {@code EPPRegistryInfoCmd} constructor that takes the qualified aLabel
   * zone object name as an argument. The mode is set to {@link Mode#name}.
   *
   * @param aTransId
   *           transaction Id associated with command
   * @param aName
   *           fully qualified aLabel zone object name to get information on
   */
  public EPPRegistryInfoCmd(String aTransId, String aName) {
    super(aTransId);
    this.setName(aName);
  }

  /**
   * {@code EPPRegistryInfoCmd} constructor that takes the qualified zone
   * object name as an argument. The mode is set to {@link Mode#name}.
   *
   * @param aTransId
   *           transaction Id associated with command
   * @param aName
   *           fully qualified zone object name to get information on
   */
  public EPPRegistryInfoCmd(String aTransId, EPPRegistryZoneName aName) {
    super(aTransId);
    this.setName(aName);
  }

  /**
   * {@code EPPRegistryInfoCmd} constructor defines the query mode to use. It
   * is most likely used when setting the mode to {@link Mode#system}.
   *
   * @param aTransId
   *           transaction Id associated with command
   * @param aMode
   *           Query mode to use.
   */
  public EPPRegistryInfoCmd(String aTransId, Mode aMode) {
    super(aTransId);
    this.setMode(aMode);
  }

  /**
   * {@code EPPRegistryInfoCmd} constructor that queries for all of the
   * accessible and/or available zone objects from the server with the desired
   * scope. The mode is set to {@link Mode#all}.
   *
   * @param aTransId
   *           transaction Id associated with command
   * @param aScope
   *           Zone scope to query for. Passing {@code null} will result in the
   *           use of the default scope {@code Scope#accessible}.
   */
  public EPPRegistryInfoCmd(String aTransId, Scope aScope) {
    super(aTransId);
    this.setScope(aScope);
  }

  /**
   * Encode a DOM Element tree from the attributes of the
   * {@code EPPRegistryInfoCmd} instance.
   *
   * @param aDocument
   *           DOM Document that is being built. Used as an Element factory.
   *
   * @return Root DOM Element representing the {@code EPPRegistryInfoCmd}
   *         instance.
   *
   * @exception EPPEncodeException
   *               Unable to encode {@code EPPRegistryInfoCmd} instance.
   */
  @Override
  protected Element doEncode(Document aDocument) throws EPPEncodeException {
    // Validate state
    if (!this.hasMode()) {
      throw new EPPEncodeException("The mode must be set");
    }
    if (this.getMode() == Mode.name && !this.hasName()) {
      throw new EPPEncodeException("The zone name must be set when the mode is \"name\"");
    }

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

    // Query mode?
    switch (this.mode) {

      case name:
        EPPUtil.encodeComp(aDocument, root, this.name);
        break;

      case all:
        Element theAllElm = aDocument.createElementNS(EPPRegistryMapFactory.NS,
              EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_REGISTRY_ALL);
        root.appendChild(theAllElm);

        // Scope
        theAllElm.setAttribute(ATTR_SCOPE, this.scope.toString());
        break;

      case system:
        root.appendChild(aDocument.createElementNS(EPPRegistryMapFactory.NS,
              EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_REGISTRY_SYSTEM));
    }

    return root;
  }

  /**
   * Decode the {@code EPPRegistryInfoCmd} attributes from the aElement DOM
   * Element tree.
   *
   * @param aElement
   *           Root DOM Element to decode {@code EPPRegistryInfoCmd} from.
   *
   * @exception EPPDecodeException
   *               Unable to decode aElement
   */
  @Override
  protected void doDecode(Element aElement) throws EPPDecodeException {

    // Mode
    Element theModeElm = EPPUtil.getFirstElementChild(aElement);
    this.mode = Mode.valueOf(theModeElm.getLocalName());

    // Query mode?
    switch (this.mode) {
      case name:
        this.name = (EPPRegistryZoneName) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
              EPPRegistryZoneName.ELM_ZONE_NAME, EPPRegistryZoneName.class);
        this.scope = Scope.accessible;
        break;
      case all:
        String theScopeStr = EPPUtil.decodeStringAttr(theModeElm, ATTR_SCOPE);
        if (theScopeStr == null) {
          // Default value
          this.scope = Scope.accessible;
        }
        else {
          this.scope = Scope.valueOf(theScopeStr);
        }
        this.name = null;
        break;
      case system:
        this.name = null;
        this.scope = Scope.accessible;
    }
  }

  /**
   * Gets the EPP command namespace associated with {@code EPPRegistryInfoCmd}
   * .
   *
   * @return {@code EPPRegistryMapFactory.NS}
   */
  @Override
  public String getNamespace() {
    return EPPRegistryMapFactory.NS;
  }

  /**
   * Gets the key for the registry object, which is the registry zone name.
   * 
   * @return The zone name if set; {@code null} otherwise.
   */
  @Override
  public String getKey() {
	 if (this.name != null) {
      return this.getName().getName();
	 }
	 else {
		 return null;
	 }
  }
  
  /**
   * Compare an instance of {@code EPPRegistryInfoCmd} with this instance.
   *
   * @param aObject
   *           Object 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 EPPRegistryInfoCmd)) {
      cat.error("EPPRegistryInfoCmd.equals(): " + aObject.getClass().getName() + " not EPPRegistryInfoCmd instance");

      return false;
    }

    if (!super.equals(aObject)) {
      cat.error("EPPRegistryInfoCmd.equals(): super class not equal");

      return false;
    }

    EPPRegistryInfoCmd theComp = (EPPRegistryInfoCmd) aObject;

    // Mode
    if (!EqualityUtil.equals(this.mode, theComp.mode)) {
      cat.error("EPPRegistryInfoCmd.equals(): mode not equal");
      return false;
    }

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

    // Scope
    if (!EqualityUtil.equals(this.scope, theComp.scope)) {
      cat.error("EPPRegistryInfoCmd.equals(): scope not equal");
      return false;
    }

    return true;
  }

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

    // Name
    if (this.name != null) {
      clone.name = (EPPRegistryZoneName) this.name.clone();
    }

    return clone;
  }

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

  /**
   * Gets the zone name.
   *
   * @return Zone name if set; {@code null} otherwise.
   */
  public EPPRegistryZoneName getName() {
    return this.name;
  }

  /**
   * Sets the zone name to query for. The mode is set to {@link Mode#name}.
   *
   * @param aName
   *           Zone name
   */
  public void setName(EPPRegistryZoneName aName) {
    this.name = aName;
    this.mode = Mode.name;
  }

  /**
   * Sets the zone name to query for. The mode is set to {@link Mode#name}.
   *
   * @param aName
   *           aLabel zone name
   */
  public void setName(String aName) {
    this.setName(new EPPRegistryZoneName(aName, EPPRegistryZoneName.Form.aLabel));
  }

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

  /**
   * Gets the query mode.
   *
   * @return The query mode if defined; {@code null} otherwise.
   */
  public Mode getMode() {
    return this.mode;
  }

  /**
   * Sets the query mode.
   *
   * @param aMode
   *           Query mode to use
   */
  public void setMode(Mode aMode) {
    this.mode = aMode;
  }

  /**
   * Gets the zone scope used with the query all option.
   *
   * @return Scope passed with the &lt;registry:all&gt; "scope" attribute if defined; {@code null} otherwise.
   */
  public Scope getScope() {
    return this.scope;
  }

  /**
   * Sets the zone scope used with the query all mode. The mode is set to
   * {@link Mode#all}.
   *
   * @param aScope
   *           Scope passed with the &lt;registry:all&gt; "scope" attribute. If
   *           {@code null} is passed, the default value of
   *           {@link Scope#accessible} will be used.
   */
  public void setScope(Scope aScope) {
    if (aScope == null) {
      // Set to the default
      this.scope = Scope.accessible;
    }
    else {
      this.scope = aScope;
    }
    this.mode = Mode.all;
  }

}
