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

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.v01.EPPRegistryCheckCmd;
import com.verisign.epp.codec.registry.v01.EPPRegistryCheckResp;
import com.verisign.epp.codec.registry.v01.EPPRegistryCreateCmd;
import com.verisign.epp.codec.registry.v01.EPPRegistryCreateResp;
import com.verisign.epp.codec.registry.v01.EPPRegistryDeleteCmd;
import com.verisign.epp.codec.registry.v01.EPPRegistryInfoCmd;
import com.verisign.epp.codec.registry.v01.EPPRegistryInfoResp;
import com.verisign.epp.codec.registry.v01.EPPRegistryUpdateCmd;
import com.verisign.epp.codec.registry.v01.EPPRegistryZone;
import com.verisign.epp.codec.registry.v01.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;

	/**
	 * Option used to get all of the zones using the info command with
	 * {@link #sendInfo()}.
	 */
	private boolean allZones = false;

	/**
	 * Option used to get the Registry system information the info command with
	 * {@link #sendInfo()}.
	 */
	private boolean system = false;

	/**
	 * 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 #setAllZones(boolean)} - Set to {@code true} to send the
	 * "get all zone summary information" form of info command. OR</li>
	 * <li>{@link #setSystem(boolean)} - Set to {@code true} to send the
	 * "get system information" form of info command.
	 * </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;

		// Get detailed information for an individual zone form?
		if ((this.zoneList != null) && (!this.zoneList.isEmpty())) {

			// Pre-condition check
			if (this.isAllZones() || this.isSystem()) {
				throw new EPPCommandException("[1] Only one form of info command can be set.");
			}
			if (this.zoneList.size() != 1) {
				throw new EPPCommandException("One zone name is required for sendInfo()");
			}

			theCommand = new EPPRegistryInfoCmd(this.transId, this.zoneList.get(0));
		} // Get all zone summary information form?
		else if (this.isAllZones()) {
			// Pre-condition check
			if (this.isSystem()) {
				throw new EPPCommandException("[2] Only one form of info command can be set.");
			}

			theCommand = new EPPRegistryInfoCmd(this.transId, true, false);

		} // Get system information form?
		else if (this.isSystem()) {

			theCommand = new EPPRegistryInfoCmd(this.transId, false, true);

		} // No information form set?
		else {
			throw new EPPCommandException("None of the info command forms are set");
		}

		// 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.
	 *
	 * @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.allZones = false;
			this.system = false;
		}
	}

	/**
	 * Add an aLabel zone name to the list of zone names.
	 *
	 * @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.allZones = false;
		this.system = false;
	}

	/**
	 * Add a zone name to the list of zone names.
	 *
	 * @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.allZones = false;
		this.system = false;
	}

	/**
	 * Is the all zones boolean set? This is used with the {@link #sendInfo()}
	 * method.
	 *
	 * @return {@code true} if set; {@code false} otherwise.
	 */
	public boolean isAllZones() {
		return this.allZones;
	}

	/**
	 * Sets the all zones boolean flag. Setting the value to {@code true} clears
	 * the zone names list.
	 *
	 * @param aAllZones
	 *           {@code true} to get all of the zones with the
	 *           {@link #sendInfo()} method.
	 */
	public void setAllZones(boolean aAllZones) {
		this.allZones = aAllZones;

		if (aAllZones) {
			this.zoneList = new ArrayList<EPPRegistryZoneName>();
			this.system = false;
		}
	}

	/**
	 * Is the system boolean set? This is used with the {@link #sendInfo()}
	 * method.
	 *
	 * @return {@code true} if set; {@code false} otherwise.
	 */
	public boolean isSystem() {
		return this.system;
	}

	/**
	 * Gets the zone information to use with {@link #sendCreate()} or
	 * {@link #sendUpdate()}.
	 *
	 * @return The zone information if defined; {@code null} otherwise.
	 */
	public EPPRegistryZone getZoneInfo() {
		return this.zone;
	}

	/**
	 * Sets the zone information to use with {@link #sendCreate()} or
	 * {@link #sendUpdate()}.
	 * 
	 * @param aZone
	 *           The zone information 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);
	}

	/**
	 * Sets the system boolean flag. Setting the value to {@code true} clears the
	 * zone names list.
	 *
	 * @param aSystem
	 *           {@code true} to get the Registry system information with the
	 *           {@link #sendInfo()} method.
	 */
	public void setSystem(boolean aSystem) {
		this.system = aSystem;

		if (aSystem) {
			this.zoneList = new ArrayList<EPPRegistryZoneName>();
			this.allZones = false;
		}
	}

	/**
	 * 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.allZones = false;
		this.system = false;
		this.extensions = null;
		this.zone = null;
	}

}
