/***********************************************************
Copyright (C) 2019 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.loginsecpolicy.v04;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

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;
import com.verisign.epp.util.EPPCatFactory;
import com.verisign.epp.util.EqualityUtil;

/**
 * This class is encoded to the &lt;loginSecPolicy:pw&gt; element that
 * represents the login password format policy. The &lt;loginSecPolicy:pw&gt;
 * element contains the following child elements:<br>
 * <ul>
 * <li>&lt;loginSecPolicy:expression&gt; - The login password format regular
 * expression.</li>
 * <li>&lt;oginSecPolicy:description&gt; - The OPTIONAL human readable
 * description of the login password format policy. The "lang" attribute MAY be
 * present to identify the language of the description if the negotiated value
 * is something other than the default value of "en" (English).</li>
 * </ul>
 */
public class EPPLoginSecPolicyPassword implements EPPCodecComponent {

	/**
	 * Logger
	 */
	private static Logger cat = Logger.getLogger(EPPLoginSecPolicyPassword.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

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

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

	/**
	 * Default Language -- English "en"
	 */
	public final static java.lang.String DEFAULT_LANG = "en";

	/**
	 * XML tag name for the {@code expression} attribute.
	 */
	private final static String ELM_EXPRESSION = "expression";

	/**
	 * XML tag name for the {@code description} attribute.
	 */
	private final static String ELM_DESCRIPTION = "description";

	/**
	 * XML tag name for the {@code specialRules} attribute.
	 */
	private final static String ELM_SPECIAL_RULES = "specialRules";

	/**
	 * XML tag name for the {@code restrictedWords} attribute.
	 */
	private final static String ELM_RESTRICTED_WORDS = "restrictedWords";

	/**
	 * XML attribute name for the {@code restrictedWordsUrl} attribute.
	 */
	private final static String ATTR_RESTRICTED_WORDS_URL = "url";

	/**
	 * XML attribute name used for the OPTIONAL description {@code lang}
	 * attribute.
	 */
	public static final String ATTR_LANG = "lang";

	/**
	 * The login password format regular expression.
	 */
	private String expression = null;

	/**
	 * Language of the description with the default of {@link #DEFAULT_LANG}.
	 */
	private java.lang.String lang = DEFAULT_LANG;

	/**
	 * The OPTIONAL human readable description of the login password format
	 * policy. The "lang" attribute MAY be present to identify the language of
	 * the description if the negotiated value is something other than the
	 * default value of "en" (English).
	 */
	private String description = null;

	/**
	 * The OPTIONAL boolean element, with a default value of "false", that
	 * indicates additional special format rules apply that cannot be represented
	 * in the regular expression. A value of "1" (or "true") means that special
	 * format rules do apply that MUST be described in the
	 * &lt;loginSecPolicy:description&gt; element for manual review. The server
	 * SHOULD represent the most specific regular expression possible with the
	 * &lt;loginSecPolicy:expression&gt; element. A value of "0" (or "false")
	 * means that the &lt;loginSecPolicy:expression&gt; element fully represents
	 * the format requirements.
	 */
	private Boolean specialRules = Boolean.FALSE;

	/**
	 * The OPTIONAL boolean element, with a default value of "false", that
	 * indicates that the server has a list of restricted words that cannot be
	 * used in the password. The optional "url" attribute references a
	 * description of the list of restricted words.
	 */
	private Boolean restrictedWords = Boolean.FALSE;

	/**
	 * The OPTIONAL {@code restrictedWords} "url" attribute references a
	 * description of the list of restricted words.
	 */
	private String restrictedWordsUrl = null;

	/**
	 * Default constructor for {@code EPPLoginSecPolicyPassword}. The expression
	 * must be set prior to calling {@link #encode(Document)}.
	 */
	public EPPLoginSecPolicyPassword() {
	}

	/**
	 * Constructor for {@code EPPLoginSecPolicyPassword} that takes the required
	 * expression attribute.
	 *
	 * @param aExpression
	 *           The login password format regular expression.
	 */
	public EPPLoginSecPolicyPassword(String aExpression) {
		this.expression = aExpression;
	}

	/**
	 * Constructor for {@code EPPLoginSecPolicyPassword} that takes all
	 * attributes.
	 *
	 * @param aExpression
	 *           The login password format regular expression.
	 * @param aLang
	 *           OPTIONAL language of the description with a default of
	 *           {@link #DEFAULT_LANG}. Set to {@link #DEFAULT_LANG} or
	 *           {@code null} to use the default value.
	 * @param aDescription
	 *           Description of the password policy
	 *
	 */
	public EPPLoginSecPolicyPassword(String aExpression, String aLang, String aDescription) {
		this.expression = aExpression;
		this.setLang(aLang);
		this.description = aDescription;
	}

	/**
	 * Encode a DOM Element tree from the attributes of the
	 * {@code EPPLoginSecPolicyPassword} instance.
	 *
	 * @param aDocument
	 *           DOM Document that is being built. Used as an Element factory.
	 *
	 * @return Element Root DOM Element representing the
	 *         {@code EPPLoginSecPolicyPassword} instance.
	 *
	 * @exception EPPEncodeException
	 *               - Unable to encode {@code EPPLoginSecPolicyPassword}
	 *               instance.
	 */
	@Override
	public Element encode(Document aDocument) throws EPPEncodeException {
		if (this.expression == null) {
			throw new EPPEncodeException("expression is null in EPPLoginSecPolicyPassword.encode(Document).");
		}

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

		// Expression
		EPPUtil.encodeString(aDocument, root, this.expression, EPPLoginSecPolicyExtFactory.NS,
		      EPPLoginSecPolicyExtFactory.NS_PREFIX + ":" + ELM_EXPRESSION);

		// Description
		if (this.hasDescription()) {
			Element theDescriptionElm = aDocument.createElementNS(EPPLoginSecPolicyExtFactory.NS,
			      EPPLoginSecPolicyExtFactory.NS_PREFIX + ":" + ELM_DESCRIPTION);

			theDescriptionElm.setAttribute(ATTR_LANG, this.lang);

			Text theDescText = aDocument.createTextNode(this.description);
			theDescriptionElm.appendChild(theDescText);
			root.appendChild(theDescriptionElm);
		}

		// Special Rules
		if (!this.hasSpecialRules()) {
			this.specialRules = Boolean.FALSE;
		}
		EPPUtil.encodeString(aDocument, root, this.specialRules.toString(), EPPLoginSecPolicyExtFactory.NS,
		      EPPLoginSecPolicyExtFactory.NS_PREFIX + ":" + ELM_SPECIAL_RULES);

		// Restricted Words
		if (!this.hasRestrictedWords()) {
			this.restrictedWords = Boolean.FALSE;
		}

		Element theRestrictedWordsElm = aDocument.createElementNS(EPPLoginSecPolicyExtFactory.NS,
		      EPPLoginSecPolicyExtFactory.NS_PREFIX + ":" + ELM_RESTRICTED_WORDS);
		Text theRestrictedWordsValue = aDocument.createTextNode(this.restrictedWords.toString());

		theRestrictedWordsElm.appendChild(theRestrictedWordsValue);
		root.appendChild(theRestrictedWordsElm);

		// Restricted Words URL
		if (this.hasRestrictedWords() && this.restrictedWords == Boolean.TRUE && this.hasRestrictedWordsUrl()) {
			theRestrictedWordsElm.setAttribute(ATTR_RESTRICTED_WORDS_URL, this.restrictedWordsUrl);
		}

		return root;
	}

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

		// Expression
		this.expression = EPPUtil.decodeString(aElement, EPPLoginSecPolicyExtFactory.NS, ELM_EXPRESSION);

		// Description
		Element theDescriptionElm = EPPUtil.getElementByTagNameNS(aElement, EPPLoginSecPolicyExtFactory.NS,
		      ELM_DESCRIPTION);

		if (theDescriptionElm != null) {
			// Lang
			this.setLang(EPPUtil.decodeStringAttr(theDescriptionElm, ATTR_LANG));

			this.description = EPPUtil.getTextContent(theDescriptionElm, true);
			if (this.description != null && this.description.isEmpty()) {
				this.description = null;
			}

		}

		// Special Rules
		this.specialRules = EPPUtil.decodeBoolean(aElement, EPPLoginSecPolicyExtFactory.NS, ELM_SPECIAL_RULES);
		if (this.specialRules == null) {
			this.specialRules = Boolean.FALSE;
		}

		// Restricted Words
		this.restrictedWords = EPPUtil.decodeBoolean(aElement, EPPLoginSecPolicyExtFactory.NS, ELM_RESTRICTED_WORDS);
		if (this.restrictedWords == null) {
			this.restrictedWords = Boolean.FALSE;
		}

		// Restricted Words Url
		if (this.restrictedWords == Boolean.TRUE) {
			Element theRestrictedWordsElm = EPPUtil.getElementByTagNameNS(aElement, EPPLoginSecPolicyExtFactory.NS,
			      ELM_RESTRICTED_WORDS);
			this.restrictedWordsUrl = EPPUtil.decodeStringAttr(theRestrictedWordsElm, ATTR_RESTRICTED_WORDS_URL);
		}
	}

	/**
	 * implements a deep {@code EPPLoginSecPolicyPassword} compare.
	 *
	 * @param aObject
	 *           {@code EPPLoginSecPolicyPassword} instance 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 EPPLoginSecPolicyPassword)) {
			return false;
		}

		EPPLoginSecPolicyPassword theComp = (EPPLoginSecPolicyPassword) aObject;

		// Expression
		if (!EqualityUtil.equals(this.expression, theComp.expression)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): expression not equal");
			return false;
		}

		// Lang
		if (!EqualityUtil.equals(this.lang, theComp.lang)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): lang not equal");
			return false;
		}

		// Description
		if (!EqualityUtil.equals(this.description, theComp.description)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): description not equal");
			return false;
		}

		// Special Rules
		if (!EqualityUtil.equals(this.specialRules, theComp.specialRules)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): specialRules not equal");
			return false;
		}

		// Restricted Words
		if (!EqualityUtil.equals(this.restrictedWords, theComp.restrictedWords)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): restrictedWords not equal");
			return false;
		}

		// Restricted Words Url
		if (!EqualityUtil.equals(this.restrictedWordsUrl, theComp.restrictedWordsUrl)) {
			cat.error("EPPLoginSecPolicyPassword.equals(): restrictedWordsUrl not equal");
			return false;
		}

		return true;
	}

	/**
	 * Clone {@code EPPLoginSecPolicyPassword}.
	 *
	 * @return clone of {@code EPPLoginSecPolicyPassword}
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.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.
	 */
	@Override
	public String toString() {
		return EPPUtil.toString(this);
	}

	/**
	 * Gets the login password format regular expression.
	 *
	 * @return the expression if defined; {@code null} otherwise.
	 */
	public String getExpression() {
		return this.expression;
	}

	/**
	 * Sets the login password format regular expression.
	 *
	 * @param aExpression
	 *           the expression to set
	 */
	public void setExpression(String aExpression) {
		this.expression = aExpression;
	}

	/**
	 * Gets the language of the status description with the default set to
	 * {@link #DEFAULT_LANG}.
	 *
	 * @return Language of description with the default value of
	 *         {@link #DEFAULT_LANG}.
	 */
	public String getLang() {
		return this.lang;
	}

	/**
	 * Sets the language of the status description with the default set to
	 * {@link #DEFAULT_LANG}.
	 *
	 * @param aLang
	 *           Language of description. If set to {@code null}, the value will
	 *           be set to the default of {@link #DEFAULT_LANG}.
	 */
	public void setLang(String aLang) {
		if (aLang == null) {
			this.lang = DEFAULT_LANG;
		}
		else {
			this.lang = aLang;
		}
	}

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

	/**
	 * Gets the status description, which is free form text describing the
	 * rationale for the status.
	 *
	 * @return Status description if defined; {@code null} otherwise.
	 */
	public String getDescription() {
		return this.description;
	}

	/**
	 * Sets the status description, which is free form text describing the
	 * rationale for the status.
	 *
	 * @param aDesc
	 *           Status description. Set to {@code null} if undefined.
	 */
	public void setDescription(String aDesc) {
		this.description = aDesc;
	}

	/**
	 * Is the special rules flag defined?
	 *
	 * @return {@code true} if the special rules flag is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasSpecialRules() {
		return (this.specialRules != null ? true : false);
	}

	/**
	 * Get special rules flag.
	 *
	 * @return flag that indicates whether the password has special rules
	 */
	public Boolean getSpecialRules() {
		return this.specialRules;
	}

	/**
	 * Set special rules flag.
	 *
	 * @param aSpecialRules
	 *           flag that indicates whether the password has special rules
	 */
	public void setSpecialRules(Boolean aSpecialRules) {
		this.specialRules = aSpecialRules;
	}

	/**
	 * Is the restricted words flag defined?
	 *
	 * @return {@code true} if the restricted words flag is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasRestrictedWords() {
		return (this.restrictedWords != null ? true : false);
	}

	/**
	 * Get restricted words flag.
	 *
	 * @return flag that indicates whether the password has restricted words
	 */
	public Boolean getRestrictedWords() {
		return this.restrictedWords;
	}

	/**
	 * Set restricted words flag.
	 *
	 * @param aRestrictedWords
	 *           flag that indicates whether the password has restricted words
	 */
	public void setRestrictedWords(Boolean aRestrictedWords) {
		this.restrictedWords = aRestrictedWords;
	}

	/**
	 * Is the restricted words url defined?
	 *
	 * @return {@code true} if the restricted words url is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasRestrictedWordsUrl() {
		return (this.restrictedWordsUrl != null ? true : false);
	}

	/**
	 * Get restricted words url.
	 *
	 * @return url that defines the restricted words if defined; {@code null}
	 *         otherwise.
	 */
	public String getRestrictedWordsUrl() {
		return this.restrictedWordsUrl;
	}

	/**
	 * Set restricted words url.
	 *
	 * @param aRestrictedWordsUrl
	 *           url that defines the restricted words. Set to {@code null} if
	 *           undefined.
	 */
	public void setRestrictedWordsUrl(String aRestrictedWordsUrl) {
		this.restrictedWordsUrl = aRestrictedWordsUrl;
	}

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

}
