/***********************************************************
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-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/

package com.verisign.epp.interfaces.registry.v02;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import com.verisign.epp.codec.gen.EPPCodecComponent;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryCreateCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryCreateResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryDeleteCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryUpdateCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryZone;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneName;
import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.interfaces.EPPSession;

/**
 * Interface class for the EPP Registry Object that complies with v01
 * (urn:ietf:params:xml:ns:registry-0.1) of the XML schema. This class can be
 * used to prepare and send the supported EPP Registry Object commands.
 * {@code EPPRegistry} requires an established {@link EPPSession} that is used
 * to send the commands and read the responses from a server.
 */
public class EPPRegistry {
  /**
   * An instance of a session.
   */
  private EPPSession session = null;

  /**
   * Transaction Id provided by client
   */
  private String transId = null;

  /**
   * What is the query mode for the info command?
   */
  private EPPRegistryInfoCmd.Mode infoMode = null;

  private EPPRegistryInfoCmd.Scope infoScope = null;

  /**
   * List of zone names to use with the commands {@link #sendCheck()},
   * {@link #sendInfo()}, and {@link #sendDelete()}. Only the
   * {@link #sendCheck} accepts more than one zone to be set.
   */
  private List<EPPRegistryZoneName> zoneList = null;

  /**
   * Zone that is used with the transform commands {@link #sendCreate()} and
   * {@link #sendUpdate()}.
   */
  private EPPRegistryZone zone = null;

  /**
   * Extensions to attach to the command.
   */
  private Vector<EPPCodecComponent> extensions = null;

  /**
   * Constructs an {@code EPPRegistry} given an initialized EPP session.
   *
   * @param aSession
   *           Server session to use.
   */
  public EPPRegistry(EPPSession aSession) {
    this.session = aSession;
  }

  /**
   * Sends a check command to the server.<br>
   * <br>
   * The required attributes have been set with the following methods:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #addZone(EPPRegistryZoneName)}, {@link #addZone(String)}, or
   * {@link #setZoneList(List)} - To set the zone names to check. More than one
   * zone name can be checked in {@code sendCheck}.</li>
   * </ul>
   *
   * <br>
   * The optional attributes have been set with the following:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setTransId(String)} - Sets the client transaction identifier
   * </li>
   * <li>{@link #addExtension(EPPCodecComponent)} or
   * {@link #setExtensions(Vector)} - Set extensions to include with the
   * command.
   * </ul>
   *
   *
   * @return {@link EPPRegistryCheckResp} containing the zone check
   *         information.
   *
   * @exception EPPCommandException
   *               Error executing the check command. Use
   *               {@link #getResponse()} to get the associated server error
   *               response.
   */
  public EPPRegistryCheckResp sendCheck() throws EPPCommandException {
    // Pre-condition check
    if (this.zoneList == null || this.zoneList.isEmpty()) {
      throw new EPPCommandException("At least one zone name is required for sendCheck()");
    }

    // Create the command
    EPPRegistryCheckCmd theCommand = new EPPRegistryCheckCmd(this.transId, this.zoneList);

    // Add extensions
    theCommand.setExtensions(this.extensions);

    // Reset registry attributes
    this.resetRegistry();

    // Process the command and response
    return (EPPRegistryCheckResp) this.session.processDocument(theCommand, EPPRegistryCheckResp.class);
  }

  /**
   * Sends a delete command to the server to delete a zone by name.<br>
   * <br>
   * The required attributes have been set with the following methods:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #addZone(EPPRegistryZoneName)}, {@link #addZone(String)}, or
   * {@link #setZoneList(List)} - To set a single zone name to delete. More
   * than one zone name will result in an {@link EPPCommandException}.</li>
   * </ul>
   *
   * <br>
   * The optional attributes have been set with the following:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setTransId(String)} - Sets the client transaction identifier
   * </li>
   * <li>{@link #addExtension(EPPCodecComponent)} or
   * {@link #setExtensions(Vector)} - Set extensions to include with the
   * command.
   * </ul>
   *
   *
   * @return {@link EPPResponse} containing the result of the delete command
   *
   * @exception EPPCommandException
   *               Error executing the delete command. Use
   *               {@link #getResponse()} to get the associated server error
   *               response.
   */
  public EPPResponse sendDelete() throws EPPCommandException {
    // Pre-condition check
    if (this.zoneList == null || this.zoneList.size() != 1) {
      throw new EPPCommandException("One zone name is required for sendDelete()");
    }

    // Create the command
    EPPRegistryDeleteCmd theCommand = new EPPRegistryDeleteCmd(this.transId, this.zoneList.get(0));

    // Add extensions
    theCommand.setExtensions(this.extensions);

    // Reset registry attributes
    resetRegistry();

    // Process the command and response
    return this.session.processDocument(theCommand, EPPResponse.class);
  }

  /**
   * Sends an info command to the server. There are three forms of the info
   * command that include get all zone summary information, get detailed
   * information for an individual zone, or get system information.<br>
   * <br>
   * The required attributes have been set with the following methods:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #addZone(EPPRegistryZoneName)}, {@link #addZone(String)}, or
   * {@link #setZoneList(List)} - To set a single zone name to get the detailed
   * information for. This is used to send the "get detailed information for an
   * individual zone" form. More than one zone name will result in an
   * {@link EPPCommandException}. OR</li>
   * <li>{@link #setAllScope(EPPRegistryInfoCmd.Scope)} - "get all zone summary
   * information" form of info command. OR</li>
   * <li>{@link #setInfoMode(EPPRegistryInfoCmd.Mode)} - Used to explicitly set
   * the info mode to {@code EPPRegistryInfoCmd.Mode.system}.
   * </ul>
   *
   * <br>
   * The optional attributes have been set with the following:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setTransId(String)} - Sets the client transaction identifier
   * </li>
   * <li>{@link #addExtension(EPPCodecComponent)} or
   * {@link #setExtensions(Vector)} - Set extensions to include with the
   * command.
   * </ul>
   *
   *
   * @return {@code EPPRegistryInfoResp} containing the requested information
   *         from the server based on the form of the info command.
   *
   * @exception EPPCommandException
   *               Error executing the info command. Use {@link #getResponse()}
   *               to get the associated server error response.
   */
  public EPPRegistryInfoResp sendInfo() throws EPPCommandException {
    EPPRegistryInfoCmd theCommand = null;

    if (this.infoMode == null) {
      throw new EPPCommandException("The info mode is not set.");
    }

    switch (this.infoMode) {
      case name:
        if (this.zoneList.size() != 1) {
          throw new EPPCommandException("One zone name is required for sendInfo()");
        }
        theCommand = new EPPRegistryInfoCmd(this.transId, this.zoneList.get(0));
        break;
      case all:
        // A {@code null} scope will result in the use of the default scope.
        theCommand = new EPPRegistryInfoCmd(this.transId, this.infoScope);
        break;
      case system:
        theCommand = new EPPRegistryInfoCmd(this.transId, EPPRegistryInfoCmd.Mode.system);
    }

    // Add extensions
    theCommand.setExtensions(this.extensions);

    // Reset registry attributes
    resetRegistry();

    // Process the command and response
    return (EPPRegistryInfoResp) this.session.processDocument(theCommand, EPPRegistryInfoResp.class);
  }

  /**
   * Sends a create command to the server to create a zone.<br>
   * <br>
   * The required attributes have been set with the following methods:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setZone(EPPRegistryZone)} - Contains the attributes of the
   * zone to create.</li>
   * </ul>
   *
   * <br>
   * The optional attributes have been set with the following:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setTransId(String)} - Sets the client transaction identifier
   * </li>
   * <li>{@link #addExtension(EPPCodecComponent)} or
   * {@link #setExtensions(Vector)} - Set extensions to include with the
   * command.
   * </ul>
   *
   *
   * @return {@link EPPRegistryCreateResp} containing the result of the create
   *         command
   *
   * @exception EPPCommandException
   *               Error executing the create command. Use
   *               {@link #getResponse()} to get the associated server error
   *               response.
   */
  public EPPRegistryCreateResp sendCreate() throws EPPCommandException {
    // Pre-condition check
    if (this.zone == null) {
      throw new EPPCommandException("Zone information is required for sendCreate()");
    }

    // Create the command
    EPPRegistryCreateCmd theCommand = new EPPRegistryCreateCmd(this.transId, this.zone);

    // Add extensions
    theCommand.setExtensions(this.extensions);

    // Reset registry attributes
    resetRegistry();

    // Process the command and response
    return (EPPRegistryCreateResp) this.session.processDocument(theCommand, EPPRegistryCreateResp.class);
  }

  /**
   * Sends an update command to the server to update a zone.<br>
   * <br>
   * The required attributes have been set with the following methods:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setZone(EPPRegistryZone)} - Contains the attributes of the
   * zone to update.</li>
   * </ul>
   *
   * <br>
   * The optional attributes have been set with the following:<br>
   * <br>
   *
   * <ul>
   * <li>{@link #setTransId(String)} - Sets the client transaction identifier
   * </li>
   * <li>{@link #addExtension(EPPCodecComponent)} or
   * {@link #setExtensions(Vector)} - Set extensions to include with the
   * command.
   * </ul>
   *
   *
   * @return {@link EPPResponse} containing the result of the update command
   *
   * @exception EPPCommandException
   *               Error executing the update command. Use
   *               {@link #getResponse()} to get the associated server error
   *               response.
   */
  public EPPResponse sendUpdate() throws EPPCommandException {
    // Pre-condition check
    if (this.zone == null) {
      throw new EPPCommandException("Zone information is required for sendUpdate()");
    }

    // Create the command
    EPPRegistryUpdateCmd theCommand = new EPPRegistryUpdateCmd(this.transId, this.zone);

    // Add extensions
    theCommand.setExtensions(this.extensions);

    // Reset registry attributes
    resetRegistry();

    // Process the command and response
    return this.session.processDocument(theCommand, EPPResponse.class);
  }

  /**
   * Gets the session to use to send commands through.
   *
   * @return Active EPP session to send commands through if defined;
   *         {@code null} otherwise.
   */
  public EPPSession getSession() {
    return this.session;
  }

  /**
   * Sets the session to use to send commands through.
   *
   * @param aSession
   *           Active EPP session to send commands through
   */
  public void setSession(EPPSession aSession) {
    this.session = aSession;
  }

  /**
   * Gets the OPTIONAL client transaction identifier.
   *
   * @return The client transaction identifier if set; {@code null} otherwise.
   */
  public String getTransId() {
    return this.transId;
  }

  /**
   * Sets the OPTIONAL client transaction identifier.
   *
   * @param aTransId
   *           Client transaction identifier
   */
  public void setTransId(String aTransId) {
    this.transId = aTransId;
  }

  /**
   * Gets the list of zone names.
   *
   * @return List of zone names
   */
  public List<EPPRegistryZoneName> getZoneList() {
    return this.zoneList;
  }

  /**
   * Sets the list of zone names to use in a command. The mode is set to
   * {@code EPPRegistryInfoCmd.Mode.all}.
   *
   * @param aZoneList
   *           List of zone names
   */
  public void setZoneList(List<EPPRegistryZoneName> aZoneList) {
    if (aZoneList == null) {
      this.zoneList = new ArrayList<EPPRegistryZoneName>();
    }
    else {
      this.zoneList = aZoneList;
      this.infoMode = EPPRegistryInfoCmd.Mode.name;
    }
  }

  /**
   * Add an aLabel zone name to the list of zone names. The mode is set to
   * {@code EPPRegistryInfoCmd.Mode.all}.
   *
   * @param aZone
   *           ALabel zone name to add to the list of zone names.
   */
  public void addZone(String aZone) {
    if (this.zoneList == null) {
      this.zoneList = new ArrayList<EPPRegistryZoneName>();
    }
    this.zoneList.add(new EPPRegistryZoneName(aZone, EPPRegistryZoneName.Form.aLabel));
    this.infoMode = EPPRegistryInfoCmd.Mode.name;
  }

  /**
   * Add a zone name to the list of zone names. The mode is set to
   * {@code EPPRegistryInfoCmd.Mode.name}.
   *
   * @param aZone
   *           Zone name to add to the list of zone names.
   */
  public void addZone(EPPRegistryZoneName aZone) {
    if (this.zoneList == null) {
      this.zoneList = new ArrayList<EPPRegistryZoneName>();
    }
    this.zoneList.add(aZone);
    this.infoMode = EPPRegistryInfoCmd.Mode.name;
  }

  /**
   * Gets the zone scope used with the info all option.
   * 
   * @return info all scope if defined; {@code null} otherwise.
   */
  public EPPRegistryInfoCmd.Scope getAllScope() {
    return this.infoScope;
  }

  /**
   * Sets the zone scope used with the info all mode. The mode is set to
   * {@code EPPRegistryInfoCmd.Mode.all}.
   * 
   * @param aScope
   *           Scope used for the info all mode.
   */
  public void setAllScope(EPPRegistryInfoCmd.Scope aScope) {
    this.infoScope = aScope;
    this.infoMode = EPPRegistryInfoCmd.Mode.all;
  }

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

  /**
   * Gets the info mode.
   *
   * @return The info mode if defined; {@code null} otherwise.
   */
  public EPPRegistryInfoCmd.Mode getInfoMode() {
    return this.infoMode;
  }

  /**
   * Sets the info mode. This is needed when using the
   * {@code EPPRegistryInfoCmd.Mode.system} mode, where the other modes are
   * automatically set using the other associated methods.
   *
   * @param aInfoMode
   *           Info mode to use
   */
  public void setInfoMode(EPPRegistryInfoCmd.Mode aInfoMode) {
    this.infoMode = aInfoMode;
  }

  /**
   * Sets the zone to use with {@link #sendCreate()} or {@link #sendUpdate()}.
   * 
   * @param aZone
   *           The zone to use with {@link #sendCreate()} or
   *           {@link #sendUpdate()}.
   */
  public void setZone(EPPRegistryZone aZone) {
    this.zone = aZone;
  }

  /**
   * Gets the command extensions.
   *
   * @return {@code Vector} of concrete {@code EPPCodecComponent} associated
   *         with the command if exists; {@code null} otherwise.
   */
  public Vector<EPPCodecComponent> getExtensions() {
    return this.extensions;
  }

  /**
   * Sets the command extensions.
   *
   * @param aExtensions
   *           Command extensions associated with the command. Setting to
   *           {@code null} clears the extensions.
   */
  public void setExtensions(Vector<EPPCodecComponent> aExtensions) {
    this.extensions = aExtensions;
  }

  /**
   * Adds a command extension object.
   *
   * @param aExtension
   *           Command extension associated with the command
   */
  public void addExtension(EPPCodecComponent aExtension) {
    if (this.extensions == null) {
      this.extensions = new Vector<EPPCodecComponent>();
    }

    this.extensions.addElement(aExtension);
  }

  /**
   * Gets the response associated with the last command. This method can be
   * used to retrieve the server error response in the catch block of
   * {@code EPPCommandException}.
   *
   * @return {@code EPPResponse} associated with the last command if defined;
   *         {@code null} otherwise.
   */
  public EPPResponse getResponse() {
    return this.session.getResponse();
  }

  /**
   * Resets the interface attributes after successfully creating a command to
   * send.
   */
  private void resetRegistry() {
    this.transId = null;
    this.zoneList = new ArrayList<EPPRegistryZoneName>();
    this.infoMode = null;
    this.infoScope = null;
    this.extensions = null;
    this.zone = null;
  }

}
