/***********************************************************
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.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.EPPDecodeException;
import com.verisign.epp.codec.gen.EPPEncodeException;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPTransId;
import com.verisign.epp.codec.gen.EPPUtil;
import com.verisign.epp.util.EqualityUtil;

/**
 * Response information associated with a {@link EPPMaintenanceInfoCmd} of type
 * {@code EPPMaintenanceInfoCmd.InfoType.id}, which will only include a single
 * element, and of type {@code EPPMaintenanceInfoCmd.InfoType.id} that can
 * include a list of zero or more maintenances items
 * ({@link EPPMaintenanceListItem}).
 *
 * @see com.verisign.epp.codec.maintenance.v1_0.EPPMaintenanceInfoCmd
 */
public class EPPMaintenanceInfoResp extends EPPResponse {

  /**
   * The type of the info response, which is either the details of an
   * individual maintenance with {@code maintenance} of a list of maintenance
   * items with {@code list}.
   */
  public enum InfoType {
    list, maintenance;
  }

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

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

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

  /**
   * XML local name for the item element
   */
  private static final String ELM_ITEM = "item";

  /**
   * XML local name for the maintenance notification list element
   */
  private static final String ELM_MAINTENANCE_LIST = "list";

  /**
   * Type of the info command with the default set to {@code InfoType.list}.
   */
  private InfoType infoType = InfoType.list;

  /**
   * Maintenance notification. The {@code maintenance} is used when the
   * {@code infoType} is set to {@code InfoType.maintenance}.
   */
  private EPPMaintenanceItem maintenance;

  /**
   * Maintenance list items.
   */
  private List<EPPMaintenanceListItem> listItems = null;

  /**
   * {@code EPPMaintenanceInfoResp} default constructor. The {@code infoType} will
   * default to {@code InfoType.list}.
   */
  public EPPMaintenanceInfoResp() {
    this.setInfoType(InfoType.list);
  }

  /**
   * {@code EPPMaintenanceInfoResp} constructor that only takes the transaction
   * identifier.
   *
   * @param aTransId
   *           Transaction Id associated with response.
   */
  public EPPMaintenanceInfoResp(EPPTransId aTransId) {
    super(aTransId);
    this.setInfoType(InfoType.list);
  }

  /**
   * {@code EPPMaintenanceInfoResp} constructor that only takes the transaction
   * identifier and the maintenance, which will set the {@code infoType} to
   * {@code InfoType#maintenance}.
   *
   * @param aTransId
   *           Transaction Id associated with command. Set to {@code null} if a
   *           client transaction identifier is not desired.
   * @param aMaintenance
   *           Maintenance object
   *
   */
  public EPPMaintenanceInfoResp(EPPTransId aTransId, EPPMaintenanceItem aMaintenance) {
    super(aTransId);
    this.setMaintenance(aMaintenance);
  }

  /**
   * {@code EPPMaintenanceInfoResp} constructor that only takes the transaction
   * identifier and the list of maintenance items, which will set the
   * {@code infoType} to {@link InfoType#list}.
   *
   * @param aTransId
   *           Transaction Id associated with command. Set to {@code null} if a
   *           client transaction identifier is not desired.
   * @param aListItems
   *           Maintenance list items
   *
   */
  public EPPMaintenanceInfoResp(EPPTransId aTransId, List<EPPMaintenanceListItem> aListItems) {
    super(aTransId);
    this.setListItems(aListItems);
  }

  /**
   * Gets the {@code infoType} for the info response.
   *
   * @return the {@code infoType} for the info response.
   */
  public InfoType getInfoType() {
    return this.infoType;
  }

  /**
   * Sets the {@code infoType} for the info response.
   *
   * @param aInfoType
   *           The {@code infoType} for the info response.
   */
  public void setInfoType(InfoType aInfoType) {
    this.infoType = aInfoType;
    if (this.infoType == InfoType.maintenance) {
      this.listItems = null;
    }
    else {
      this.maintenance = null;
      if (this.listItems == null) {
        this.listItems = new ArrayList<>();
      }
    }
  }

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

  /**
   * Gets the maintenance.
   *
   * @return The maintenance if defined; {@code null} otherwise.
   */
  public EPPMaintenanceItem getMaintenance() {
    return this.maintenance;
  }

  /**
   * Sets the maintenance. When set to a non-{@code null} value, the
   * {@code infoType} is set to {@code InfoType.maintenance}; otherwise
   * {@code infoType} is set to {@code InfoType.list}.
   *
   * @param aMaintenance
   *           The maintenance.
   */
  public void setMaintenance(EPPMaintenanceItem aMaintenance) {
    this.maintenance = aMaintenance;
    if (this.maintenance != null) {
      this.setInfoType(InfoType.maintenance);
    }
    else {
      this.setInfoType(InfoType.list);
    }
  }

  /**
   * Is there any maintenance list items set?
   *
   * @return {@code true} if there is at least one
   *         {@link EPPMaintenanceListItem} set; {@code false} otherwise.
   */
  public boolean hasListItems() {
    if (this.listItems != null && !this.listItems.isEmpty()) {
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Adds a maintenance item to the list of maintenance items.
   *
   * @param aItem
   *           maintenance item to add to the list of maintenance items.
   */
  public void addListItem(EPPMaintenanceListItem aItem) {
    this.setInfoType(InfoType.list);
    this.listItems.add(aItem);
  }

  /**
   * Gets the list of maintenance items.
   *
   * @return The list of maintenance items if defined; {@code null} otherwise.
   */
  public List<EPPMaintenanceListItem> getListItems() {
    return this.listItems;
  }

  /**
   * Sets the list of maintenance items. When set to a non-{@code null} value,
   * the {@code infoType} is set to {@code InfoType.list}; otherwise
   * {@code infoType} is set to {@code InfoType.maintenance}.
   *
   * @param aListItmes
   *           the list items to set.
   */
  public void setListItems(List<EPPMaintenanceListItem> aListItmes) {
    this.listItems = aListItmes;
    if (this.listItems != null) {
      this.setInfoType(InfoType.list);
    }
    else {
      this.setInfoType(InfoType.maintenance);
    }
  }

  /**
   * Encode a DOM Element tree from the attributes of the
   * {@code EPPMaintenanceInfoResp} instance.
   *
   * @param aDocument
   *           DOM Document that is being built. Used as an Element factory.
   *
   * @return Element Root DOM Element representing the EPPMaintenanceInfoResp
   *         instance.
   *
   * @exception EPPEncodeException
   *               Unable to encode EPPMaintenanceInfoResp instance.
   */
  @Override
  protected Element doEncode(Document aDocument) throws EPPEncodeException {

    // Check required attributes
    if ((this.infoType == InfoType.maintenance) && (this.maintenance == null)) {
      throw new EPPEncodeException("Undefined maintence when infoType = maintenance in EPPMaintenanceInfoResp");
    }
    if ((this.infoType == InfoType.list) && (this.listItems == null)) {
      throw new EPPEncodeException("Undefined listItems when infoType = list in EPPMaintenanceInfoResp");
    }

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

    if (this.infoType == InfoType.maintenance) {
      EPPUtil.encodeComp(aDocument, root, this.maintenance);

    }
    else { // this.infoType == InfoType.list
      Element theTypeElm = aDocument.createElementNS(EPPMaintenanceMapFactory.NS,
            EPPMaintenanceMapFactory.NS_PREFIX + ":" + ELM_MAINTENANCE_LIST);
      root.appendChild(theTypeElm);
      EPPUtil.encodeCompList(aDocument, theTypeElm, this.listItems);
    }

    return root;
  }

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

    Element theTypeElm = EPPUtil.getFirstElementChild(aElement);

    if (theTypeElm == null) {
      throw new EPPDecodeException("Unable to find child element of " + ELM_NAME);
    }

    if (theTypeElm.getLocalName().equals(ELM_ITEM)) {
      this.setMaintenance((EPPMaintenanceItem) EPPUtil.decodeComp(aElement, EPPMaintenanceMapFactory.NS,
            EPPMaintenanceItem.ELM_LOCALNAME, EPPMaintenanceItem.class));
    }
    else if (theTypeElm.getLocalName().equals(ELM_MAINTENANCE_LIST)) {
      this.setListItems(EPPUtil.decodeCompList(theTypeElm, EPPMaintenanceMapFactory.NS,
            EPPMaintenanceListItem.ELM_LOCALNAME, EPPMaintenanceListItem.class));
    }
    else {
      throw new EPPDecodeException("Unsupported to child element " + theTypeElm.getLocalName() + " of " + ELM_NAME);
    }

  }

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

    if (this.infoType == InfoType.list) {
      clone.listItems = (List) ((ArrayList) this.listItems).clone();
    }

    return clone;
  }

  /**
   * Gets the EPP response type associated with {@code EPPMaintenanceInfoResp}.
   *
   * @return {@code EPPMaintenanceInfoResp.ELM_NAME}
   */
  @Override
  public String getType() {
    return ELM_NAME;
  }

  /**
   * Gets the EPP command namespace associated with
   * {@code EPPMaintenanceInfoResp} .
   *
   * @return {@code EPPMaintenanceMapFactory.NS}
   */
  @Override
  public String getNamespace() {
    return EPPMaintenanceMapFactory.NS;
  }

  /**
   * Compare an instance of {@code EPPMaintenanceInfoResp} with this instance.
   *
   * @param aObject
   *           Object 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 EPPMaintenanceInfoResp)) {
      return false;
    }

    EPPMaintenanceInfoResp other = (EPPMaintenanceInfoResp) aObject;

    // Maintenance
    if (!EqualityUtil.equals(this.maintenance, other.maintenance)) {
      cat.error("EPPMaintenanceInfoResp.equals(): maintenance not equal");
      return false;
    }

    // List Items
    if (!EPPUtil.equalLists(this.listItems, other.listItems)) {
      cat.error("EPPMaintenanceInfoResp.equals(): listItems not equal");
      return false;
    }

    return true;
  }

  /**
   * 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);
  }

}
