/*
 * last modified---
 * 	12-12-23 break out from Enft.js
 * 	11-20-23 in PayeeConfig, use BigNumber for all value fields and arithmetic
 * 	11-08-23 add PayeeConfig.nextSeq and remove renumberings on delete, to avoid
 * 			 confusing React
 * 	10-26-23 recode delPayee() to use Array.splice() to remove
 * 	09-20-23 supply bound versions of certain methods in PayeeConfig, SpendPayee
 * 	08-29-23 add PayeeConfig.symbol, PayeeConfig.active
 * 	08-11-23 add memo and rand fields to SpendPayeeS
 * 	08-10-23 make adding and subtracting SpendPayeeS affect the total
 *
 * purpose---
 * 	provide classes to encapsulate Payee configurations and individual payees
 */

const BigNumber = require("bignumber.js");

// class describing a spend payee for eNFTs
class SpendPayee {
	constructor() {
		this.sequence = 1;
		// in ethers, x 1e18 (converted to wei) when used
		this.amount = new BigNumber(0);
		this.address = '';
		this.rand = '';
		this.memo = '';
		// supply a bound version of setRand()
		const unboundSetRand = this.setRand;
		this.boundSetRand = unboundSetRand.bind(this);
	}

	// method to set the salt value with 16 random bytes (in base64)
	setRand() {
		const randArray = new Uint8Array(8);
		window.crypto.getRandomValues(randArray);
		let rand = btoa(randArray);
		// NB: 22 bytes base64 == 16 bytes decoded
		this.rand = rand.substring(0, 22);
	}
}

// class describing a spend payee configuration for a given asset type
class PayeeConfig {
	constructor() {
		this.asset = '';
		this.total = new BigNumber(0);
		this.payees = [];		// of SpendPayeeS
		this.selectedENFTs = new Map();		// Id to Enft
		this.symbol = '';
		this.active = false;
		this.nextSeq = 1;
		// supply a bound version of addPayee()
		const unboundAddPayee = this.addPayee;
		this.boundAddPayee = unboundAddPayee.bind(this);
		// supply a bound version of delPayee()
		const unboundDelPayee = this.delPayee;
		this.boundDelPayee = unboundDelPayee.bind(this);
	}

	/* add a new payee
	 * @param address the payee account
	 * @param amount the amount to pay (in wei)
	 * @return the payee added
	 */
	addPayee(address, amount) {
		const payee = new SpendPayee();
		payee.address = address;
		payee.amount = payee.amount.plus(amount);
		payee.sequence = this.nextSeq++;
		this.payees.push(payee);
		this.total = this.total.plus(amount);
		return payee;
	}

	/* remove an existing payee
	 * @param seqId the sequence number of the payee to be removed
	 */
	delPayee(seqId) {
		// first, reduce total
		const match = this.payees.find(elt => elt.sequence === seqId);
		const delIdx = this.payees.findIndex(elt => elt.sequence === seqId);
		if (match !== undefined && delIdx !== -1) {
			this.total = this.total.minus(match.amount);
		} else {
			console.error("No payee match found for seqId " + seqId);
			return;
		}

		// remove the appropriate index from the array
		this.payees.splice(delIdx, 1);
	}

	/* add an eNFT to the mapping of selected eNFTs
	 * @param id the unique ID of the eNFT being selected
	 * @param enft the eNFT metadata
	 */
	selectENFT(id, enft) {
		if (!this.selectedENFTs.has(id) && enft !== undefined) {
			this.selectedENFTs.set(id, enft);
		}
	}

	/* remove an eNFT from the mapping of selected eNFTs
	 * @param id the unique ID of the eNFT being deselected
	 */
	deselectENFT(id) {
		if (this.selectedENFTs.has(id)) {
			this.selectedENFTs.delete(id);
		}
	}

	/* determine whether an eNFT is selected
	 * @param id the unique ID of the eNFT
	 * @return true if selected, else false
	 */
	isENFTselected(id) {
		return this.selectedENFTs.has(id);
	}
}

export default PayeeConfig;
export { SpendPayee };
