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

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

package com.verisign.epp.codec.loginsec.v1_0;

import java.util.Date;

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;

/**
 * Login Security Event that identifies a security warning or error for the
 * client to address. There may be many {@code EPPLoginSecEvent} instances
 * in the {@link EPPLoginSecData} extension to the
 * {@link com.verisign.epp.codec.gen.EPPResponse}. The
 * {@code EPPLoginSecEvent} includes a set of generic attributes and an
 * extensible set of types to support a large set of possible security events.
 */
public class EPPLoginSecEvent implements EPPCodecComponent {

	/**
	 * Log4j category for logging
	 */
	private static Logger cat = Logger.getLogger(EPPLoginSecEvent.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

	/**
	 * The default language of the description, which is "en".
	 */
	public static final String DEFAULT_LANG = "en";

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

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

	/**
	 * Attribute name for type attribute.
	 */
	private final static String ATTR_TYPE = "type";

	/**
	 * Attribute name for name attribute.
	 */
	private final static String ATTR_TYPE_NAME = "name";

	/**
	 * Attribute name for level attribute.
	 */
	private final static String ATTR_LEVEL = "level";

	/**
	 * Attribute name for exDate attribute.
	 */
	private final static String ATTR_EXDATE = "exDate";

	/**
	 * Attribute name for value attribute.
	 */
	private final static String ATTR_VALUE = "value";

	/**
	 * Attribute name for duration attribute.
	 */
	private final static String ATTR_DURATION = "duration";

	/**
	 * Attribute name for lang attribute.
	 */
	private final static String ATTR_LANG = "lang";

	/**
	 * Event type
	 */
	private EventType type;

	/**
	 * OPTIONAL type name attribute used when the type is set to
	 * {@link EventType#CUSTOM}.
	 */
	private String typeName;

	/**
	 * Event level
	 */
	private EventLevel level;

	/**
	 * OPTIONAL event expiry date
	 */
	private Date exDate;

	/**
	 * OPTIONAL value attribute.
	 */
	private String value;

	/**
	 * OPTIONAL duration attribute.
	 */
	private String duration;

	/**
	 * OPTIONAL lang attribute.
	 */
	private String lang = DEFAULT_LANG;

	/**
	 * OPTIONAL description text for event.
	 */
	private String description;

	/**
	 * Default constructor for {@code EPPLoginSecEvent}.
	 */
	public EPPLoginSecEvent() {
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes the required
	 * attributes of type and level.
	 * 
	 * @param aType
	 *           Event type
	 * @param aLevel
	 *           Event level
	 */
	public EPPLoginSecEvent(EventType aType, EventLevel aLevel) {
		this.type = aType;
		this.level = aLevel;
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes required attributes
	 * and a description.
	 * 
	 * @param aType
	 *           Event type
	 * @param aLevel
	 *           Event level
	 * @param aDescription
	 *           Option description of event. Set to {@code null} for no
	 *           description.
	 * 
	 */
	public EPPLoginSecEvent(EventType aType, EventLevel aLevel, String aDescription) {
		this(aType, aLevel);

		this.description = aDescription;
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes likely attributes for
	 * an expiry event.
	 * 
	 * @param aType
	 *           Event type
	 * @param aLevel
	 *           Event level
	 * @param aExDate
	 *           Expiration date of event
	 * @param aDescription
	 *           Option description of event. Set to {@code null} for no
	 *           description.
	 * 
	 */
	public EPPLoginSecEvent(EventType aType, EventLevel aLevel, Date aExDate, String aDescription) {
		this(aType, aLevel);

		this.exDate = aExDate;
		this.description = aDescription;
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes likely attribute for
	 * a value event.
	 * 
	 * @param aType
	 *           Event type
	 * @param aLevel
	 *           Event level
	 * @param aValue
	 *           Value of event
	 * @param aDescription
	 *           Option description of event. Set to {@code null} for no
	 *           description.
	 */
	public EPPLoginSecEvent(EventType aType, EventLevel aLevel, String aValue, String aDescription) {
		this(aType, aLevel);

		this.value = aValue;
		this.description = aDescription;
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes likely attributes for
	 * a custom event.
	 * 
	 * @param aType
	 *           Event type
	 * @param aTypeName
	 *           Custom type name
	 * @param aLevel
	 *           Event level
	 * @param aDescription
	 *           Option description of event. Set to {@code null} for no
	 *           description.
	 */
	public EPPLoginSecEvent(EventType aType, String aTypeName, EventLevel aLevel, String aDescription) {
		this(aType, aLevel);

		this.typeName = aTypeName;
		this.description = aDescription;
	}

	/**
	 * {@code EPPLoginSecEvent} constructor that takes likely attribute for
	 * a statistical event.
	 * 
	 * @param aType
	 *           Event type
	 * @param aTypeName
	 *           Statistical sub-type name
	 * @param aLevel
	 *           Event level
	 * @param aValue
	 *           Value of event
	 * @param aDuration
	 *           Duration of the event
	 * @param aDescription
	 *           Option description of event. Set to {@code null} for no
	 *           description.
	 */
	public EPPLoginSecEvent(EventType aType, String aTypeName, EventLevel aLevel, String aValue, String aDuration,
	      String aDescription) {
		this(aType, aLevel);

		this.typeName = aTypeName;
		this.value = aValue;
		this.duration = aDuration;
		this.description = aDescription;
	}

	/**
	 * Gets the type for the security event.
	 * 
	 * @return Type of the security event if set; {@code null} otherwise.
	 */
	public EventType getType() {
		return this.type;
	}

	/**
	 * Sets the type of the security event.
	 * 
	 * @param aType
	 *           The type of the security event
	 */
	public void setType(EventType aType) {
		this.type = aType;
	}

	/**
	 * Is the type name (custom type or sub-type) set?
	 *
	 * @return {@code true} if the type name is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasTypeName() {
		if (this.typeName != null) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the optional sub-type or the custom type name of the event. If the
	 * type is set to {@link EventType#CUSTOM}, then the type name must be set.
	 * 
	 * @return Gets the optional type name if set; {@code null} otherwise.
	 */
	public String getTypeName() {
		return this.typeName;
	}

	/**
	 * Sets the optional sub-type or the custom type name of the event.
	 * 
	 * @param aTypeName
	 *           Sub-type or custom type name of the event.
	 */
	public void setTypeName(String aTypeName) {
		this.typeName = aTypeName;
	}

	/**
	 * Gets the event level.
	 * 
	 * @return Level of the event if defined; {@code null} otherwise.
	 */
	public EventLevel getLevel() {
		return this.level;
	}

	/**
	 * Sets the event level.
	 * 
	 * @param aLevel
	 *           Level of the event
	 */
	public void setLevel(EventLevel aLevel) {
		this.level = aLevel;
	}

	/**
	 * Is the expiration date set?
	 *
	 * @return {@code true} if the expiration date is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasExDate() {
		if (this.exDate != null) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the optional expiration of the event item (e.g., password, client
	 * certificate). The client must address the event prior to the expiration
	 * date and time.
	 * 
	 * @return The expiration date and time if set; {@code null} otherwise.
	 */
	public Date getExDate() {
		return this.exDate;
	}

	/**
	 * Sets the optional expiration of the event item (e.g., password, client
	 * certificate). The client must address the event prior to the expiration
	 * date and time.
	 * 
	 * @param aExDate
	 *           Expiration date and time of the event item
	 */
	public void setExDate(Date aExDate) {
		this.exDate = aExDate;
	}

	/**
	 * Is the event value set?
	 *
	 * @return {@code true} if the event value is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasValue() {
		if (this.value != null) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the optional value associated with the event (e.g., cipher of an
	 * cipher event or TLS protocol of a TLS protocol event).
	 * 
	 * @return The event value if defined; {@code null} otherwise.
	 */
	public String getValue() {
		return this.value;
	}

	/**
	 * Sets the optional value associated with the event (e.g., cipher of an
	 * cipher event or TLS protocol of a TLS protocol event).
	 * 
	 * @param aValue
	 *           Event value to set
	 */
	public void setValue(String aValue) {
		this.value = aValue;
	}

	/**
	 * Is the duration set?
	 *
	 * @return {@code true} if the duration is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasDuration() {
		if (this.duration != null) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the optional duration associated with a statistical warning event.
	 * The duration follows the format of an XML schema duration type.
	 * 
	 * @return The duration if defined; {@code null} otherwise.
	 */
	public String getDuration() {
		return this.duration;
	}

	/**
	 * Sets the optional duration associated with a statistical warning event.
	 * The duration follows the format of an XML schema duration type.
	 * 
	 * @param aDuration
	 *           The duration to set
	 */
	public void setDuration(String aDuration) {
		this.duration = aDuration;
	}

	/**
	 * Gets the optional language of the description with the default of
	 * {@link #DEFAULT_LANG} ("en").
	 * 
	 * @return The language of the description
	 */
	public String getLang() {
		return this.lang;
	}

	/**
	 * Sets the optional language of the description with the default of
	 * {@link #DEFAULT_LANG} ("en").
	 * 
	 * @param aLang
	 *           Language of the description
	 */
	public void setLang(String aLang) {
		if ((aLang == null) || aLang.isEmpty()) {
			this.lang = DEFAULT_LANG;
		}
		else {
			this.lang = aLang;
		}
	}

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

	/**
	 * Gets the optional description for the event. The language of the
	 * description is defined by {@link #getLang()}.
	 * 
	 * @return The description if defined; {@code null} otherwise.
	 */
	public String getDescription() {
		return this.description;
	}

	/**
	 * Sets the optional description for the event. If the language is not
	 * English ("en"), set the language using {@link #setLang(String)}.
	 * 
	 * @param aDescription
	 *           The description to set.
	 */
	public void setDescription(String aDescription) {
		this.description = aDescription;
	}

	/**
	 * Encode instance into a DOM element tree. A DOM Document is passed as an
	 * argument and functions as a factory for DOM objects. The root element
	 * associated with the instance is created and each instance attribute is
	 * appended as a child node.
	 *
	 * @param aDocument
	 *           DOM Document, which acts is an Element factory
	 *
	 * @return Element Root element associated with the object
	 *
	 * @exception EPPEncodeException
	 *               Error encoding {@code EPPLoginSecEvent}
	 */
	@Override
	public Element encode(Document aDocument) throws EPPEncodeException {

		if (aDocument == null) {
			throw new EPPEncodeException("aDocument is null" + " on in EPPLoginSecEvent.encode(Document)");
		}
		if (this.type == null) {
			throw new EPPEncodeException("type is null" + " on in EPPLoginSecEvent.encode(Document)");
		}
		if (this.level == null) {
			throw new EPPEncodeException("level is null" + " on in EPPLoginSecEvent.encode(Document)");
		}

		// Create root element
		Element root = aDocument.createElementNS(EPPLoginSecExtFactory.NS, ELM_NAME);

		// Type
		root.setAttribute(ATTR_TYPE, this.type.toString());

		// Type Name
		if (this.hasTypeName()) {
			root.setAttribute(ATTR_TYPE_NAME, this.typeName);
		}

		// Level
		root.setAttribute(ATTR_LEVEL, this.level.toString());

		// Expiration Date
		if (this.hasExDate()) {
			root.setAttribute(ATTR_EXDATE, EPPUtil.encodeTimeInstant(this.exDate));
		}

		// Value
		if (this.hasValue()) {
			root.setAttribute(ATTR_VALUE, this.value);
		}

		// Duration
		if (this.hasDuration()) {
			root.setAttribute(ATTR_DURATION, this.duration);
		}

		// Description
		if (this.hasDescription()) {

			// Language
			root.setAttribute(ATTR_LANG, this.lang);

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

		return root;
	}

	/**
	 * Decode a DOM element tree to initialize the instance attributes. The
	 * {@code aElement} argument represents the root DOM element and is used
	 * to traverse the DOM nodes for instance attribute values.
	 *
	 * @param aElement
	 *           {@code Element} to decode
	 *
	 * @exception EPPDecodeException
	 *               Error decoding {@code Element}
	 */
	@Override
	public void decode(Element aElement) throws EPPDecodeException {

		String theAttr = null;

		// Type
		theAttr = aElement.getAttribute(ATTR_TYPE);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.type = EventType.getEventType(theAttr);
		}
		else {
			this.type = null;
		}

		// Type Name
		theAttr = aElement.getAttribute(ATTR_TYPE_NAME);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.typeName = theAttr;
		}
		else {
			this.typeName = null;
		}

		// Level
		theAttr = aElement.getAttribute(ATTR_LEVEL);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.level = EventLevel.getEventLevel(theAttr);
		}
		else {
			this.level = null;
		}

		// Expiration Date
		theAttr = aElement.getAttribute(ATTR_EXDATE);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.exDate = EPPUtil.decodeTimeInstant(theAttr);
		}
		else {
			this.exDate = null;
		}

		// Value
		theAttr = aElement.getAttribute(ATTR_VALUE);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.value = theAttr;
		}
		else {
			this.value = null;
		}

		// Duration
		theAttr = aElement.getAttribute(ATTR_DURATION);
		if (theAttr != null && !theAttr.isEmpty()) {
			this.duration = theAttr;
		}
		else {
			this.duration = null;
		}

		// Description
		this.description = EPPUtil.getTextContent(aElement, true);
		if (this.description.isEmpty()) {
			this.description = null;
		}

		// Language
		this.setLang(aElement.getAttribute(ATTR_LANG));
	}

	/**
	 * Compare an instance of {@code EPPLoginSecEvent} with this instance.
	 *
	 * @param aObject
	 *           Object to compare with.
	 *
	 * @return {@code true} if equal; {@code false} otherwise.
	 */
	@Override
	public boolean equals(Object aObject) {
		if (!(aObject instanceof EPPLoginSecEvent)) {
			cat.error("EPPLoginSecEvent.equals(): " + aObject.getClass().getName() + " not EPPLoginSecEvent instance");

			return false;
		}

		EPPLoginSecEvent other = (EPPLoginSecEvent) aObject;

		// Type
		if (!EqualityUtil.equals(this.type, other.type)) {
			cat.error("EPPLoginSecEvent.equals(): type not equal");
			return false;
		}

		// Type Name
		if (!EqualityUtil.equals(this.typeName, other.typeName)) {
			cat.error("EPPLoginSecEvent.equals(): typeName not equal");
			return false;
		}

		// Level
		if (!EqualityUtil.equals(this.level, other.level)) {
			cat.error("EPPLoginSecEvent.equals(): level not equal");
			return false;
		}

		// Expiration Date
		if (!EqualityUtil.equals(this.exDate, other.exDate)) {
			cat.error("EPPLoginSecEvent.equals(): exDate not equal");
			return false;
		}

		// Value
		if (!EqualityUtil.equals(this.value, other.value)) {
			cat.error("EPPLoginSecEvent.equals(): value not equal");
			return false;
		}

		// Duration
		if (!EqualityUtil.equals(this.duration, other.duration)) {
			cat.error("EPPLoginSecEvent.equals(): duration not equal");
			return false;
		}

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

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

		return true;
	}

	/**
	 * Clone an {@code EPPCodecComponent} instance.
	 *
	 * @return clone of concrete {@code EPPLoginSecEvent}
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {

		EPPLoginSecEvent clone = (EPPLoginSecEvent) super.clone();

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

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

}
