/***********************************************************
Copyright (C) 2004 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

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

import java.util.Vector;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

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;

/**
 * Represents attributes to add, remove or change with a
 * {@code EPPHostUpdateCmd}. In {@code EPPHostUpdateCmd}, an instance of
 * {@code EPPHostAddRemove} is used to specify the attributes to add, an
 * instance of {@code EPPHostAddRemove} is used to specify the attributes to
 * remove and instance of {@code EPPHostAddRemove} is used to specify the
 * attributes to change<br>
 * <br>
 * The &lt;host:add&gt; and &lt;host:rem&gt; elements SHALL contain the
 * following child elements: <br>
 * 
 * <ul>
 * <li>One or more &lt;host:address&gt; elements that contains the IP addresses
 * to be associated with or removed from the host. IP address restrictions
 * explained in the &lt;create&gt; command mapping apply here as well.</li>
 * <li>One or more &lt;host:status&gt; elements that contain status values to be
 * associated with or removed from the object. When specifying a value to be
 * removed, only the attribute value is significant; element text is not
 * required to match a value for removal.</li>
 * </ul>
 * 
 * <br>
 * The &lt;host:chg&gt; element SHALL contain the following child elements: <br>
 * 
 * <ul>
 * <li>A &lt;host:name&gt; element that contains a new fully qualified host name
 * by which the host object will be known.</li>
 * </ul>
 *
 * @see com.verisign.epp.codec.host.EPPHostUpdateCmd
 * @see com.verisign.epp.codec.host.EPPHostAddress
 */
public class EPPHostAddRemove implements EPPCodecComponent {
  /** mode of {@code EPPHostAddRemove} is not specified. */
  final static short MODE_NONE = 0;

  /** mode of {@code EPPHostAddRemove} is to add attributes. */
  final static short MODE_ADD = 1;

  /** mode of {@code EPPHostAddRemove} is to remove attributes. */
  final static short MODE_REMOVE = 2;

  /**
   * XML tag name when the {@code mode} attribute is
   * {@code EPPHostAddRemove.MODE_ADD}. This is a package constant, so the
   * container {@code EPPCodecComponent} can use it on a decode operation.
   */
  final static String ELM_ADD = "add";

  /**
   * XML tag name when the {@code mode} attribute is
   * {@code EPPHostAddRemove.MODE_REMOVE}. This is a package constant, so the
   * container {@code EPPCodecComponent} can use it on a decode operation.
   */
  final static String ELM_REMOVE = "rem";

  /**
   * XML tag name when the {@code mode} attribute is
   * {@code EPPHostAddRemove.MODE_REMOVE}. This is a package constant, so the
   * container {@code EPPCodecComponent} can use it on a decode operation.
   */
  final static String ELM_CHANGE = "chg";

  /**
   * XML tag name when the {@code mode} attribute is
   * {@code EPPHostAddRemove.MODE_ADD}. This is a package constant, so the
   * container {@code EPPCodecComponent} can use it on a decode operation.
   */
  final static String ELM_NAME = "name";

  /** mode of {@code EPPHostAddRemove} is to change attributes. */
  final static short MODE_CHANGE = 3;

  /** name to change. */
  private String name = null;

  /** statuses to add or remove. */
  private Vector<EPPHostStatus> statuses = null;

  /**
   * Mode of EPPHostAddRemove. Must be {@code MODE_ADD} or {@code MODE_REMOVE}
   * to be valid. This attribute will be set by the parent container
   * {@code EPPCodecComponent}. For example, {@code EPPHostUpdateCmd} will set
   * the mode for its {@code EPPHostAddRemove} instances.
   */
  private short mode = MODE_NONE;

  /** Addresses to add or remove. */
  private Vector<EPPHostAddress> addresses = null;

  /**
   * Default constructor for {@code EPPHostAddRemove}. The addresses attribute
   * defaults to {@code null}.
   */
  public EPPHostAddRemove() {
    addresses = null;
    statuses = null;
    name = null;
  }

  /**
   * Constructor for {@code EPPHostAddRemove} that includes the attributes as
   * arguments.
   *
   * @param aAddresses
   *           Vector of {@code EPPHostAddress} instances. Is {@code null} or
   *           empty for no modifications.
   */
  public EPPHostAddRemove(Vector<EPPHostAddress> aAddresses) {
    setAddresses(aAddresses);
    statuses = null;
    name = null;
  }

  /**
   * Constructor for {@code EPPHostAddRemove}. The addresses attribute defaults
   * to {@code null}.
   *
   * @param aName
   *           DOCUMENT ME!
   */
  public EPPHostAddRemove(String aName) {
    addresses = null;
    statuses = null;
    name = aName;
  }

  /**
   * Constructor for {@code EPPHostAddRemove} that includes the attributes as
   * arguments.
   *
   * @param aAddresses
   *           Vector of {@code EPPHostAddress} instances. Is {@code null} or
   *           empty for no modifications.
   * @param aStatuses
   *           Vector of {@code EPPHostStatus} instances. Is {@code null} or
   *           empty for no modifications.
   */
  public EPPHostAddRemove(Vector<EPPHostAddress> aAddresses, Vector<EPPHostStatus> aStatuses) {
    setAddresses(aAddresses);
    setStatuses(aStatuses);
    name = null;
  }

  /**
   * Are addresses set?
   *
   * @return {@code true} if addresses are set; {@code false} otherwise.
   */
  public boolean hasAddresses() {
    if (this.addresses != null) {
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Add an address to the list of addresses.
   * 
   * @param aAddress
   *           Address to add
   */
  public void addAddress(EPPHostAddress aAddress) {
    if (this.addresses == null) {
      this.addresses = new Vector<EPPHostAddress>();
    }

    this.addresses.add(aAddress);
  }

  /**
   * Gets the addresses to add or remove.
   *
   * @return Vector {@code EPPHostAddress} instances if defined; {@code null}
   *         otherwise.
   */
  public Vector<EPPHostAddress> getAddresses() {
    return addresses;
  }

  /**
   * Sets the addresses to add or remove.
   *
   * @param aAddresses
   *           Vector of {@code EPPHostAddress} instances.
   */
  public void setAddresses(Vector<EPPHostAddress> aAddresses) {
    addresses = aAddresses;

    if ((addresses != null) && (addresses.size() == 0)) {
      addresses = null;
    }
  }

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

    // Add or Remove node
    if (mode == MODE_ADD) {
      root = aDocument.createElementNS(EPPHostMapFactory.NS, EPPHostMapFactory.NS_PREFIX + ":" + ELM_ADD);
    }
    else if (mode == MODE_REMOVE) {
      root = aDocument.createElementNS(EPPHostMapFactory.NS, EPPHostMapFactory.NS_PREFIX + ":" + ELM_REMOVE);
    }
    else if (mode == MODE_CHANGE) {
      root = aDocument.createElementNS(EPPHostMapFactory.NS, EPPHostMapFactory.NS_PREFIX + ":" + ELM_CHANGE);
    }
    else {
      throw new EPPEncodeException("Invalid EPPHostAddRemove mode of " + mode);
    }

    if (mode == MODE_CHANGE) {
      // name
      EPPUtil.encodeString(aDocument, root, name, EPPHostMapFactory.NS, EPPHostMapFactory.NS_PREFIX + ":" + ELM_NAME);
    }
    else {
      // Addresses
      EPPUtil.encodeCompVector(aDocument, root, addresses);

      // Statuses
      EPPUtil.encodeCompVector(aDocument, root, statuses);
    }

    return root;
  }

  /**
   * Decode the {@code EPPHostAddRemove} attributes from the {@code aElement}
   * DOM Element tree.
   *
   * @param aElement
   *           Root DOM Element to decode {@code EPPHostAddRemove} from.
   *
   * @exception EPPDecodeException
   *               Unable to decode {@code aElement}.
   */
  public void decode(Element aElement) throws EPPDecodeException {
    if (aElement.getLocalName().equals(EPPUtil.getLocalName(ELM_ADD))) {
      mode = MODE_ADD;
    }
    else if (aElement.getLocalName().equals(EPPUtil.getLocalName(ELM_REMOVE))) {
      mode = MODE_REMOVE;
    }
    else if (aElement.getLocalName().equals(EPPUtil.getLocalName(ELM_CHANGE))) {
      mode = MODE_CHANGE;
    }
    else {
      throw new EPPDecodeException("Invalid EPPHostAddRemove mode of " + aElement.getLocalName());
    }

    if (mode == MODE_CHANGE) {
      // name
      name = EPPUtil.decodeString(aElement, EPPHostMapFactory.NS, ELM_NAME);
    }
    else {
      // Addresses
      addresses = EPPUtil.decodeCompVector(aElement, EPPHostMapFactory.NS, EPPHostAddress.ELM_NAME,
            EPPHostAddress.class);

      if (addresses.size() == 0) {
        addresses = null;
      }

      // statuses
      statuses = EPPUtil.decodeCompVector(aElement, EPPHostMapFactory.NS, EPPHostStatus.ELM_NAME,
            EPPHostStatus.class);

      if (statuses.size() == 0) {
        statuses = null;
      }
    }
  }

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

    EPPHostAddRemove theComp = (EPPHostAddRemove) aObject;

    // Mode
    if (mode != theComp.mode) {
      return false;
    }

    // Addresses
    if (!EPPUtil.equalVectors(addresses, theComp.addresses)) {
      return false;
    }

    // Statuses
    if (!EPPUtil.equalVectors(statuses, theComp.statuses)) {
      return false;
    }

    // name
    if (!((name == null) ? (theComp.name == null) : name.equals(theComp.name))) {
      return false;
    }

    return true;
  }
  // End EPPHostAddRemove.equals(Object)

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

    clone = (EPPHostAddRemove) super.clone();

    // Addresses
    if (this.hasAddresses()) {
      clone.addresses = (Vector<EPPHostAddress>) this.addresses.clone();

      for (int i = 0; i < this.addresses.size(); i++) {
        clone.addresses.setElementAt((EPPHostAddress) (this.addresses.elementAt(i)).clone(), i);
      }
    }

    // Statuses
    if (this.hasStatuses()) {
      clone.statuses = (Vector<EPPHostStatus>) this.statuses.clone();

      for (int i = 0; i < this.statuses.size(); i++) {
        clone.statuses.setElementAt((EPPHostStatus) (this.statuses.elementAt(i)).clone(), i);
      }
    }

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

  /**
   * Gets the mode of {@code EPPHostAddRemove}. There are two valid modes
   * {@code EPPHostAddRemove.MODE_ADD} and
   * {@code EPPHostAddRemove.MODE_REMOVE}. If no mode has been satisfied, than
   * the mode is set to {@code EPPHostAddRemove.MODE_NONE}.
   *
   * @return One of the {@code EPPHostAddRemove_MODE} constants.
   */
  short getMode() {
    return mode;
  }

  /**
   * Sets the mode of {@code EPPHostAddRemove}. There are two valid modes
   * {@code EPPHostAddRemove.MODE_ADD} and
   * {@code EPPHostAddRemove.MODE_REMOVE}. If no mode has been satisfied, than
   * the mode is set to {@code EPPHostAddRemove.MODE_NONE}
   *
   * @param aMode
   *           {@code EPPHostAddRemove.MODE_ADD} or
   *           {@code EPPHostAddRemove.MODE_REMOVE}.
   */
  void setMode(short aMode) {
    mode = aMode;
  }

  /**
   * Gets name.
   *
   * @return String Name
   */
  public String getName() {
    return name;
  }

  /**
   * Are statuses set?
   *
   * @return {@code true} if statuses are set; {@code false} otherwise.
   */
  public boolean hasStatuses() {
    if (this.statuses != null) {
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Add a status to the list of statuses.
   * 
   * @param aStatus
   *           Status to add
   */
  public void addStatus(EPPHostStatus aStatus) {
    if (this.statuses == null) {
      this.statuses = new Vector<EPPHostStatus>();
    }

    this.statuses.add(aStatus);
  }

  /**
   * Gets statuses.
   *
   * @return {@code Vector} of {@code EPPHostStatus} if defined; {@code null}
   *         otherwise.
   */
  public Vector<EPPHostStatus> getStatuses() {
    return statuses;
  }

  /**
   * Sets statuses.
   *
   * @param aStatuses
   *           {@code Vector} of {@code EPPHostStatus} instances
   */
  public void setStatuses(Vector<EPPHostStatus> aStatuses) {
    statuses = aStatuses;

    if ((statuses != null) && (statuses.size() == 0)) {
      statuses = null;
    }
  }

  /**
   * Sets name.
   *
   * @param aName
   *           String
   */
  public void setName(String aName) {
    name = aName;
  }

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