(function (EXPORTS) { //floTokenAPI v1.0.4a
    /* Token Operator to send/receive tokens via blockchain using API calls*/
    'use strict';
    const tokenAPI = EXPORTS;

    const DEFAULT = {
        apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
        currency: floGlobals.currency || "rupee"
    }

    Object.defineProperties(tokenAPI, {
        URL: {
            get: () => DEFAULT.apiURL
        },
        currency: {
            get: () => DEFAULT.currency,
            set: currency => DEFAULT.currency = currency
        }
    });

    if (floGlobals.currency) tokenAPI.currency = floGlobals.currency;

    Object.defineProperties(floGlobals, {
        currency: {
            get: () => DEFAULT.currency,
            set: currency => DEFAULT.currency = currency
        }
    });

    const fetch_api = tokenAPI.fetch = function (apicall) {
        return new Promise((resolve, reject) => {
            console.debug(DEFAULT.apiURL + apicall);
            fetch(DEFAULT.apiURL + apicall).then(response => {
                if (response.ok)
                    response.json().then(data => resolve(data));
                else
                    reject(response)
            }).catch(error => reject(error))
        })
    }

    const getBalance = tokenAPI.getBalance = function (floID, token = DEFAULT.currency) {
        return new Promise((resolve, reject) => {
            fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
                .then(result => resolve(result.balance || 0))
                .catch(error => reject(error))
        })
    }

    tokenAPI.getTx = function (txID) {
        return new Promise((resolve, reject) => {
            fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
                if (res.result === "error")
                    reject(res.description);
                else if (!res.parsedFloData)
                    reject("Data piece (parsedFloData) missing");
                else if (!res.transactionDetails)
                    reject("Data piece (transactionDetails) missing");
                else
                    resolve(res);
            }).catch(error => reject(error))
        })
    }

    tokenAPI.sendToken = function (privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
        return new Promise((resolve, reject) => {
            let senderID = floCrypto.getFloID(privKey);
            if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
                return reject("Invalid amount");
            getBalance(senderID, token).then(bal => {
                if (amount > bal)
                    return reject(`Insufficient ${token}# balance`);
                floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options)
                    .then(txid => resolve(txid))
                    .catch(error => reject(error))
            }).catch(error => reject(error))
        });
    }

    function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) {
        return new Promise((resolve, reject) => {
            var trx = bitjs.transaction();
            trx.addinput(utxo, vout, scriptPubKey)
            trx.addoutput(receiverID, floBlockchainAPI.sendAmt);
            trx.addflodata(`send ${amount} ${token}#`);
            var signedTxHash = trx.sign(privKey, 1);
            floBlockchainAPI.broadcastTx(signedTxHash)
                .then(txid => resolve([receiverID, txid]))
                .catch(error => reject([receiverID, error]))
        })
    }

    //bulk transfer tokens
    tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) {
        return new Promise((resolve, reject) => {
            if (typeof receivers !== 'object')
                return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")

            let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers);
            let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id));
            let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0);
            if (invalidReceivers.length)
                return reject(`Invalid receivers: ${invalidReceivers}`);
            else if (invalidAmount.length)
                return reject(`Invalid amounts: ${invalidAmount}`);

            if (receiver_list.length == 0)
                return reject("Receivers cannot be empty");

            if (receiver_list.length == 1) {
                let receiver = receiver_list[0], amount = amount_list[0];
                floTokenAPI.sendToken(privKey, amount, receiver, "", token)
                    .then(txid => resolve({ success: { [receiver]: txid } }))
                    .catch(error => reject(error))
            } else {
                //check for token balance
                floTokenAPI.getBalance(sender, token).then(token_balance => {
                    let total_token_amout = amount_list.reduce((a, e) => a + e, 0);
                    if (total_token_amout > token_balance)
                        return reject(`Insufficient ${token}# balance`);

                    //split utxos
                    floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => {
                        //wait for the split utxo to get confirmation
                        floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => {
                            //send tokens using the split-utxo
                            var scriptPubKey = split_tx.vout[0].scriptPubKey.hex;
                            let promises = [];
                            for (let i in receiver_list)
                                promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey));
                            Promise.allSettled(promises).then(results => {
                                let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value));
                                let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason));
                                resolve({ success, failed });
                            })
                        }).catch(error => reject(error))
                    }).catch(error => reject(error))
                }).catch(error => reject(error))
            }

        })
    }

    tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
        return new Promise((resolve, reject) => {
            fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
                .then(result => resolve(result))
                .catch(error => reject(error))
        })
    }

    const util = tokenAPI.util = {};

    util.parseTxData = function (txData) {
        let parsedData = {};
        for (let p in txData.parsedFloData)
            parsedData[p] = txData.parsedFloData[p];
        parsedData.sender = txData.transactionDetails.vin[0].addr;
        for (let vout of txData.transactionDetails.vout)
            if (vout.scriptPubKey.addresses[0] !== parsedData.sender)
                parsedData.receiver = vout.scriptPubKey.addresses[0];
        parsedData.time = txData.transactionDetails.time;
        return parsedData;
    }

})('object' === typeof module ? module.exports : window.floTokenAPI = {});