/***********************************************************
 Copyright (C) 2025 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.balance.v01;

import java.math.BigDecimal;

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

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.EPPResponse;
import com.verisign.epp.codec.gen.EPPTransId;
import com.verisign.epp.codec.gen.EPPUtil;

/**
 * A concrete EPPResponse that knows how to encode/decode Balance Info response
 * from/to XML and object instance.
 */

public class EPPBalanceInfoResp extends EPPResponse {

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

	/**
	 * XML local name for <code>EPPFeeCreData</code>.
	 */
	public static final String ELM_LOCALNAME = "infData";

	/**
	 * The poll data XML tag name
	 */
	public static final String ELM_NAME = EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_LOCALNAME;

	/**
	 * Default currency is "USD". The specification doesn't include a default,
	 * but adding it to make it easier to transition from the proprietary Balance
	 * Mapping.
	 */
	public static final String CURRENCY_DEFAULT = "USD";

	/**
	 * XML local name for the currency
	 */
	private static final String ELM_CURRENCY = "currency";

	/**
	 * The creditLimit XML tag name
	 */
	public static final String ELM_CREDIT_LIMIT = "creditLimit";

	/**
	 * The balance XML tag name
	 */
	public static final String ELM_BALANCE = "balance";

	/**
	 * The availableCredit XML tag name
	 */
	public static final String ELM_AVAILABLE_CREDIT = "availableCredit";

	/**
	 * The creditThreshold XML tag name
	 */
	public static final String ELM_CREDIT_THRESHOLD = "creditThreshold";

	/**
	 * Currency of the fees or credits.
	 */
	private String currency = CURRENCY_DEFAULT;

	/**
	 * The creditLimit
	 */
	private BigDecimal creditLimit;

	/**
	 * The balance
	 */
	private BigDecimal balance;

	/**
	 * The availableCredit
	 */
	private BigDecimal availableCredit;

	/**
	 * creditThreshold
	 */
	private BigDecimal creditThreshold;

	/**
	 * Default constructor
	 */
	public EPPBalanceInfoResp() {
	}

	/**
	 * {@code EPPBalanceInfoResp} that takes all of the required attributes.
	 * 
	 * @param aTransId
	 *           Transaction identifier information (server and optional client)
	 * @oaram aCurrency Currency used by the account balance elements. The value
	 *        of this element MUST be a three-character currency code from
	 *        [ISO4217_2015].
	 * @param aCreditLimit
	 *           Credit limit to set
	 * @param aBalance
	 *           Balance to set
	 * @param aAvailableCredit
	 *           Available credit to set
	 */
	public EPPBalanceInfoResp(EPPTransId aTransId, String aCurrency, BigDecimal aCreditLimit, BigDecimal aBalance,
	      BigDecimal aAvailableCredit) {
		super(aTransId);

		this.currency = aCurrency;
		this.creditLimit = aCreditLimit;
		this.balance = aBalance;
		this.availableCredit = aAvailableCredit;
	}

	/**
	 * {@code EPPBalanceInfoResp} that takes all of the attributes.
	 * 
	 * @param aTransId
	 *           Transaction identifier information (server and optional client)
	 * @oaram aCurrency Currency used by the account balance elements. The value
	 *        of this element MUST be a three-character currency code from
	 *        [ISO4217_2015].
	 * @param aCreditLimit
	 *           Credit limit to set
	 * @param aBalance
	 *           Balance to set
	 * @param aAvailableCredit
	 *           Available credit to set
	 * @param aCreditThreshold
	 *           Credit threshold value
	 */
	public EPPBalanceInfoResp(EPPTransId aTransId, String aCurrency, BigDecimal aCreditLimit, BigDecimal aBalance,
	      BigDecimal aAvailableCredit, BigDecimal aCreditThreshold) {

		this(aTransId, aCurrency, aCreditLimit, aBalance, aAvailableCredit);
		this.creditThreshold = aCreditThreshold;
	}

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

	/**
	 * Put all data contained in this poll response into the given XML document
	 * 
	 * @param aDocument
	 *           the DOM Document to attach data to.
	 * @return the element that is a parent of the poll data
	 * @throws EPPEncodeException
	 *            Thrown if any errors occur during the encode process
	 */
	public Element doEncode(Document aDocument) throws EPPEncodeException {

		try {
			// Validate State
			validateState();
		}
		catch (EPPCodecException e) {
			cat.error("EPPBalanceInfoResponse.doEncode(): Invalid state on encode: " + e);
			throw new EPPEncodeException("EPPBalanceInfoResponse invalid state: " + e);
		}

		if (aDocument == null) {
			throw new EPPEncodeException("aDocument is null" + " on in EPPBalanceInfoResponse.doEncode(Document)");
		}

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

		root.setAttribute("xmlns:balance", EPPBalanceMapFactory.NS);

		// Currency
		EPPUtil.encodeString(aDocument, root, this.currency, EPPBalanceMapFactory.NS,
		      EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_CURRENCY);

		// creditLimit
		EPPUtil.encodeString(aDocument, root, this.creditLimit.toString(), EPPBalanceMapFactory.NS,
		      EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_CREDIT_LIMIT);

		// balance
		EPPUtil.encodeString(aDocument, root, this.balance.toString(), EPPBalanceMapFactory.NS,
		      EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_BALANCE);

		// availableCredit
		EPPUtil.encodeString(aDocument, root, this.availableCredit.toString(), EPPBalanceMapFactory.NS,
		      EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_AVAILABLE_CREDIT);

		// creditThreshold
		if (this.hasCreditThreshold()) {
			EPPUtil.encodeString(aDocument, root, this.creditThreshold.toString(), EPPBalanceMapFactory.NS,
			      EPPBalanceMapFactory.NS_PREFIX + ":" + ELM_CREDIT_THRESHOLD);
		}

		return root;

	}

	/**
	 * Populates the data of this instance from the given XML Element which is
	 * part of a DOM Document
	 * 
	 * @param aElement
	 *           the element that is a parent of the poll data
	 * @throws EPPDecodeException
	 *            thrown if any errors occur during the decode operation
	 */
	public void doDecode(Element aElement) throws EPPDecodeException {

		// Currency
		this.currency = EPPUtil.decodeString(aElement, EPPBalanceMapFactory.NS, ELM_CURRENCY);
		if (this.currency != null && this.currency.isEmpty()) {
			this.currency = null;
		}

		// creditLimit
		this.creditLimit = new BigDecimal(EPPUtil.decodeString(aElement, EPPBalanceMapFactory.NS, ELM_CREDIT_LIMIT));

		// balance
		this.balance = new BigDecimal(EPPUtil.decodeString(aElement, EPPBalanceMapFactory.NS, ELM_BALANCE));

		// availableCredit
		this.availableCredit = new BigDecimal(
		      EPPUtil.decodeString(aElement, EPPBalanceMapFactory.NS, ELM_AVAILABLE_CREDIT));

		// creditThreshold
		String theCreditThresholdStr = EPPUtil.decodeString(aElement, EPPBalanceMapFactory.NS, ELM_CREDIT_THRESHOLD);
		if (theCreditThresholdStr != null) {
			this.creditThreshold = new BigDecimal(theCreditThresholdStr);
		}
		else {
			this.creditThreshold = null;
		}
	}

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

		EPPBalanceInfoResp theComp = (EPPBalanceInfoResp) aObject;

		// currency
		if (!((this.currency == null) ? (theComp.currency == null) : this.currency.equals(theComp.currency))) {
			cat.error("EPPBalanceInfoResp.equals(): currency not equal");
			return false;
		}

		// creditLimit
		if (!((this.creditLimit == null) ? (theComp.creditLimit == null) :
		      this.creditLimit.equals(theComp.creditLimit))) {
			cat.error("EPPBalanceInfoResp.equals(): creditLimit not equal");
			return false;
		}

		// balance
		if (!((this.balance == null) ? (theComp.balance == null) : this.balance.equals(theComp.balance))) {
			cat.error("EPPBalanceInfoResp.equals(): balance not equal");
			return false;
		}

		// availableCredit
		if (!((this.availableCredit == null) ? (theComp.availableCredit == null) :
		      this.availableCredit.equals(theComp.availableCredit))) {
			cat.error("EPPBalanceInfoResp.equals(): availableCredit not equal");
			return false;
		}

		// creditThreshold
		if (!((this.creditThreshold == null) ? (theComp.creditThreshold == null) :
		      this.creditThreshold.equals(theComp.creditThreshold))) {
			cat.error("EPPBalanceInfoResp.equals(): creditThreshold not equal");
			return false;
		}

		return true;
	}

	/**
	 * Clone {@code EPPLowBalancePollResponse}.
	 * 
	 * @return clone of {@code EPPLowBalancePollResponse}
	 * 
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	public Object clone() throws CloneNotSupportedException {

		EPPBalanceInfoResp clone = (EPPBalanceInfoResp) super.clone();

		// currency
		clone.currency = this.currency;

		// creditLimit
		if (this.creditLimit != null) {
			clone.creditLimit = new BigDecimal(this.creditLimit.toString());
		}
		else {
			clone.creditLimit = null;
		}

		// balance
		if (this.balance != null) {
			clone.balance = new BigDecimal(this.balance.toString());
		}
		else {
			clone.balance = null;
		}

		// availableCredit
		if (this.availableCredit != null) {
			clone.availableCredit = new BigDecimal(this.availableCredit.toString());
		}
		else {
			clone.availableCredit = null;
		}

		// creditThreshold
		if (creditThreshold != null) {
			clone.creditThreshold = new BigDecimal(this.creditThreshold.toString());
		}
		else {
			clone.creditThreshold = null;
		}

		return clone;
	}

	/**
	 * Validate the state of the {@code EPPBalanceInfoResp} 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 {@code 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
	 *            Thrown if the instance is in an invalid state
	 */
	void validateState() throws EPPCodecException {

		// balance
		if (!hasCurrency()) {
			throw new EPPCodecException("EPPBalanceInfoResp required currency attribute is not set");
		}

		// creditLimit
		if (creditLimit == null) {
			throw new EPPCodecException("EPPBalanceInfoResp required creditLimit attribute is not set");
		}

		// balance
		if (balance == null) {
			throw new EPPCodecException("EPPBalanceInfoResp required balance attribute is not set");
		}

		// availableCredit
		if (availableCredit == null) {
			throw new EPPCodecException("EPPBalanceInfoResp required availableCredit attribute is not set");
		}

	}

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

	/**
	 * Gets the currency value.
	 * 
	 * @return Currency if defined; {@code null} otherwise.
	 */
	public String getCurrency() {
		return this.currency;
	}

	/**
	 * Sets the currency value.
	 * 
	 * @param aCurrency
	 *           Currency value
	 */
	public void setCurrency(String aCurrency) {
		this.currency = aCurrency;
	}

	/**
	 * Gets the credit limit
	 * 
	 * @return credit limit if set; {@code null} otherwise.
	 */
	public BigDecimal getCreditLimit() {
		return this.creditLimit;
	}

	/**
	 * Sets the credit Limit
	 * 
	 * @param aCreditLimit
	 *           Credit limit to set
	 */
	public void setCreditLimit(BigDecimal aCreditLimit) {
		this.creditLimit = aCreditLimit;
	}

	/**
	 * Gets the balance
	 * 
	 * @return balance if set; {@code null} otherwise.
	 */
	public BigDecimal getBalance() {
		return this.balance;
	}

	/**
	 * Sets the balance
	 * 
	 * @param aBalance
	 *           Balance to set
	 */
	public void setBalance(BigDecimal aBalance) {
		this.balance = aBalance;
	}

	/**
	 * Gets the available credit
	 * 
	 * @return available credit if set; {@code null} otherwise.
	 */
	public BigDecimal getAvailableCredit() {
		return this.availableCredit;
	}

	/**
	 * Sets the available credit
	 * 
	 * @param aAvailableCredit
	 *           Available credit to set
	 */
	public void setAvailableCredit(BigDecimal aAvailableCredit) {
		this.availableCredit = aAvailableCredit;
	}

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

	/**
	 * Gets the credit threshold
	 * 
	 * @return Credit threshold if set; {@code null} otherwise.
	 */
	public BigDecimal getCreditThreshold() {
		return this.creditThreshold;
	}

	/**
	 * Sets the credit threshold
	 * 
	 * @param aCreditThreshold
	 *           Credit threshold to set
	 */
	public void setCreditThreshold(BigDecimal aCreditThreshold) {
		this.creditThreshold = aCreditThreshold;
	}

}
