/***********************************************************
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.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import org.apache.log4j.Logger;

import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.registry.v02.EPPRegistryBatchJob;
import com.verisign.epp.codec.registry.v02.EPPRegistryBatchSchedule;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckResult;
import com.verisign.epp.codec.registry.v02.EPPRegistryContact;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactAddress;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactCity;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactName;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactOrg;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactPostalCode;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactStateProvince;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactStreet;
import com.verisign.epp.codec.registry.v02.EPPRegistryCreateResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryDNSSEC;
import com.verisign.epp.codec.registry.v02.EPPRegistryDS;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomain;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomain.HostModelSupported;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainContact;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainHostLimit;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainNSLimit;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainName;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainPeriod;
import com.verisign.epp.codec.registry.v02.EPPRegistryExceedMaxExDate;
import com.verisign.epp.codec.registry.v02.EPPRegistryExternalHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryGracePeriod;
import com.verisign.epp.codec.registry.v02.EPPRegistryHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryIDN;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryInternalHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryKey;
import com.verisign.epp.codec.registry.v02.EPPRegistryLanguage;
import com.verisign.epp.codec.registry.v02.EPPRegistryMaxSig;
import com.verisign.epp.codec.registry.v02.EPPRegistryMinMaxLength;
import com.verisign.epp.codec.registry.v02.EPPRegistryPendingDeletePeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPendingRestorePeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPostal;
import com.verisign.epp.codec.registry.v02.EPPRegistryRGP;
import com.verisign.epp.codec.registry.v02.EPPRegistryRedemptionPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryRegex;
import com.verisign.epp.codec.registry.v02.EPPRegistryReservedNames;
import com.verisign.epp.codec.registry.v02.EPPRegistryServices;
import com.verisign.epp.codec.registry.v02.EPPRegistryServices.EPPRegistryObjURI;
import com.verisign.epp.codec.registry.v02.EPPRegistryServicesExt;
import com.verisign.epp.codec.registry.v02.EPPRegistryServicesExt.EPPRegistryExtURI;
import com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus;
import com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus.Status;
import com.verisign.epp.codec.registry.v02.EPPRegistryTransferHoldPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneSummary;
import com.verisign.epp.codec.registry.v02.EPPRegistryZone;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneName;
import com.verisign.epp.interfaces.EPPApplicationSingle;
import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.interfaces.EPPSession;
import com.verisign.epp.util.EPPCatFactory;
import com.verisign.epp.util.Environment;
import com.verisign.epp.util.TestThread;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class EPPRegistryTst extends TestCase {
	/**
	 * Handle to the Singleton EPP Application instance (
	 * {@code EPPApplicationSingle})
	 */
	private static EPPApplicationSingle app = EPPApplicationSingle.getInstance();

	/**
	 * Name of configuration file to use for test (default = epp.config).
	 */
	private static String configFileName = "epp.config";

	/**
	 * Logging category
	 */
	private static final Logger cat = Logger.getLogger(EPPRegistryTst.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

	/**
	 * EPP Registry associated with test
	 */
	private EPPRegistry registry = null;

	/**
	 * EPP Session associated with test
	 */
	private EPPSession session = null;

	/**
	 * Current test iteration
	 */
	private int iteration = 0;

	/**
	 * Random instance for the generation of unique objects (hosts, IP addresses,
	 * etc.).
	 */
	private Random rd = new SecureRandom();

	/**
	 * Allocates an {@code EPPRegistryTst} with a logical name. The constructor
	 * will initialize the base class {@code TestCase} with the logical name.
	 *
	 * @param name
	 *           Logical name of the test
	 */
	public EPPRegistryTst(String name) {
		super(name);
	}

	/**
	 * Test the Registry Mapping by doing the following:
	 * <ol>
	 * <li>Create a set of zones.</li>
	 * <li>Update a zone.</li>
	 * <li>Check the availability (existence) of the zones.</li>
	 * <li>Get all summary information for the zones.</li>
	 * <li>Get detailed zone information for some zones.</li>
	 * <li>Get registry system information.</li>
	 * <li>Delete a set of zones.</li>
	 * <li>
	 *
	 * </ol>
	 */
	public void testRegistry() {
		int numIterations = 1;

		String iterationsStr = System.getProperty("iterations");

		if (iterationsStr != null) {
			try {
				numIterations = Integer.parseInt(iterationsStr);
				numIterations = (numIterations < 1) ? 1 : numIterations;
			}
			catch (Exception e) {
				numIterations = 1;
			}
		}

		printStart("Test Suite");

		registryCreate("com", true);
		registryCreate("EXAMPLE1", true);
		// Create existing tld will result in error
		registryCreate("EXAMPLE1", false);
		registryCreate("EXAMPLE2", true);
		registryCreate("EXAMPLE3", true);

		registryUpdate("EXAMPLE1", true);
		registryUpdate("EXAMPLE2", true);
		registryUpdate("EXAMPLE3", true);
		// Updating non-existing TLD will result in error
		registryUpdate("EXAMPLE4", false);

		Map zonesAvail = new HashMap();
		zonesAvail.put("EXAMPLE1", Boolean.FALSE);
		zonesAvail.put("EXAMPLE2", Boolean.FALSE);
		zonesAvail.put("EXAMPLE3", Boolean.FALSE);
		// newtld4 is available
		zonesAvail.put("EXAMPLE4", Boolean.TRUE);
		registryCheck(zonesAvail);

		// All Info
		registryInfo(EPPRegistryInfoCmd.Mode.all, null, EPPRegistryInfoCmd.Scope.both);

		// Zone Detail Info
		registryInfo(EPPRegistryInfoCmd.Mode.name, "EXAMPLE2", null);
		registryInfo(EPPRegistryInfoCmd.Mode.name, "com", null);

		// System Info
		registryInfo(EPPRegistryInfoCmd.Mode.system, null, null);

		registryDelete("EXAMPLE1", true);
		registryDelete("EXAMPLE1", false);
		registryDelete("EXAMPLE10", false);

		printEnd("Test Suite");
	}

	/**
	 * Unit test of {@code EPPRegistry.sendCheck}.
	 * 
	 * @param aZoneNames
	 *           Zone names to check
	 */
	public void registryCheck(Map aZoneNames) {
		printStart("registryCheck");

		EPPRegistryCheckResp response;
		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.print("registryCheck:");
			this.registry.setTransId("ABC-12345-XYZ");

			for (Iterator it = aZoneNames.entrySet().iterator(); it.hasNext();) {
				String name = (String) ((Entry) it.next()).getKey();
				System.out.print(" " + name + " ");
				this.registry.addZone(name);
			}
			System.out.println("");

			response = this.registry.sendCheck();

			System.out.println("registryCheck: Response = [" + response + "]\n\n");

			for (Iterator it = aZoneNames.entrySet().iterator(); it.hasNext();) {
				Entry entry = (Entry) it.next();
				String name = (String) entry.getKey();
				Boolean available = (Boolean) entry.getValue();
				inner: for (Object element : response.getCheckResults()) {
					EPPRegistryCheckResult result = (EPPRegistryCheckResult) element;
					if (result.getName().equals(name)) {
						if (result.isAvailable().booleanValue() == available.booleanValue()) {
							break inner;
						}
						else {
							fail("Expected availability for tld \"" + name + "\": " + available.booleanValue() + ", but got: "
							      + result.isAvailable());
						}
					}
				}
			}

			assertTrue(response != null && response.isSuccess());
		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		printEnd("registryCheck");
	}

	/**
	 * Unit test of {@code EPPRegistry.sendCreate}.
	 * 
	 * @param aZoneName
	 *           Name of zone to create
	 * @param aShouldSucceed
	 *           is the test expected to succeed?
	 */
	public void registryCreate(String aZoneName, boolean aShouldSucceed) {
		printStart("registryCreate");

		EPPRegistryCreateResp response = null;
		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("registryCreate: " + aZoneName);
			this.registry.setTransId("ABC-12345-XYZ");
			this.registry.setZone(buildZoneInfo(new EPPRegistryZoneName(aZoneName), true, true, true));

			response = this.registry.sendCreate();
			System.out.println("registryCreate: Response = [" + response + "]\n\n");

			if (aShouldSucceed) {
				assertTrue(response != null && response.isSuccess());
			}
			else {
				fail("Expecting error in response");
			}
		}
		catch (EPPCommandException e) {
			if (aShouldSucceed) {
				handleException(e);
			}
		}

		printEnd("registryCreate");
	}

	/**
	 * Unit test of {@code EPPRegistry.sendDelete}.
	 * 
	 * @param aZoneName
	 *           Name of zone to delete
	 * @param aShouldSucceed
	 *           is the test expected to succeed?
	 */
	public void registryDelete(String aZoneName, boolean aShouldSucceed) {
		printStart("registryDelete");

		EPPResponse response = null;
		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("registryDelete: " + aZoneName);
			this.registry.setTransId("ABC-12345-XYZ");
			this.registry.addZone(aZoneName);

			response = this.registry.sendDelete();
			System.out.println("registryDelete: Response = [" + response + "]\n\n");

			if (aShouldSucceed) {
				assertTrue(response != null && response.isSuccess());
			}
			else {
				fail("Expecting error in response");
			}
		}
		catch (EPPCommandException e) {
			if (aShouldSucceed) {
				handleException(e);
			}
		}

		printEnd("registryDelete");
	}

	/**
	 * Unit test of {@code EPPRegistry.sendUpdate}.
	 * 
	 * @param aZoneName
	 *           Name of zone to update
	 * @param aShouldSucceed
	 *           is the test expected to succeed?
	 */
	public void registryUpdate(String aZoneName, boolean aShouldSucceed) {
		printStart("registryUpdate");

		EPPResponse response = null;
		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("registryUpdate: " + aZoneName);
			this.registry.setTransId("ABC-12345-XYZ");
			this.registry.setZone(this.buildZoneInfo(new EPPRegistryZoneName(aZoneName), true, true, true));

			response = this.registry.sendUpdate();
			System.out.println("registryUpdate: Response = [" + response + "]\n\n");

			if (aShouldSucceed) {
				assertTrue(response != null && response.isSuccess());
			}
			else {
				fail("Expecting error in response");
			}
		}
		catch (EPPCommandException e) {
			if (aShouldSucceed) {
				handleException(e);
			}
		}

		printEnd("registryUpdate");
	}

	/**
	 * Test for the registry info command that supports the three forms of
	 * getting detailed information for an individual zone by setting the
	 * {@code aZoneName} parameter to a non-{@code null} value, getting a summary
	 * of all zones by setting the {@code aScope} parameter to a non-{@code null}
	 * value, and getting the system information by setting the {@code aSystem}
	 * parameter to true. There can be only one form used as a time.
	 *
	 * @param aMode
	 *           One of the support info modes (
	 *           {@code EPPRegistryInfoCmd.Mode.name},
	 *           {@code EPPRegistryInfoCmd.Mode.all}, or
	 *           {@code EPPRegistryInfoCmd.Mode.system})
	 * @param aZoneName
	 *           Used with the "name" info mode that represents the zone name to
	 *           query. Set to {@code null} with the
	 *           {@code EPPRegistryInfoCmd.Mode.all} or
	 *           {@code EPPRegistryInfoCmd.Mode.system} modes.
	 * @param aScope
	 *           Used with the "all" info mode that represents the scope of the
	 *           zones to query. Set to {@code null} with the
	 *           {@code EPPRegistryInfoCmd.Mode.name} or
	 *           {@code EPPRegistryInfoCmd.Mode.system} modes.
	 */
	public void registryInfo(EPPRegistryInfoCmd.Mode aMode, String aZoneName, EPPRegistryInfoCmd.Scope aScope) {
		printStart("registryInfo");

		EPPRegistryInfoResp response = null;

		try {

			switch (aMode) {
				case name:
					if (aZoneName == null) {
						Assert.fail("aZoneName must not be null with the name info mode");
					}
					this.registry.setTransId("ABC-12345-XYZ");
					this.registry.addZone(aZoneName);
					response = this.registry.sendInfo();

					assertTrue(response != null && response.isSuccess());

					assertTrue(response.getZoneList() == null && response.getZoneInfo() != null);
					System.out.println("Zone accessible = " + response.getZoneInfo().isAccessible());
					EPPRegistryZone info = response.getZoneInfo().getZone();
					System.out.println("Zone name: " + info.getName());
					if (info.getServices() != null) {
						System.out.println("Services:");
						for (Object element : info.getServices().getObjURIs()) {
							EPPRegistryObjURI objUri = (EPPRegistryObjURI) element;
							System.out.println("\tobjURI: " + objUri.getUri() + ", required: " + objUri.getRequired());
						}
					}
					if (info.getServices() != null && info.getServices().getExtension() != null) {
						System.out.println("Services extension:");
						for (Iterator it = info.getServices().getExtension().getExtURIs().iterator(); it.hasNext();) {
							EPPRegistryExtURI extUri = (EPPRegistryExtURI) it.next();
							System.out.println("\textURI: " + extUri.getUri() + ", required: " + extUri.getRequired());
						}
					}
					System.out.println("crId: " + info.getCreatedBy());
					System.out.println("crDate: " + info.getCreatedDate());
					System.out.println("upId: " + info.getLastUpdatedBy());
					System.out.println("upDate: " + info.getLastUpdatedDate());

					if (info.hasBatchJobs()) {
						List<EPPRegistryBatchJob> jobs = info.getBatchJobs();
						for (EPPRegistryBatchJob currJob : jobs) {
							System.out.println("batchJob: " + currJob);
						}
					}
					info.getBatchJobs();

					EPPRegistryDomain domain = info.getDomain();
					assertTrue(domain != null);
					System.out.println("Domain: " + domain);
					EPPRegistryHost host = info.getHost();
					assertTrue(host != null);
					EPPRegistryContact contact = info.getContact();
					assertTrue(contact != null);
					break;
				case all:
					if (aScope == null) {
						Assert.fail("aScope must not be null with the all info mode");
					}

					this.registry.setTransId("ABC-12345-XYZ");
					this.registry.setAllScope(EPPRegistryInfoCmd.Scope.both);
					response = this.registry.sendInfo();

					assertTrue(response != null && response.isSuccess());

					assertTrue(response.getZoneList() != null && response.getZoneList().getZoneList() != null
					      && response.getZoneInfo() == null && response.getZoneList().getZoneList().size() > 0);
					System.out.println("All TLDs: ");
					for (Iterator it = response.getZoneList().getZoneList().iterator(); it.hasNext();) {
						EPPRegistryZoneSummary tld = (EPPRegistryZoneSummary) it.next();
						assertTrue(tld.getName().getName() != null && tld.getName().getName().length() > 0);
						assertTrue(tld.getCreateDate() != null);
						System.out.print(tld.getName() + "\tcreated on " + tld.getCreateDate());
						if (tld.getUpdateDate() != null) {
							System.out.println("\tupdated on " + tld.getUpdateDate());
						}
						else {
							System.out.println();
						}
					}

					break;
				case system:
					this.registry.setTransId("ABC-12345-XYZ");
					this.registry.setInfoMode(aMode);
					response = this.registry.sendInfo();

					assertTrue(response != null && response.isSuccess());

					System.out.println("System Info Response = " + response);

			}

			printEnd("registryInfo");
		}
		catch (EPPCommandException e) {
			handleException(e);
		}
	}

	/**
	 * Make a pseudo random zone name.
	 *
	 * @return pseudo random zone name
	 */
	public String makeZoneName() {
		int len = this.rd.nextInt(15);
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < len; i++) {
			sb.append('a' + this.rd.nextInt(26));
		}
		return sb.toString();
	}

	/**
	 * Handle an {@code EPPCommandException}, which can be either a server
	 * generated error or a general exception. If the exception was caused by a
	 * server error, "Server Error :&lt;Response XML&gt;" will be specified. If
	 * the exception was caused by a general algorithm error, "General Error
	 * :&lt;Exception Description&gt;" will be specified.
	 *
	 * @param aException
	 *           Exception thrown during test
	 */
	public void handleException(Exception aException) {
		EPPResponse theResponse = null;
		if (aException instanceof EPPCommandException) {
			theResponse = ((EPPCommandException) aException).getResponse();
		}

		aException.printStackTrace();

		// Is a server specified error?
		if ((theResponse != null) && (!theResponse.isSuccess())) {
			Assert.fail("Server Error : " + theResponse);
		}

		else {
			Assert.fail("General Error : " + aException);
		}
	}

	public static Test suite() {
		TestSuite suite = new TestSuite(EPPRegistryTst.class);

		String theConfigFileName = System.getProperty("EPP.ConfigFile");
		if (theConfigFileName != null) {
			configFileName = theConfigFileName;
		}

		try {
			app.initialize(configFileName);
		}

		catch (EPPCommandException e) {
			e.printStackTrace();

			Assert.fail("Error initializing the EPP Application: " + e);
		}

		return suite;
	}

	/**
	 * Setup the test by establishing an EPP session with the server.
	 */
	@Override
	protected void setUp() {
		try {
			String theSessionClassName = System.getProperty("EPP.SessionClass");

			if (theSessionClassName != null) {
				try {
					Class theSessionClass = Class.forName(theSessionClassName);

					if (!EPPSession.class.isAssignableFrom((theSessionClass))) {
						Assert.fail(theSessionClassName + " is not a subclass of EPPSession");
					}

					this.session = (EPPSession) theSessionClass.getDeclaredConstructor().newInstance();
				}
				catch (Exception ex) {
					Assert.fail("Exception instantiating EPP.SessionClass value " + theSessionClassName + ": " + ex);
				}
			}
			else {
				this.session = new EPPSession();
			}

			this.session.setClientID(Environment.getProperty("EPP.Test.clientId", "ClientX"));
			this.session.setPassword(Environment.getProperty("EPP.Test.password", "foo-BAR2"));
		}

		catch (Exception e) {
			e.printStackTrace();

			Assert.fail("Error initializing the session: " + e);
		}

		initSession();

		// System.out.println("out init");
		this.registry = new EPPRegistry(this.session);
	}

	/**
	 * JUNIT {@code tearDown}, which currently does nothing.
	 */
	@Override
	protected void tearDown() {
		endSession();
	}

	/**
	 * Unit test of {@code EPPSession.initSession}. The session attribute is
	 * initialized with the attributes defined in the EPP sample files.
	 */
	private void initSession() {
		printStart("initSession");

		// Set attributes for initSession
		this.session.setTransId("ABC-12345-XYZ");

		this.session.setVersion("1.0");

		this.session.setLang("en");

		// Initialize the session
		try {
			this.session.initSession();
		}

		catch (EPPCommandException e) {
			EPPResponse response = this.session.getResponse();

			// Is a server specified error?
			if ((response != null) && (!response.isSuccess())) {
				Assert.fail("Server Error : " + response);
			}
			else {
				e.printStackTrace();

				Assert.fail("initSession Error : " + e);
			}
		}

		printEnd("initSession");
	}

	/**
	 * Unit test of {@code EPPSession.endSession}. The session with the EPP
	 * Server will be terminated.
	 */
	private void endSession() {
		printStart("endSession");

		this.session.setTransId("ABC-12345-XYZ");

		// End the session
		try {
			this.session.endSession();
		}

		catch (EPPCommandException e) {
			EPPResponse response = this.session.getResponse();

			// Is a server specified error?
			if ((response != null) && (!response.isSuccess())) {
				Assert.fail("Server Error : " + response);
			}

			else // Other error
			{
				e.printStackTrace();

				Assert.fail("initSession Error : " + e);
			}
		}

		printEnd("endSession");
	}

	/**
	 * Print the start of a test with the {@code Thread} name if the current
	 * thread is a {@code TestThread}.
	 *
	 * @param aTest
	 *           name for the test
	 */
	private void printStart(String aTest) {
		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ", iteration " + this.iteration + ": ");

			cat.info(Thread.currentThread().getName() + ", iteration " + this.iteration + ": " + aTest + " Start");
		}

		System.out.println("Start of " + aTest);

		System.out.println("****************************************************************\n");
	}

	/**
	 * Print the end of a test with the {@code Thread} name if the current thread
	 * is a {@code TestThread}.
	 *
	 * @param aTest
	 *           name for the test
	 */
	private void printEnd(String aTest) {
		System.out.println("****************************************************************");

		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ", iteration " + this.iteration + ": ");

			cat.info(Thread.currentThread().getName() + ", iteration " + this.iteration + ": " + aTest + " End");
		}

		System.out.println("End of " + aTest);

		System.out.println("\n");
	}

	/**
	 * Creates a populated {@link EPPRegistryZone} instance given a zone name.
	 *
	 * @param aZone
	 *           Zone name to create a populated {@link EPPRegistryZone} instance
	 *           for.
	 *
	 * @param aContactPolicy
	 *           Include the contact policy? If {@code true} the contact policy
	 *           information will be included in the response; otherwise there
	 *           will be no contact policy information.
	 * 
	 * @param aDsDataInterface
	 *           Is the DNSSEC DS data interface supported? If {@code true} the
	 *           DS data interface is supported; otherwise the Key data interface
	 *           is supported.
	 * 
	 * @param aHostObjModel
	 *           Is the host object model used in RFC 5731? If {@code true} then
	 *           the host object model is used; otherwise the host attribute
	 *           model is used.
	 *
	 * 
	 * @return Populated {@link EPPRegistryZone} instance
	 */
	public static EPPRegistryZone buildZoneInfo(EPPRegistryZoneName aZone, boolean aContactPolicy,
	      boolean aDsDataInterface, boolean aHostObjModel) {

		EPPRegistryZone theZoneInfo = new EPPRegistryZone(aZone);

		theZoneInfo.setDomain(buildDomain(aDsDataInterface, aHostObjModel));
		theZoneInfo.setHost(buildHost(aHostObjModel));
		if (aContactPolicy) {
			theZoneInfo.setContact(buildContact());
		}

		EPPRegistryServices services = new EPPRegistryServices();
		services.addObjURI(new EPPRegistryObjURI("urn:ietf:params:xml:ns:registry-1.2", Boolean.TRUE));
		services.addObjURI(new EPPRegistryObjURI("urn:ietf:params:xml:ns:registry-1.1", Boolean.FALSE));
		EPPRegistryServicesExt svcExt = new EPPRegistryServicesExt();
		services.setExtension(svcExt);
		svcExt.addExtURI(new EPPRegistryExtURI("urn:ietf:params:xml:ns:registry-1.2", Boolean.TRUE));
		svcExt.addExtURI(new EPPRegistryExtURI("urn:ietf:params:xml:ns:registry-1.1", Boolean.FALSE));
		theZoneInfo.setServices(services);

		theZoneInfo.setCreatedBy("crId");
		theZoneInfo.setCreatedDate(new Date());
		theZoneInfo.setUnsupportedData(EPPRegistryZone.UnsupportedData.fail);

		EPPRegistryBatchJob batchJob = new EPPRegistryBatchJob("localTzBatch",
		      "Batch with multiple local time schedules (name and offset)");
		batchJob.addSchedule(new EPPRegistryBatchSchedule("04:00:00", "EST5EDT"));
		batchJob.addSchedule(new EPPRegistryBatchSchedule("07:00:00-05:00", null));
		theZoneInfo.addBatchJob(batchJob);

		batchJob = new EPPRegistryBatchJob("multiBatchSchedule", "Batch with multiple UTC schedules");
		batchJob.addSchedule(new EPPRegistryBatchSchedule("12:00:00Z", null));
		batchJob.addSchedule(new EPPRegistryBatchSchedule("12:00:00Z", EPPRegistryBatchSchedule.DayOfWeek.SUNDAY, null));
		batchJob.addSchedule(new EPPRegistryBatchSchedule("17:00:00Z", 15, null));
		theZoneInfo.addBatchJob(batchJob);

		return theZoneInfo;
	}

	/**
	 * Build a populated domain object policy object to include in a zone with
	 * the DS Data Interface.
	 *
	 * @return A populated {@link EPPRegistryDomain} instance
	 */
	public static EPPRegistryDomain buildDomain() {
		return buildDomain(true, true);
	}

	/**
	 * Build a populated domain object policy object to include in a zone.
	 * 
	 * @param aDsDataInterface
	 *           Is the DNSSEC DS data interface supported? IF {@code true} the
	 *           DS data interface is supported; otherwise the Key data interface
	 *           is supported.
	 * @param aHostObjModel
	 *           Is the host object model used in RFC 5731? If {@code true} then
	 *           the host object model is used; otherwise the host attribute
	 *           model is used.
	 *
	 * @return A populated {@link EPPRegistryDomain} instance
	 */
	public static EPPRegistryDomain buildDomain(boolean aDsDataInterface, boolean aHostObjModel) {
		EPPRegistryDomain domain = new EPPRegistryDomain();

		List domainNames = new ArrayList();
		EPPRegistryDomainName domainName = new EPPRegistryDomainName();
		domainName.setLevel(Integer.valueOf(2));
		domainName.setMinLength(Integer.valueOf(5));
		domainName.setMaxLength(Integer.valueOf(50));
		domainName.setAlphaNumStart(Boolean.valueOf(true));
		domainName.setAlphaNumEnd(Boolean.valueOf(false));
		domainName.setALabelSupported(Boolean.valueOf(true));
		domainName.setNameRegex(new EPPRegistryRegex("^[a-zA-Z\\d][a-zA-Z\\d\\-]{4,49}$",
		      "5 to 50 DNS characters starting with alphanumeric"));

		EPPRegistryReservedNames reservedNames = new EPPRegistryReservedNames();
		List rNames = new ArrayList();
		reservedNames.setReservedNames(rNames);
		rNames.add("reserved1");
		rNames.add("reserved2");
		// reservedNames.setReservedNameURI("http://example.com/reservedNames");

		domainName.setReservedNames(reservedNames);
		domainNames.add(domainName);

		try {
			domainName = (EPPRegistryDomainName) domainName.clone();
			domainName.setLevel(Integer.valueOf(3));
			domainName.getReservedNames().setReservedNames(new ArrayList());
			domainName.getReservedNames().setReservedNameURI("http://testrn.vrsn.com");
			domainNames.add(domainName);
		}
		catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}

		domain.setDomainNames(domainNames);

		EPPRegistryIDN idn = new EPPRegistryIDN();
		idn.setIdnVersion("1.1");
		idn.setIdnaVersion("2008");
		idn.setUnicodeVersion("6.0");
		idn.addLanguage(new EPPRegistryLanguage("CHI", "http://www.iana.org/domains/idn-tables/tables/com_zh_1.1.txt",
		      EPPRegistryLanguage.VariantStrategy.restricted));
		idn.addLanguage(new EPPRegistryLanguage("LATN", "http://www.iana.org/domains/idn-tables/tables/eu_latn_1.0.html",
		      EPPRegistryLanguage.VariantStrategy.blocked));
		idn.setCommingleAllowed(Boolean.TRUE);
		domain.setIdn(idn);

		domain.setPremiumSupport(Boolean.valueOf(true));
		domain.setContactsSupported(Boolean.valueOf(false));

		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.admin, 1, 4));
		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.billing, 2, 5));
		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.tech, 3, 6));

		domain.setNameServerLimit(new EPPRegistryDomainNSLimit(1, 16));

		if (aHostObjModel) {
			domain.setChildHostLimit(new EPPRegistryDomainHostLimit(0, null));
		}

		domain.addPeriod(new EPPRegistryDomainPeriod("create", Boolean.TRUE));
		domain.addPeriod(new EPPRegistryDomainPeriod("renew", 1, EPPRegistryPeriodType.Unit.y, 10,
		      EPPRegistryPeriodType.Unit.y, 2, EPPRegistryPeriodType.Unit.y));
		domain.addPeriod(new EPPRegistryDomainPeriod("transfer", 1, EPPRegistryPeriodType.Unit.y, 8,
		      EPPRegistryPeriodType.Unit.y, 3, EPPRegistryPeriodType.Unit.y));

		domain.addExceedMaxExDate(new EPPRegistryExceedMaxExDate(EPPRegistryExceedMaxExDate.Policy.fail, "renew"));
		domain.addExceedMaxExDate(new EPPRegistryExceedMaxExDate(EPPRegistryExceedMaxExDate.Policy.clip, "transfer"));

		domain.setTransferHoldPeriod(new EPPRegistryTransferHoldPeriodType(1, EPPRegistryPeriodType.Unit.y));

		domain.addGracePeriod(new EPPRegistryGracePeriod("create", 1, EPPRegistryPeriodType.Unit.m));
		domain.addGracePeriod(new EPPRegistryGracePeriod("renew", 2, EPPRegistryPeriodType.Unit.h));
		domain.addGracePeriod(new EPPRegistryGracePeriod("transfer", 3, EPPRegistryPeriodType.Unit.d));

		EPPRegistryRGP rgp = new EPPRegistryRGP();
		rgp.setPendingDeletePeriod(new EPPRegistryPendingDeletePeriodType(1, EPPRegistryPeriodType.Unit.m));
		rgp.setRedemptionPeriod(new EPPRegistryRedemptionPeriodType(1, EPPRegistryPeriodType.Unit.m));
		rgp.setPendingRestorePeriod(new EPPRegistryPendingRestorePeriodType(1, EPPRegistryPeriodType.Unit.m));
		domain.setRgp(rgp);

		EPPRegistryDNSSEC dnssec = new EPPRegistryDNSSEC();

		// DS Data Interface?
		if (aDsDataInterface) {
			EPPRegistryDS ds = new EPPRegistryDS(0, 13);
			ds.addAlgorithm(3);
			ds.addDigestType(1);
			dnssec.setDs(ds);
		} // Key Data Interface
		else {
			EPPRegistryKey key = new EPPRegistryKey(0, 13);
			key.addFlags(257);
			key.addProtocol(3);
			key.addAlgorithm(3);
			dnssec.setKey(key);
		}

		dnssec.setMaxSigLife(new EPPRegistryMaxSig(true, 1, 2, 3));
		dnssec.setUrgent(Boolean.TRUE);

		domain.setDnssec(dnssec);
		domain.setMaxCheckDomain(Integer.valueOf(12));

		EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
		supportedStatus.addStatus(Status.DOMAIN_CLIENTDELETEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERDELETEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTHOLD);
		supportedStatus.addStatus(Status.DOMAIN_SERVERHOLD);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTRENEWPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERRENEWPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_INACTIVE);
		supportedStatus.addStatus(Status.DOMAIN_OK);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGCREATE);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGDELETE);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGRENEW);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGTRANSFER);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGUPDATE);
		domain.setSupportedStatus(supportedStatus);

		domain.setAuthInfoRegex(new EPPRegistryRegex("^.*$", "exp"));
		domain.setNullAuthInfoSupported(Boolean.FALSE);
		if (aHostObjModel) {
			domain.setHostModelSupported(HostModelSupported.hostObj);
		}
		else {
			domain.setHostModelSupported(HostModelSupported.hostAttr);
		}

		return domain;
	}

	/**
	 * Build a populated contact object policy object to include in a zone.
	 *
	 * @return A populated {@link EPPRegistryContact} instance
	 */
	public static EPPRegistryContact buildContact() {
		EPPRegistryContact contact = new EPPRegistryContact();

		contact.setContactIdRegex(new EPPRegistryRegex("^.*$"));
		contact.setContactIdPrefix("EX");
		contact.setSharePolicy(EPPRegistryContact.SharePolicy.perZone);

		contact.setIntPostalInfoTypeSupport(EPPRegistryContact.PostalInfoTypeSupport.locOrIntSupport);

		contact.setAuthInfoRegex(new EPPRegistryRegex("^.*$", "exp"));

		contact.setMaxCheckContact(Integer.valueOf(15));

		EPPRegistryPostal postalInfo = new EPPRegistryPostal();
		postalInfo.setLocCharRegex(new EPPRegistryRegex("^.*$"));
		postalInfo.setName(new EPPRegistryContactName(5, 15));
		postalInfo.setOrg(new EPPRegistryContactOrg(2, 12));
		postalInfo.setVoiceRequired(Boolean.TRUE);
		postalInfo.setEmailRegex(new EPPRegistryRegex("^.+\\..+$"));

		EPPRegistryContactAddress address = new EPPRegistryContactAddress();
		address.setStreet(new EPPRegistryContactStreet(2, 12, 0, 3));
		address.setCity(new EPPRegistryContactCity(5, 15));
		address.setStateProvince(new EPPRegistryContactStateProvince(1, 11));
		address.setPostalCode(new EPPRegistryContactPostalCode(2, 12));

		postalInfo.setAddress(address);
		postalInfo.setVoiceRequired(Boolean.TRUE);
		postalInfo.setVoiceExt(new EPPRegistryMinMaxLength(5, 15));
		postalInfo.setFaxExt(new EPPRegistryMinMaxLength(5, 15));

		contact.setMaxCheckContact(Integer.valueOf(5));

		contact.setPostalInfo(postalInfo);

		EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
		supportedStatus.addStatus(Status.CONTACT_CLIENTDELETEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERDELETEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_CLIENTTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_CLIENTUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_LINKED);
		supportedStatus.addStatus(Status.CONTACT_OK);
		supportedStatus.addStatus(Status.CONTACT_PENDINGCREATE);
		supportedStatus.addStatus(Status.CONTACT_PENDINGDELETE);
		supportedStatus.addStatus(Status.CONTACT_PENDINGTRANSFER);
		supportedStatus.addStatus(Status.CONTACT_PENDINGUPDATE);
		contact.setSupportedStatus(supportedStatus);

		return contact;
	}

	/**
	 * Build a populated host object policy object to include in a zone.
	 * 
	 * @param aHostObjModel
	 *           Is the host object model used in RFC 5731? If {@code true} then
	 *           the host object model is used; otherwise the host attribute
	 *           model is used.
	 *
	 * @return A populated {@link EPPRegistryHost} instance
	 */
	public static EPPRegistryHost buildHost(boolean aHostObjModel) {
		EPPRegistryHost host = new EPPRegistryHost();

		if (aHostObjModel) {
			host.setInternal(
			      new EPPRegistryInternalHost(5, 15, EPPRegistryInternalHost.SharePolicy.perZone, Boolean.FALSE));
			host.setExternal(
			      new EPPRegistryExternalHost(2, 12, EPPRegistryExternalHost.SharePolicy.perZone, Boolean.FALSE));

			host.setMaxCheckHost(Integer.valueOf(15));

			EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
			supportedStatus.addStatus(Status.HOST_CLIENTDELETEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_SERVERDELETEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_CLIENTUPDATEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_SERVERUPDATEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_LINKED);
			supportedStatus.addStatus(Status.HOST_OK);
			supportedStatus.addStatus(Status.HOST_PENDINGCREATE);
			supportedStatus.addStatus(Status.HOST_PENDINGDELETE);
			supportedStatus.addStatus(Status.HOST_PENDINGTRANSFER);
			supportedStatus.addStatus(Status.HOST_PENDINGUPDATE);
			host.setSupportedStatus(supportedStatus);
		} // Host Attribute Model
		else {
			host.setInternal(new EPPRegistryInternalHost(5, 15, null, Boolean.FALSE));
			host.setExternal(new EPPRegistryExternalHost(2, 12, null, Boolean.FALSE));
		}

		host.setNameRegex(new EPPRegistryRegex("^.*$"));
		host.addInvalidIP("http://www.example.com/invalidip-1.txt");
		host.addInvalidIP("http://www.example.com/invalidip-2.txt");

		return host;
	}

}
