/***********************************************************
Copyright (C) 2021 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.maintenance.v1_0;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.verisign.epp.codec.gen.EPPCodecComponent;
import com.verisign.epp.codec.gen.EPPCodecException;
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.EqualityUtil;

/**
 * Represents a maintenance that includes the following child elements:<br>
 * <ul>
 * <li>&lt;maint:id&gt; element that is the maintenance identifier.</li>
 * <li>&lt;maint:type&gt; The OPTIONAL type of the maintenance that has the
 * possible set of values defined by server policy.
 * <li>&lt;maint:pollType&gt; An OPTIONAL element that is the registry
 * maintenance notification poll message type.</li>
 * <li>&lt;maint:systems&gt; elements that contains one or more
 * &lt;maint:system&gt; elements.</li>
 * <li>&lt;maint:environment&gt; element that indicates the type of the affected
 * system using the Environment enumeration.</li>
 * <li>&lt;maint:start&gt; element that represents the maintenance start date
 * and time.</li>
 * <li>&lt;maint:end&gt; element that represents the maintenance end date and
 * time.</li>
 * <li>&lt;maint:reason&gt; element that is the reason behind the
 * maintenance.</li>
 * <li>&lt;maint:description&gt; OPTIONAL free-form description of the
 * maintenance.</li>
 * <li>&lt;maint:tlds&gt; OPTIONAL element containing list of affected top-level
 * domains or registry zones.</li>
 * <li>&lt;maint:intervention&gt; OPTIONAL element that contains when the
 * maintenance affects the connection or implementation for the client.</li>
 * <li>&lt;maint:crDate&gt; element that represents the created date and time
 * for the maintenance.</li>
 * <li>&lt;maint:upDate&gt; OPTIONAL element that represents the updated date
 * and time for the maintenance.</li>
 * </ul>
 */
public class EPPMaintenanceItem implements EPPCodecComponent {

  /**
   * Poll type enumerated values.
   */
  public enum PollType {
    create, update, delete, courtesy, end;
  }

  /**
   * Environment enumerated values.
   */
  public enum Environment {
    production, ote, staging, dev, custom;
  }

  /**
   * Reason enumerated values.
   */
  public enum Reason {
    planned, emergency;
  }

  /**
   * Category for logging
   */
      private static Logger cat = LoggerFactory.getLogger(EPPMaintenanceItem.class);
        

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

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

  /**
   * XML local name for the type element
   */
  private final static String ELM_TYPE = "type";

  /**
   * XML local name for the pollType element
   */
  private final static String ELM_POLL_TYPE = "pollType";

  /**
   * XML local name for the maintenance systems element
   */
  private final static String ELM_SYSTEMS = "systems";

  /**
   * XML local name for the maintenance environment element
   */
  private final static String ELM_ENVIRONMENT = "environment";

  /**
   * XML tag name for the start date attribute.
   */
  private final static String ELM_START_DATE = "start";

  /**
   * XML tag name for the end date attribute.
   */
  private final static String ELM_END_DATE = "end";

  /**
   * XML local name for the maintenance reason element
   */
  private final static String ELM_REASON = "reason";

  /**
   * XML local name for the maintenance detail element
   */
  private final static String ELM_DETAIL = "detail";

  /**
   * XML local name for the maintenance description element
   */
  private final static String ELM_DESCRIPTION = "description";

  /**
   * XML local name for the maintenance tlds element
   */
  private final static String ELM_TLDS = "tlds";

  /**
   * XML local name for the maintenance tld element
   */
  private final static String ELM_TLD = "tld";

  /**
   * XML tag name for the created date attribute.
   */
  private final static String ELM_CREATED_DATE = "crDate";

  /**
   * XML tag name for the updated date attribute.
   */
  private final static String ELM_LAST_UPDATED_DATE = "upDate";

  /**
   * XML attribute name for environment type.
   */
  private final static String ATTR_ENVIRONMENT_TYPE = "type";

  /**
   * XML attribute name for custom environment name.
   */
  private final static String ATTR_ENVIRONMENT_CUSTOM = "name";

  /**
   * Maintenance identifier.
   */
  private EPPMaintenanceId maintenanceId;

  /**
   * Zero or more OPTIONAL types of the maintenance that has the possible set
   * of values defined by server policy, such as "Routine Maintenance",
   * "Software Update", "Software Upgrade", or "Extended Outage".
   */
  private List<EPPMaintenanceType> types = null;

  /**
   * OPTIONAL Registry Maintenance Notification poll message type.
   */
  private PollType pollType;

  /**
   * Maintenance systems.
   */
  private List<EPPMaintenanceSystem> systems = new ArrayList<EPPMaintenanceSystem>();

  /**
   * Maintenance environment.
   */
  private Environment environment = null;

  /**
   * OPTIONAL custom maintenance environment.
   */
  private String customEnvironment = null;

  /**
   * Maintenance start date and time.
   */
  private Date startDate;

  /**
   * Maintenance end date and time.
   */
  private Date endDate;

  /**
   * Maintenance reason with the default set to {@link Reason#planned}.
   */
  private Reason reason = Reason.planned;

  /**
   * Optional URI to the detailed maintenance description.
   */
  private String detail = null;

  /**
   * Zero or more OPTIONAL free-form descriptions of the maintenance without
   * having to create and traverse an external resource
   */
  private List<EPPMaintenanceDescription> descriptions = null;

  /**
   * Maintenance TLDs.
   */
  private List<String> tlds = null;

  /**
   * Maintenance intervention.
   */
  private EPPMaintenanceIntervention intervention = null;

  /**
   * Maintenance created date
   */
  private Date createdDate;

  /**
   * Optional maintenance last updated date
   */
  private Date lastUpdatedDate;

  /**
   * Default constructor for {@code EPPMaintenance}. All the the attributes
   * default to {@code null}. Must call required setter methods before invoking
   * {@link #encode(Document)}, which include:<br>
   * <br>
   * <ul>
   * <li>maintenance identifier -
   * {@link #setMaintenanceId(EPPMaintenanceId)}</li>
   * <li>systems - {@link #setSystems(List)}</li>
   * <li>environment - {@link #setStartDate(Date)}</li>
   * <li>start date - {@link #setEndDate(Date)}</li>
   * <li>end date - {@link #setEnvironment(Environment)}</li>
   * <li>reason - {@link #setReason(Reason)}</li>
   * <li>created date - {@link #setCreatedDate(Date)}</li>
   * </ul>
   */
  public EPPMaintenanceItem() {
  }

  /**
   * Constructor for {@code EPPMaintenance} with all of the required attributes
   * as parameters.
   *
   * @param aMaintenanceId
   *           Maintenance identifier
   * @param aSystems
   *           Systems affected by the maintenance.
   * @param aEnvironment
   *           Type of the affected system.
   * @param aStartDate
   *           Maintenance start date and time
   * @param aEndDate
   *           Maintenance end date and time
   * @param aReason
   *           Reason behind the maintenance.
   * @param aCreatedDate
   *           Maintenance created date
   */
  public EPPMaintenanceItem(EPPMaintenanceId aMaintenanceId, List<EPPMaintenanceSystem> aSystems,
        Environment aEnvironment, Date aStartDate, Date aEndDate, Reason aReason, Date aCreatedDate) {
    this.maintenanceId = aMaintenanceId;
    this.systems = aSystems;
    this.environment = aEnvironment;
    this.startDate = aStartDate;
    this.endDate = aEndDate;
    this.reason = aReason;
    this.createdDate = aCreatedDate;
  }

  /**
   * Gets the maintenance identifier.
   *
   * @return The maintenance identifier if defined; {@code null} otherwise.
   */
  public EPPMaintenanceId getMaintenanceId() {
    return this.maintenanceId;
  }

  /**
   * Sets the maintenance identifier.
   *
   * @param aMaintenanceId
   *           The maintenance identifier.
   */
  public void setMaintenanceId(EPPMaintenanceId aMaintenanceId) {
    this.maintenanceId = aMaintenanceId;
  }

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

  /**
   * Gets the types of the maintenance.
   *
   * @return The types of the maintenance if defined; {@code null} otherwise.
   */
  public List<EPPMaintenanceType> getTypes() {
    return this.types;
  }

  /**
   * Add a type to the list of types.
   *
   * @param aType
   *           Type to add to the list.
   */
  public void addType(EPPMaintenanceType aType) {
    if (aType != null) {
      if (this.types == null) {
        this.types = new ArrayList<EPPMaintenanceType>();
      }
      this.types.add(aType);
    }
  }

  /**
   * Sets the types of the maintenance.
   *
   * @param aTypes
   *           Types of the maintenance.
   */
  public void setTypes(List<EPPMaintenanceType> aTypes) {
    this.types = aTypes;
  }

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

  /**
   * Gets the poll type of the Registry Maintenance Notification poll message.
   *
   * @return Poll type of the Registry Maintenance Notification poll message if
   *         defined; {@code null} otherwise.
   */
  public PollType getPollType() {
    return this.pollType;
  }

  /**
   * Sets the poll type of the Registry Maintenance Notification poll message.
   *
   * @param aPollType
   *           Poll type of the Registry Maintenance Notification poll message.
   *           Set to {@code null} to unset it.
   */
  public void setPollType(PollType aPollType) {
    this.pollType = aPollType;
  }

  /**
   * Gets the maintenance systems.
   *
   * @return Maintenance systems if defined; {@code null} otherwise.
   */
  public List<EPPMaintenanceSystem> getSystems() {
    return this.systems;
  }

  /**
   * Add a system to the list of affected systems.
   *
   * @param aSystem
   *           Affected system to add to the list.
   */
  public void addSystem(EPPMaintenanceSystem aSystem) {
    if (aSystem != null) {
      this.systems.add(aSystem);
    }
  }

  /**
   * Sets the maintenance systems.
   *
   * @param aSystems
   *           Maintenance systems to set.
   */
  public void setSystems(List<EPPMaintenanceSystem> aSystems) {
    this.systems = aSystems;
  }

  /**
   * Gets the type of the affected system.
   *
   * @return Type of the affected system if defined; {@code code} otherwise.
   */
  public Environment getEnvironment() {
    return this.environment;
  }

  /**
   * Sets the type of the affected system.
   *
   * @param aEnvironment
   *           The type of the affect system to set.
   */
  public void setEnvironment(Environment aEnvironment) {
    this.environment = aEnvironment;
  }

  /**
   * Is the custom environment name defined?
   *
   * @return {@code true} if the custom environment name is defined;
   *         {@code false} otherwise.
   */
  public boolean hasCustomEnvironment() {
    return (this.customEnvironment != null ? true : false);
  }

  /**
   * Gets the custom environment name, when the environment is set to
   * {@link Environment#custom}.
   *
   * @return Custom environment name if defined; {@code null} otherwise.
   */
  public String getCustomEnvironment() {
    return this.customEnvironment;
  }

  /**
   * Sets the custom environment name, when the environment is set to
   * {@link Environment#custom}.
   *
   * @param aCustomEnvironment
   *           The custom environment name to set.
   */
  public void setCustomEnvironment(String aCustomEnvironment) {
    this.customEnvironment = aCustomEnvironment;
  }

  /**
   * Gets the optional maintenance start date and time.
   *
   * @return start date and time if defined; {@code null} otherwise.
   */
  public Date getStartDate() {
    return this.startDate;
  }

  /**
   * Sets the optional maintenance start date and time.
   *
   * @param aStartDate
   *           Maintenance start date and time. Set to {@code null} if
   *           undefined.
   */
  public void setStartDate(Date aStartDate) {
    this.startDate = aStartDate;
  }

  /**
   * Gets the optional maintenance end date and time.
   *
   * @return end date and time if defined; {@code null} otherwise.
   */
  public Date getEndDate() {
    return this.endDate;
  }

  /**
   * Sets the optional maintenance end date and time.
   *
   * @param aEndDate
   *           Maintenance end date and time. Set to {@code null} if undefined.
   */
  public void setEndDate(Date aEndDate) {
    this.endDate = aEndDate;
  }

  /**
   * Gets the reason behind the maintenance.
   *
   * @return Reason behind the maintenance if defined; {@code null} otherwise.
   */
  public Reason getReason() {
    return this.reason;
  }

  /**
   * Sets the reason behind the maintenance.
   *
   * @param aReason
   *           Reason behind the maintenance to set.
   */
  public void setReason(Reason aReason) {
    this.reason = aReason;
  }

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

  /**
   * Gets URI to detailed maintenance description.
   *
   * @return URI to detailed maintenance description if defined; {@code null}
   *         otherwise.
   */
  public String getDetail() {
    return this.detail;
  }

  /**
   * Sets URI to detailed maintenance description.
   *
   * @param aDetail
   *           URI to detailed maintenance description to set
   */
  public void setDetail(String aDetail) {
    this.detail = aDetail;
  }

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

  /**
   * Gets the free-form descriptions of the maintenance without having to
   * create an external resource.
   *
   * @return The free-form descriptions of the maintenance if defined;
   *         {@code null} otherwise.
   */
  public List<EPPMaintenanceDescription> getDescriptions() {
    return this.descriptions;
  }

  /**
   * Add a description to the list of descriptions.
   *
   * @param aDescription
   *           Description to add to the list.
   */
  public void addDescription(EPPMaintenanceDescription aDescription) {
    if (aDescription != null) {
      if (this.descriptions == null) {
        this.descriptions = new ArrayList<EPPMaintenanceDescription>();
      }
      this.descriptions.add(aDescription);
    }
  }

  /**
   * Sets the free-form descriptions of the maintenance without having to
   * create an external resource.
   *
   * @param aDescriptions
   *           Free-form descriptions of the maintenance without having to
   *           create an external resource. Set to {@code null} to unset the
   *           descriptions.
   */
  public void setDescriptions(List<EPPMaintenanceDescription> aDescriptions) {
    this.descriptions = aDescriptions;
  }

  /**
   * Gets the optional top-level domains affected by the maintenance.
   *
   * @return top-level domains affected by the maintenance if defined;
   *         {@code null} otherwise.
   */
  public List<String> getTlds() {
    return this.tlds;
  }

  /**
   * Add a top-level domain to the list of affected top-level domains.
   *
   * @param aTld
   *           Top-level domain to add to the list.
   */
  public void addTld(String aTld) {
    if (aTld != null) {
      if (this.tlds == null) {
        this.tlds = new ArrayList<String>();
      }
      this.tlds.add(aTld);
    }
  }

  /**
   * Optional top-level domains affected by the maintenance.
   *
   * @param aTlds
   *           the tlds to set. Set to {@code null} if undefined.
   */
  public void setTlds(List<String> aTlds) {
    this.tlds = aTlds;
  }

  /**
   * Gets the optional client intervention by the maintenance.
   *
   * @return optional client intervention if defined; {@code null} otherwise.
   */
  public EPPMaintenanceIntervention getIntervention() {
    return this.intervention;
  }

  /**
   * Sets the optional client intervention.
   *
   * @param aIntervention
   *           client intervention. Set to {@code null} if undefined.
   */
  public void setIntervention(EPPMaintenanceIntervention aIntervention) {
    this.intervention = aIntervention;
  }

  /**
   * Gets the maintenance created date.
   *
   * @return Maintenance created date if defined; {@code null} otherwise.
   */
  public Date getCreatedDate() {
    return this.createdDate;
  }

  /**
   * Sets the maintenance created date.
   *
   * @param aCreatedDate
   *           Maintenance created date
   */
  public void setCreatedDate(Date aCreatedDate) {
    this.createdDate = aCreatedDate;
  }

  /**
   * Is the last updated date defined?
   *
   * @return {@code true} if the last updated date is defined; {@code false}
   *         otherwise.
   */
  public boolean hasLastUpdatedDate() {
    return (this.lastUpdatedDate != null ? true : false);
  }

  /**
   * Gets the optional maintenance last updated date.
   *
   * @return Maintenance last updated date if defined; {@code null} otherwise.
   */
  public Date getLastUpdatedDate() {
    return this.lastUpdatedDate;
  }

  /**
   * Sets the maintenance last updated date.
   *
   * @param aLastUpdatedDate
   *           Maintenance last updated date
   */
  public void setLastUpdatedDate(Date aLastUpdatedDate) {
    this.lastUpdatedDate = aLastUpdatedDate;
  }

  /**
   * Validate the state of the {@code EPPMaintenance} instance. A valid state
   * means that all of the required attributes have been set. If validateState
   * returns without an exception, the state is valid. If the state is not
   * valid, the EPPCodecException will contain a description of the error.
   * throws EPPCodecException State error. This will contain the name of the
   * attribute that is not valid.
   *
   * @throws EPPCodecException
   *            On validation error
   */
  void validateState() throws EPPCodecException {

    if (this.maintenanceId == null) {
      throw new EPPCodecException("maintenanceId required attribute is not set");
    }

    if (this.systems == null) {
      throw new EPPCodecException("systems required attribute is not set");
    }

    if (this.environment == null) {
      throw new EPPCodecException("environment required attribute is not set");
    }

    if (this.environment == Environment.custom && this.customEnvironment == null) {
      throw new EPPCodecException("customEnvironment required attribute is not set when environment is \"custom\"");
    }

    if (this.startDate == null) {
      throw new EPPCodecException("startDate required attribute is not set");
    }

    if (this.endDate == null) {
      throw new EPPCodecException("endDate required attribute is not set");
    }

    if (this.environment == null) {
      throw new EPPCodecException("environment required attribute is not set");
    }

    if (this.reason == null) {
      throw new EPPCodecException("reason required attribute is not set");
    }

    if (this.createdDate == null) {
      throw new EPPCodecException("createdDate required attribute is not set");
    }
  }

  /**
   * Encode a DOM Element tree from the attributes of the
   * {@code EPPMaintenance} instance.
   *
   * @param aDocument
   *           DOM Document that is being built. Used as an Element factory.
   *
   * @return Root DOM Element representing the {@code EPPMaintenance} instance.
   *
   * @exception EPPEncodeException
   *               Unable to encode {@code EPPMaintenance} instance.
   */
  @Override
  public Element encode(Document aDocument) throws EPPEncodeException {
    try {
      validateState();
    }
    catch (EPPCodecException e) {
      throw new EPPEncodeException("Invalid state on EPPMaintenance.encode: " + e);
    }

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

    // Identifier
    EPPUtil.encodeComp(aDocument, root, this.maintenanceId);

    // Types
    EPPUtil.encodeCompList(aDocument, root, this.types);

    // Poll Type
    if (this.hasPollType()) {
      EPPUtil.encodeString(aDocument, root, this.pollType.toString(), EPPMaintenanceMapFactory.NS,
            EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_POLL_TYPE);
    }

    // Systems
    Element systemsElm = aDocument.createElementNS(EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_SYSTEMS);
    root.appendChild(systemsElm);
    EPPUtil.encodeCompList(aDocument, systemsElm, this.systems);

    // Environment
    Element environmentElm = aDocument.createElementNS(EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_ENVIRONMENT);
    root.appendChild(environmentElm);
    environmentElm.setAttribute(ATTR_ENVIRONMENT_TYPE, this.environment.toString());

    if (this.environment == Environment.custom) {
      environmentElm.setAttribute(ATTR_ENVIRONMENT_CUSTOM, this.customEnvironment);
    }

    // Start Date
    EPPUtil.encodeTimeInstant(aDocument, root, this.startDate, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_START_DATE);

    // End Date
    EPPUtil.encodeTimeInstant(aDocument, root, this.endDate, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_END_DATE);

    // Reason
    EPPUtil.encodeString(aDocument, root, this.reason.toString(), EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_REASON);

    // Detail
    EPPUtil.encodeString(aDocument, root, this.detail, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_DETAIL);

    // Descriptions
    EPPUtil.encodeCompList(aDocument, root, this.descriptions);

    // Tlds
    if (this.tlds != null) {
      Element tldsElm = aDocument.createElementNS(EPPMaintenanceMapFactory.NS,
            EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_TLDS);
      root.appendChild(tldsElm);
      EPPUtil.encodeStringList(aDocument, tldsElm, this.tlds, EPPMaintenanceMapFactory.NS,
            EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_TLD);
    }

    // Intervention
    EPPUtil.encodeComp(aDocument, root, this.intervention);

    // Created Date
    EPPUtil.encodeTimeInstant(aDocument, root, this.createdDate, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_CREATED_DATE);

    // Last Updated Date
    EPPUtil.encodeTimeInstant(aDocument, root, this.lastUpdatedDate, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_LAST_UPDATED_DATE);

    return root;
  }

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

    // Identifier
    this.maintenanceId = (EPPMaintenanceId) EPPUtil.decodeComp(aElement, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceId.ELM_LOCALNAME, EPPMaintenanceId.class);

    // Types
    this.types = EPPUtil.decodeCompList(aElement, EPPMaintenanceMapFactory.NS, EPPMaintenanceType.ELM_LOCALNAME,
          EPPMaintenanceType.class);
    if (this.types.size() == 0) {
      this.types = null;
    }

    // Poll Type
    Element pollTypeElm = EPPUtil.getElementByTagNameNS(aElement, EPPMaintenanceMapFactory.NS, ELM_POLL_TYPE);
    if (pollTypeElm != null) {
      this.pollType = PollType.valueOf(EPPUtil.decodeString(aElement, EPPMaintenanceMapFactory.NS, ELM_POLL_TYPE));
    }
    else {
      this.pollType = null;
    }

    // Systems
    Element systemsElm = EPPUtil.getElementByTagNameNS(aElement, EPPMaintenanceMapFactory.NS, ELM_SYSTEMS);
    this.systems = EPPUtil.decodeCompList(systemsElm, EPPMaintenanceMapFactory.NS, EPPMaintenanceSystem.ELM_LOCALNAME,
          EPPMaintenanceSystem.class);

    // Environment
    Element environmentElm = EPPUtil.getElementByTagNameNS(aElement, EPPMaintenanceMapFactory.NS, ELM_ENVIRONMENT);
    String environmentStr = environmentElm.getAttribute(ATTR_ENVIRONMENT_TYPE);
    this.environment = Environment.valueOf(environmentStr);
    if (this.environment == Environment.custom) {
      this.customEnvironment = environmentElm.getAttribute(ATTR_ENVIRONMENT_CUSTOM);
    }

    // Start Date
    this.startDate = EPPUtil.decodeTimeInstant(aElement, EPPMaintenanceMapFactory.NS, ELM_START_DATE);

    // End Date
    this.endDate = EPPUtil.decodeTimeInstant(aElement, EPPMaintenanceMapFactory.NS, ELM_END_DATE);

    // Reason
    this.reason = Reason.valueOf(EPPUtil.decodeString(aElement, EPPMaintenanceMapFactory.NS, ELM_REASON));

    // Detail
    this.detail = EPPUtil.decodeString(aElement, EPPMaintenanceMapFactory.NS, ELM_DETAIL);

    // Descriptions
    this.descriptions = EPPUtil.decodeCompList(aElement, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceDescription.ELM_LOCALNAME, EPPMaintenanceDescription.class);
    if (this.descriptions.size() == 0) {
      this.descriptions = null;
    }

    // Tlds
    Element tldsElm = EPPUtil.getElementByTagNameNS(aElement, EPPMaintenanceMapFactory.NS, ELM_TLDS);
    if (tldsElm != null) {
      this.tlds = (List<String>) EPPUtil.decodeList(tldsElm, EPPMaintenanceMapFactory.NS, ELM_TLD);
    }
    else {
      this.tlds = null;
    }

    // Intervention
    this.intervention = (EPPMaintenanceIntervention) EPPUtil.decodeComp(aElement, EPPMaintenanceMapFactory.NS,
          EPPMaintenanceIntervention.ELM_LOCALNAME, EPPMaintenanceIntervention.class);

    // Created Date
    this.createdDate = EPPUtil.decodeTimeInstant(aElement, EPPMaintenanceMapFactory.NS, ELM_CREATED_DATE);

    // Last Updated Date
    this.lastUpdatedDate = EPPUtil.decodeTimeInstant(aElement, EPPMaintenanceMapFactory.NS, ELM_LAST_UPDATED_DATE);
  }

  /**
   * implements a deep {@code EPPMaintenance} compare.
   *
   * @param aObject
   *           {@code EPPMaintenance} instance to compare with
   *
   * @return {@code true} of {@code aObject} is equal to instance;
   *         {@code false} otherwise.
   */
  @Override
  public boolean equals(Object aObject) {

    if (!(aObject instanceof EPPMaintenanceItem)) {
      return false;
    }

    EPPMaintenanceItem other = (EPPMaintenanceItem) aObject;

    // Identifier
    if (!EqualityUtil.equals(this.maintenanceId, other.maintenanceId)) {
      cat.error("EPPMaintenance.equals(): maintenanceId not equal");
      return false;
    }

    // Types
    if (!EqualityUtil.equals(this.types, other.types)) {
      cat.error("EPPMaintenance.equals(): types not equal");
      return false;
    }

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

    // Systems
    if (!EPPUtil.equalLists(this.systems, other.systems)) {
      cat.error("EPPMaintenance.equals(): systems not equal");
      return false;
    }

    // Environment
    if (!EqualityUtil.equals(this.environment, other.environment)) {
      cat.error("EPPMaintenance.equals(): environment not equal");
      return false;
    }

    // Custom Environment
    if (!EqualityUtil.equals(this.customEnvironment, other.customEnvironment)) {
      cat.error("EPPMaintenance.equals(): customEnvironment not equal");
      return false;
    }

    // Start Date
    if (!EqualityUtil.equals(this.startDate, other.startDate)) {
      cat.error("EPPMaintenance.equals(): startDate not equal");
      return false;
    }

    // End Date
    if (!EqualityUtil.equals(this.endDate, other.endDate)) {
      cat.error("EPPMaintenance.equals(): endDate not equal");
      return false;
    }

    // Reason
    if (!EqualityUtil.equals(this.reason, other.reason)) {
      cat.error("EPPMaintenance.equals(): reason not equal");
      return false;
    }

    // Detail
    if (!EqualityUtil.equals(this.detail, other.detail)) {
      cat.error("EPPMaintenance.equals(): detail not equal");
      return false;
    }

    // Descriptions
    if (!EqualityUtil.equals(this.descriptions, other.descriptions)) {
      cat.error("EPPMaintenance.equals(): descriptions not equal");
      return false;
    }

    // Tlds
    if (!EPPUtil.equalLists(this.tlds, other.tlds)) {
      cat.error("EPPMaintenance.equals(): tlds not equal");
      return false;
    }

    // Intervention
    if (!EqualityUtil.equals(this.intervention, other.intervention)) {
      cat.error("EPPMaintenance.equals(): intervention not equal");
      return false;
    }

    // Created Date
    if (!EqualityUtil.equals(this.createdDate, other.createdDate)) {
      cat.error("EPPMaintenance.equals(): createdDate not equal");
      return false;
    }

    // Last Updated Date
    if (!EqualityUtil.equals(this.lastUpdatedDate, other.lastUpdatedDate)) {
      cat.error("EPPMaintenance.equals(): lastUpdatedDate not equal");
      return false;
    }

    return true;
  }

  /**
   * Clone {@code EPPMaintenance}.
   *
   * @return clone of {@code EPPMaintenance}
   *
   * @exception CloneNotSupportedException
   *               standard Object.clone exception
   */
  @Override
  public Object clone() throws CloneNotSupportedException {
    EPPMaintenanceItem clone = null;

    clone = (EPPMaintenanceItem) super.clone();

    // Types
    if (this.types != null) {
      clone.types = new ArrayList<EPPMaintenanceType>();
      clone.types.addAll(this.types);
    }
    else {
      clone.descriptions = null;
    }

    // Systems
    clone.systems = new ArrayList<EPPMaintenanceSystem>();
    clone.systems.addAll(this.systems);

    // Descriptions
    if (this.descriptions != null) {
      clone.descriptions = new ArrayList<EPPMaintenanceDescription>();
      clone.descriptions.addAll(this.descriptions);
    }
    else {
      clone.descriptions = null;
    }

    // Tlds
    if (this.tlds != null) {
      clone.tlds = new ArrayList<String>();
      clone.tlds.addAll(this.tlds);
    }
    else {
      clone.tlds = null;
    }

    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 EPPMaintenanceMapFactory.NS;
  }

}
