/*
 * last modified---
 * 	05-31-23 add getMVOStaking()
 * 	05-24-23 new
 *
 * purpose--
 * 	class to fetch, store, and provide access to data about all MVOs on a chain
 */

import MVOComm from './MVOComm.js';

// class to store a 'struct MVO{}' from the MVOStaking contract (plus mvoId)
class MVOStaking {
	constructor() {
		// MVO Id
		this.mvoId = '';

		// string listenURI
		this.listenURI = '';

		// address payable stakingAddress
		this.stakingAddress = '';

		// address signingAddress
		this.signingAddress = '';

		// string encrPubkey
		this.encrPubkey = '';

		// uint256 stakingQuantum
		this.stakingQuantum = 0n;

		// uint120 memberPoolId
		this.memberPoolId = 0;

		// uint120 rating
		this.rating = 0;

		// bool active
		this.active = true;
	}

	// set the MVOId
	setMVOId(mId) {
		this.mvoId = mId;
	}

	/* configure from a record retrieved from the blockchain
	 * @param record a MVOStaking record as downloaded from the blockchain
	 */
	config(record) {
		this.listenURI = record.listenURI;
		this.stakingAddress = record.stakingAddress;
		this.signingAddress = record.signingAddress;
		this.encrPubkey = record.encrPubkey;
		this.stakingQuantum = record.stakingQuantum;
		this.memberPoolId = record.memberPoolId;
		this.rating = record.rating;
		this.active = record.active;
	}
}

// class to store all that is known about the MVOs on the current chain
class MVOs {
	constructor(chainId) {
		// chain Id
		this.chainId = chainId;

		// list of MVOs
		this.availableMVOs = [];

		// currently selected MVO (elt of availableMVOs)
		this.selectedMVO = undefined;

		// staking total of all MVOs on this chain (at 1e-18 scale)
		this.stakingTotal = 0;
	}


	/* init for a specific chain
	 * @param chain the chainId
	 * @param mvoList the MVOStaking array
	 * @param stakingTotal the total of the stakings in the array
	 */
	initialize(chain, mvoList, stakingTotal) {
		this.chainId = chain;
		this.stakingTotal = stakingTotal;
		this.availableMVOs = mvoList;
		this.selectedMVO = undefined;
	}

	/* obtain the staking record for a given MVO
	 * @param mvoId the ID of the MVO
	 */
	getMVOStaking(mvoId) {
		var staking = undefined;
		for (let idx = 0; idx < this.availableMVOs.length; idx++) {
			if (this.availableMVOs[idx].mvoId === mvoId) {
				staking = this.availableMVOs[idx];
				break;
			}
		}
		return staking;
	}

	/* init an MVO communications object for the selected MVO
	 * @param purpose the reason we're talking to this MVO (one of: "wallet",
	 * "receipts", "deposit", "withdraw", "spend")
	 * @param doEncrypt encrypt transaction messages (bool)
	 * @return the communicator object
	 */
	getMVOCommunicator(purpose, doEncrypt) {
		this.pickRandomMVO();
		if (this.selectedMVO === undefined) {
			console.error("Cannot get MVO communicator; no MVO selected");
			return null;
		}
		const mvo = this.selectedMVO;
		const communicator = new MVOComm(mvo.mvoId,
										 mvo.listenURI,
										 purpose,
										 doEncrypt);
		communicator.setReqChain(this.chainId);
		communicator.setMVOPubkey(mvo.encrPubkey);

		// if purpose is "wallet" or "receipts", alter the port on the URL
		const urlStr = mvo.listenURI;
		if (purpose === 'wallet' || purpose === 'receipts') {
			let colonIdx = urlStr.lastIndexOf(":");
			var port = urlStr.substring(colonIdx+1);
			// we should have nnnnn/ for port
			let slashIdx = port.indexOf("/");
			if (slashIdx === port.length-1) {
				port = port.substring(0, port.length-1);
			}
			let portNum = +port;
			// receipts port is +4 from mvo port
			portNum += 4;
			const revisedUrl = urlStr.substring(0, colonIdx)
								+ ":" + portNum + "/download";
			communicator.setMVOURL(revisedUrl);
		}
		else {
			communicator.setMVOURL(urlStr + "mvo");
		}
		return communicator;
	}

	/* select the MVO to use, based on weighted staking algorithm
	 * @return randomly selected MVO
	 */
	pickRandomMVO() {
		// check that we're initialized
		if (this.availableMVOs.length === 0 || this.stakingTotal === 0) {
			console.error("No available MVOs to select from");
			this.selectedMVO = undefined;
			return this.selectedMVO;
		}

		// pick a pseudo-random float between 0 and 1
		const pickPct = Math.random();

		// loop through the list of available MVOs
		var runningTotal = 0;
		var selectedMVO = undefined;
		for (const mvo of this.availableMVOs) {
			if (!mvo.active) continue;

			// weight by the size of each MVO's staking
			const ratio = (mvo.stakingQuantum / 1e18) / this.stakingTotal;
			runningTotal += ratio;
			if (pickPct <= runningTotal) {
				selectedMVO = mvo;
				break;
			}
		}
		this.selectedMVO = selectedMVO;
		return selectedMVO;
	}
}

export default MVOs;
export { MVOStaking };
