/***********************************************************
Copyright (C) 2017 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
 ***********************************************************/
package com.verisign.epp.codec.fee.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 org.w3c.dom.Text;

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;

/**
 * The command data included in a check response, that includes:<br>
 * <ol>
 * <li>command, customName, phase, subphase, and period from the base
 * {@link EPPFeeCommand}</li>
 * <li>fees</li>
 * <li>credits</li>
 * <li>classification</li>
 * </ol>
 */
public class EPPFeeCommandData extends EPPFeeCommand {

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

  /**
   * XML local name for the classification
   */
  private static final String ELM_CLASSIFICATION = "class";

  /**
   * XML attribute name for the <code>fee:reason</code> attribute.
   */
  private final static String ATTR_LANG = "lang";

  /**
   * Default XML attribute value for the reason language.
   */
  private final static String DEFAULT_LANG = "en";

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

  /**
   * OPTIONAL standard attribute name that specifies the standard command fee
   * with a default value of "0".
   */
  public static final String ATTR_STANDARD = "standard";

  /**
   * OPTIONAL list of fees.
   */
  private List<EPPFeeValue> fees = new ArrayList<EPPFeeValue>();

  /**
   * OPTIONAL list of credits.
   */
  private List<EPPFeeCredit> credits = new ArrayList<EPPFeeCredit>();

  /**
   * XML attribute value for the <code>lang</code> attribute.
   */
  private String language = DEFAULT_LANG;

  /**
   * Reason not providing the fee information
   */
  private String reason;

  /**
   * OPTIONAL "standard" attribute with default of "0".
   */
  private boolean standard = false;

  /**
   * Default constructor.
   */
  public EPPFeeCommandData() {
  }

  /**
   * Instantiate instance by using an existing <code>EPPFeeCommand</code>
   * instance. This will clone the <code>EPPFeeCommand</code> attributes.
   * 
   * @param aCommand
   *           instance to initialize the <code>EPPFeeCommand</code> instance
   *           with.
   */
  public EPPFeeCommandData(EPPFeeCommand aCommand) {
    super(aCommand);
  }

  /**
   * Instantiate instance by using an existing <code>EPPFeeCommandData</code>
   * instance. This is equivalent to cloning the <code>EPPFeeCommandData</code>
   * instance.
   * 
   * @param aCommand
   *           instance to initialize the <code>EPPFeeCommandData</code>
   *           instance with.
   */
  public EPPFeeCommandData(EPPFeeCommandData aCommand) {
    super(aCommand);

    // Fees
    if (aCommand.fees != null) {
      for (EPPFeeValue fee : aCommand.fees) {
        try {
          this.addFee((EPPFeeValue) fee.clone());
        }
        catch (CloneNotSupportedException e) {
          // ignore
        }
      }
    }

    // Credits
    if (aCommand.credits != null) {
      for (EPPFeeCredit credit : aCommand.credits) {
        try {
          this.addCredit((EPPFeeCredit) credit.clone());
        }
        catch (CloneNotSupportedException e) {
          // ignore
        }
      }
    }

    // Reason
    this.reason = aCommand.reason;

    // Language
    this.language = aCommand.language;
  }

  /**
   * Create <code>EPPFeeCommand</code> instance with a defined command value.
   * 
   * @param aCommand
   *           Command value.
   */
  public EPPFeeCommandData(Command aCommand) {
    super(aCommand);
  }

  /**
   * Create <code>EPPFeeCommand</code> instance with a defined command value
   * and custom name value.
   * 
   * @param aCommand
   *           Command value, which should be {@link Command#CUSTOM}.
   * @param aCustomName
   *           Custom name of the command.
   */
  public EPPFeeCommandData(Command aCommand, String aCustomName) {
    super(aCommand, aCustomName);
  }

  /**
   * Create <code>EPPFeeCommand</code> instance with a defined command and
   * phase value.
   * 
   * @param aCommand
   *           Command value.
   * @param aPhase
   *           Phase value.
   */
  public EPPFeeCommandData(Command aCommand, Phase aPhase) {
    super(aCommand, aPhase);
  }

  /**
   * Create <code>EPPFeeCommand</code> instance with a defined command, phase,
   * and sub-phase value.
   * 
   * @param aCommand
   *           Command value.
   * @param aPhase
   *           Phase value.
   * @param aSubPhase
   *           Sub-phase value
   */
  public EPPFeeCommandData(Command aCommand, Phase aPhase, String aSubPhase) {
    super(aCommand, aPhase, aSubPhase);
  }

  /**
   * Create <code>EPPFeeCommand</code> instance with all attributes.
   * 
   * @param aCommand
   *           Command value, which should be {@link Command#CUSTOM}.
   * @param aCustomName
   *           Custom name of the command.
   * @param aPhase
   *           Phase for command. Set to <code>null</code> for no phase.
   * @param aSubPhase
   *           Sub-phase for command. Set to <code>null</code> for no
   *           sub-phase.
   * @param aStandard
   *           Command used the standard fee.
   * @param aPeriod
   *           Period for command. Set to <code>null</code> for no period.
   * @param aFees
   *           Fees for command. Set to <code>null</code> for no fees.
   * @param aCredits
   *           Credits for command. Set to <code>null</code> for no credits.
   * @param aReason
   *           Reason that the fee information is not available for command.
   *           Set to <code>null</code> for no reason.
   * @param aLanguage
   *           Language for reason with default of "en". Set to
   *           <code>null</code> to use default language.
   */
  public EPPFeeCommandData(Command aCommand, String aCustomName, Phase aPhase, String aSubPhase, boolean aStandard,
        EPPFeePeriod aPeriod, List<EPPFeeValue> aFees, List<EPPFeeCredit> aCredits, String aReason, String aLanguage) {
    super(aCommand, aCustomName, aPhase, aSubPhase, aPeriod);
    this.setFees(aFees);
    this.setCredits(aCredits);
    this.reason = aReason;
    this.setLanguage(aLanguage);
    this.standard = aStandard;
  }

  /**
   * Are the fees defined?
   * 
   * @return <code>true</code> if the fees are defined; <code>false</code>
   *         otherwise.
   */
  public boolean hasFees() {
    return !this.fees.isEmpty();
  }

  /**
   * Gets the list of fees if defined.
   * 
   * @return List of fees if defined; empty list otherwise.
   */
  public List<EPPFeeValue> getFees() {
    return this.fees;
  }

  /**
   * Adds a fee to the list of fees.
   * 
   * @param aFee
   *           The fee to add.
   */
  public void addFee(EPPFeeValue aFee) {
    if (aFee == null)
      return;

    this.fees.add(aFee);
  }

  /**
   * Sets the list of fees.
   * 
   * @param aFees
   *           The fees to set.
   */
  public void setFees(List<EPPFeeValue> aFees) {
    if (aFees == null)
      this.fees = new ArrayList<EPPFeeValue>();
    else
      this.fees = aFees;
  }

  /**
   * Are the credits defined?
   * 
   * @return <code>true</code> if the credits are defined; <code>false</code>
   *         otherwise.
   */
  public boolean hasCredits() {
    return !this.credits.isEmpty();
  }

  /**
   * Gets the list of credits if defined.
   * 
   * @return List of credits if defined; empty list otherwise.
   */
  public List<EPPFeeCredit> getCredits() {
    return this.credits;
  }

  /**
   * Adds a credit to the list of credits.
   * 
   * @param aCredit
   *           The credit to add.
   */
  public void addCredit(EPPFeeCredit aCredit) {
    if (aCredit == null)
      return;

    this.credits.add(aCredit);
  }

  /**
   * Sets the list of credits.
   * 
   * @param aCredits
   *           The credits to set.
   */
  public void setCredits(List<EPPFeeCredit> aCredits) {
    if (aCredits == null)
      this.credits = new ArrayList<EPPFeeCredit>();
    else
      this.credits = aCredits;
  }

  /**
   * Is the reason defined if the available attribute is <code>false</code>?
   * 
   * @return <code>true</code> if the reason is defined; <code>false</code>
   *         otherwise.
   */
  public boolean hasReason() {
    return (this.reason != null ? true : false);
  }

  /**
   * Gets the reason value.
   * 
   * @return Reason if defined; <code>null</code> otherwise.
   */
  public String getReason() {
    return this.reason;
  }

  /**
   * Sets the reason value.
   * 
   * @param aReason
   *           reason value
   */
  public void setReason(String aReason) {
    this.reason = aReason;
  }

  /**
   * Gets reason language
   *
   * @return Reason language
   */
  public String getLanguage() {
    return this.language;
  }

  /**
   * Sets language attribute.
   *
   * @param aLanguage
   *           Sets domain reason language attribute.
   */
  public void setLanguage(String aLanguage) {
    if (aLanguage == null) {
      this.language = DEFAULT_LANG;
    }
    else {
      this.language = aLanguage;
    }
  }

  /**
   * Is the command a standard command?
   * 
   * @return <code>true</code> if it is a standard fee command;
   *         <code>false</code> otherwise.
   */
  public boolean isStandard() {
    return this.standard;
  }

  /**
   * Sets whether the command is a standard fee command.
   * 
   * @param aStandard
   *           Set to <code>true</code> if the fee is refundable,
   *           <code>false</code>, or <code>null</code> if undefined.
   */
  public void setStandard(boolean aStandard) {
    this.standard = aStandard;
    cat.debug("setStandard: standard = " + this.standard);
  }

  /**
   * Clone <code>EPPFeeCommand</code> instance.
   *
   * @return clone of <code>EPPFeeCommand</code>
   *
   * @exception CloneNotSupportedException
   *               standard Object.clone exception
   */
  public Object clone() throws CloneNotSupportedException {
    EPPFeeCommandData clone = null;

    clone = (EPPFeeCommandData) super.clone();

    // Fees
    clone.fees = new ArrayList<EPPFeeValue>();
    for (EPPFeeValue fee : this.fees) {
      clone.fees.add((EPPFeeValue) fee.clone());
    }

    // Credits
    clone.credits = new ArrayList<EPPFeeCredit>();
    for (EPPFeeCredit credit : this.credits) {
      clone.credits.add((EPPFeeCredit) credit.clone());
    }

    // Reason
    clone.reason = this.reason;

    // Language
    clone.language = this.language;
    
    // Standard
    clone.standard = this.standard;       

    return clone;
  }

  /**
   * Decode the <code>EPPFeeCommand</code> element aElement DOM Element tree.
   *
   * @param aElement
   *           - Root DOM Element to decode <code>EPPFeeCommand</code> from.
   *
   * @exception EPPDecodeException
   *               Unable to decode aElement
   */
  public void decode(Element aElement) throws EPPDecodeException {

    super.decode(aElement);

    // Fees
    this.fees = EPPUtil.decodeCompList(aElement, EPPFeeExtFactory.NS, EPPFeeValue.ELM_LOCALNAME, EPPFeeValue.class);

    // Credits
    this.credits = EPPUtil.decodeCompList(aElement, EPPFeeExtFactory.NS, EPPFeeCredit.ELM_LOCALNAME,
          EPPFeeCredit.class);

    // Reason
    Element theReasonElm = EPPUtil.getElementByTagNameNS(aElement, EPPFeeExtFactory.NS, ELM_REASON);

    if (theReasonElm != null) {
      this.reason = theReasonElm.getFirstChild().getNodeValue();

      String theLang = theReasonElm.getAttribute(ATTR_LANG);

      if (theLang.length() > 0) {
        this.language = theLang;
      }
    }
    
    // Standard
    this.standard = EPPUtil.decodeBooleanAttr(aElement, ATTR_STANDARD);   
  }

  /**
   * Encode a DOM Element tree from the attributes of the
   * <code>EPPFeeCommand</code> instance.
   *
   * @param aDocument
   *           - DOM Document that is being built. Used as an Element factory.
   *
   * @return Element - Root DOM Element representing the
   *         <code>EPPFeeCommand</code> instance.
   *
   * @exception EPPEncodeException
   *               - Unable to encode <code>EPPFeeCommand</code> instance.
   */
  public Element encode(Document aDocument) throws EPPEncodeException {

    Element root = super.encode(aDocument);

    // Fees
    EPPUtil.encodeCompList(aDocument, root, this.fees);

    // Credits
    EPPUtil.encodeCompList(aDocument, root, this.credits);

    // Reason
    if (this.hasReason()) {
      Element reasonElm = aDocument.createElementNS(EPPFeeExtFactory.NS,
            EPPFeeExtFactory.NS_PREFIX + ":" + ELM_REASON);
      root.appendChild(reasonElm);

      // Language
      if (!language.equals(DEFAULT_LANG)) {
        reasonElm.setAttribute(ATTR_LANG, this.language);
      }

      // Reason
      Text aReason = aDocument.createTextNode(this.reason);
      reasonElm.appendChild(aReason);
    }
    
    // Standard
    EPPUtil.encodeBooleanAttr(root, ATTR_STANDARD, this.standard);    

    return root;
  }

  /**
   * Implements a deep <code>EPPFeeCommand</code> compare.
   *
   * @param aObject
   *           <code>EPPFeeCommand</code> instance to compare with
   *
   * @return <code>true</code> if equal; <code>false</code> otherwise
   */
  public boolean equals(Object aObject) {
    if (!(aObject instanceof EPPFeeCommandData)) {
      cat.error("EPPFeeCommandData.equals(): " + aObject.getClass().getName() + "! instanceof "
            + EPPFeeCommandData.class.getName());
      return false;
    }

    if (!super.equals((EPPFeeCommand) aObject)) {
      cat.error("EPPFeeCommandData.equals(): super EPPFeeCommand not equal");
      return false;
    }

    EPPFeeCommandData other = (EPPFeeCommandData) aObject;

    // Fees
    if (!EqualityUtil.equals(this.fees, other.fees)) {
      cat.error("EPPFeeCommandData.equals(): fees not equal");
      return false;
    }

    // Credits
    if (!EqualityUtil.equals(this.credits, other.credits)) {
      cat.error("EPPFeeCommandData.equals(): credits not equal");
      return false;
    }

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

    // Language
    if (!EqualityUtil.equals(this.language, other.language)) {
      cat.error("EPPFeeCommandData.equals(): language not equal");
      return false;
    }
    
    // Standard
    if (!EqualityUtil.equals(this.standard, other.standard)) {
      cat.error("EPPFeeCommandData.equals(): standard not equal");
      return false;
    }
    
    return true;
  }

  /**
   * Implementation of <code>Object.toString</code>, which will result in an
   * indented XML <code>String</code> representation of the concrete
   * <code>EPPCodecComponent</code>.
   *
   * @return Indented XML <code>String</code> if successful; <code>ERROR</code>
   *         otherwise.
   */
  public String toString() {
    return EPPUtil.toString(this);
  }

}
