"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./utils");
const utils_2 = require("./contract/utils");
const debug = require('debug')('SetlApiSDK');
class SetlClient {
    /**
     * Creates an instance of SetlApiSDK.
     */
    constructor(stateService, connectionService, restService, jwtService, ep, opts) {
        this.stateService = stateService;
        this.connectionService = connectionService;
        this.restService = restService;
        this.jwtService = jwtService;
        this.events = this.connectionService.events$;
        this.blockObs = this.connectionService.block$;
        if (ep.startsWith('https://')) {
            this.stateService.endpoint = ep.replace('https://', '');
            this.stateService.opts = Object.assign({ port: 443 }, opts);
        }
        else {
            this.stateService.endpoint = ep.replace('http://', '');
            this.stateService.opts = opts;
        }
    }
    get events$() {
        return this.events;
    }
    get block$() {
        return this.blockObs;
    }
    switchToSocket() {
        this.stateService.transport = 'socket';
    }
    switchToRpc() {
        this.stateService.transport = 'rpc';
    }
    disconnect() {
        this.connectionService.memberSocket.disconnect();
        this.connectionService.walletSocket.disconnect();
    }
    /**
     * ==== MEMBERNODE api/ ====
     */
    /**
     * Get the instance endpoint
     *
     * @returns {string}
     * @memberof SetlApiSDK
     */
    get endpoint() {
        return this.stateService.endpoint;
    }
    /**
     * Get the instance options
     *
     * @returns {Types.Options}
     * @memberof SetlApiSDK
     */
    get options() {
        return this.stateService.opts;
    }
    /**
     * Get the instance session token
     *
     * @returns {string}
     * @memberof SetlApiSDK
     */
    get sessionToken() {
        return this.stateService.token;
    }
    /**
     * Sets an Session Token
     *
     * @memberof SetlApiSDK
     */
    setSessionToken(token) {
        this.stateService.token = token;
        return this.stateService.token;
    }
    /**
     * Request using our older style JWT endpoints, allows all requests/tx to be sent to either
     * membernode or walletnode
     *
     * @param apiKey - API Key
     * @param userId - User ID
     * @param payload - object to send
     * @param requestType - {request,tx} switches between request and tx, default is request
     * @param endpoint - {wallet,member} switches between walletnode for member, default is wallet
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    jwt(payload, requestType = 'request', endpoint = 'wallet') {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.stateService.token.length) {
                yield this.jwtService.getToken();
            }
            return this.jwtService.call(payload, requestType, endpoint);
        });
    }
    /**
     * Request a new token for the given username and password.
     * Token is stored on the instance as sessionToken and used automatically against future requests
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    login(payload) {
        return this.restService.getToken(payload);
    }
    /**
     * Request the current authorised users details
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    me() {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.memberSocket.me();
            case 'rpc':
                return this.restService.me();
        }
    }
    /**
     * Request all wallets or an individual wallet by it's unique ID
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    wallets(payload = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    const allWallets = yield this.connectionService.memberSocket.walletAccess();
                    if (payload && payload.walletId) {
                        return allWallets.Data.filter(w => w.walletID === +payload.walletId);
                    }
                    return allWallets.Data;
                case 'rpc':
                    /* Request a single wallet, by ID if present. */
                    if (payload && payload.walletId)
                        return this.restService.wallet(payload);
                    /* Request all wallets. */
                    return this.restService.allWallets();
            }
        });
    }
    /**
     * Request all messages for the currently authorised user
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    messages(fromWalletId, toWalletId) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    return (yield this.connectionService.memberSocket.messages(fromWalletId, toWalletId)).Data;
                case 'rpc':
                    return this.restService.allMessages();
            }
        });
    }
    /**
     * Request for a message to be decrypt.
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    decryptMessage(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.memberSocket.decrypt(payload.aliceWalletId, payload.bobWalletId, payload.encryptedMessage);
            case 'rpc':
                return this.restService.decryptMessage(payload);
        }
    }
    /**
     * Request for a message to be encrypt.
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    encryptMessage(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.memberSocket.encrypt(payload.aliceWalletId, payload.bobWalletId, payload.message);
            case 'rpc':
                return this.restService.encryptMessage(payload);
        }
    }
    /**
     * ==== REQUEST api/request ====
     */
    /**
     * Request the currently authorised users access privileges
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    access(payload) {
        return this.restService.access(payload);
    }
    /**
     * Request the currently authorised users address which they may use.
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    addresses(walletId) {
        return __awaiter(this, void 0, void 0, function* () {
            // TODO: Call the addresses endpoint on the membernode as it returns labels etc
            return this.restService.addresses(walletId);
        });
    }
    /**
     * Request the currently authorised users available asset classes
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    allAssetClasses() {
        return this.restService.allAssetClasses();
    }
    /**
     * Request the currently authorised users available namespaces
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    allNamespaces() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.restService.allNamespaces();
        });
    }
    /**
     * Request details of a specific asset class
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    assetClasses(payload) {
        return this.restService.assetClasses(payload);
    }
    /**
   * Request details of the last block in the chain
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    block() {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.block();
            case 'rpc':
                return this.restService.block();
        }
    }
    /**
     * Request details of a specific contract
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    contract(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.contract(payload.walletid, payload.contractid);
            case 'rpc':
                return this.restService.contract(payload);
        }
    }
    /**
     * Request which addresses hold assets
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    holders(payload) {
        return this.restService.holders(payload);
    }
    /**
     * Request a summary of who holds an asset or assets controlled by the user
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    holdings(payload) {
        return this.restService.holdings(payload);
    }
    /**
     * Request a detailed summary of who holds an asset or assets controlled by the user
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    holdingsDetail(payload) {
        return this.restService.holdingsDetail(payload);
    }
    /**
     * Request a detailed summary of who holds an asset or assets controlled by the user
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    holdingsDetails(payload) {
        return this.restService.holdingsDetails(payload);
    }
    /**
     * Request details of a specific namespace
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    namespace(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.namespace(payload);
            case 'rpc':
                return this.restService.namespace(payload);
        }
    }
    /**
     * Request to create a new address
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    newAddress(payload) {
        return this.restService.newAddress(payload);
    }
    /**
     * Performs a simple ping request
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    ping() {
        return this.restService.ping();
    }
    /**
     * Request details of the Power of Attorney powers issued to an address
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    poaAttorneyDetails(payload) {
        return this.restService.poaAttorneyDetails(payload);
    }
    /**
     * Request details of the Power of Attorney powers issued by an address
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    poaOwnerDetails(payload) {
        return this.restService.poaOwnerDetails(payload);
    }
    /**
     * Create a unique prefix for request IDs
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    requestPrefix() {
        return this.restService.requestPrefix();
    }
    /**
     * Request details of the current chain state
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    state() {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.state();
            case 'rpc':
                return this.restService.state();
        }
    }
    /**
     * ==== TX api/tx ====
     */
    /**
/**
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    transferAsset(payload) {
        return this.restService.transferAsset(payload);
    }
    /**
     * Encumber
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    encumber(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.encumberAsset(payload.walletId, payload.address, payload.subjectAddress || '', payload.reference || '', payload.namespace, payload.classId, payload.amount, payload.beneficiaries.map(b => [b.address, b.startTime, b.endTime]), payload.administrators.map(a => [a.address, a.startTime, a.endTime]), payload.isCumulative || true, payload.metadata || '');
            case 'rpc':
                return this.restService.encumber(payload);
        }
    }
    /**
     * Exercise encumbrance
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    exerciseEncumbrance(payload) {
        return this.restService.exerciseEncumbrance(payload);
    }
    /**
     * Perform the Issueing of an asset
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    issueAsset(payload) {
        return this.restService.issueAsset(payload);
    }
    /**
     * Perform an Issuer Transfer
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    issuerTransfer(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.issuerTransferPayload(payload);
            case 'rpc':
                return this.restService.issuerTransfer(payload);
        }
    }
    /**
     * Null
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    null(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.nullTxPayload({ walletid: payload.walletId, address: payload.address });
            case 'rpc':
                return this.restService.null(payload);
        }
    }
    /**
     * Perform the Registration of an Asset
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    registerAsset(payload) {
        return this.restService.registerAsset(payload);
    }
    /**
     * Perform the Registration of a Namespace
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    registerNamespace(payload) {
        return this.restService.registerNamespace(payload);
    }
    /**
     * Transfer from Many
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    transferFromMany(payload) {
        switch (this.stateService.transport) {
            case 'socket':
                return this.connectionService.walletSocket.transferFromManyPayload(payload);
            case 'rpc':
                return this.restService.transferFromMany(payload);
        }
    }
    /**
     * Create a new Transaction
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    createTx(payload) {
        return this.restService.createTx(payload);
    }
    /**
     * Unencumber
     *
     * @returns {Promise<any>}
     * @memberof SetlApiSDK
     */
    unencumber(payload) {
        return this.restService.unencumber(payload);
    }
    getTxs() {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    return this.connectionService.memberSocket.getTxs();
                case 'rpc':
                    return this.jwt({ topic: 'repgettxs' }, 'request', 'member');
            }
        });
    }
    createContract(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            const payTotal = payload.parties.reduce((acc, party) => {
                return acc + party.payList.reduce((acc, payee) => acc + payee.quantity, 0);
            }, 0);
            const receiveTotal = payload.parties.reduce((acc, party) => {
                return acc + party.receiveList.reduce((acc, recipient) => acc + recipient.quantity, 0);
            }, 0);
            if (payTotal !== receiveTotal) {
                const diff = receiveTotal - payTotal;
                const sign = (diff < 0) ? '' : '+';
                throw new Error(`Pay/Receive lists do not balance. Receive is ${sign}${diff}`);
            }
            if (!payload.encumbrance) {
                payload.encumbrance = '';
            }
            if (!payload.addencumbrance) {
                payload.addencumbrance = [];
            }
            if (!payload.authorisations) {
                payload.authorisations = [];
            }
            if (!payload.parameters) {
                payload.parameters = [];
            }
            switch (this.stateService.transport) {
                case 'socket':
                    return this.connectionService.walletSocket.createContract(payload);
                case 'rpc':
                    return this.jwt(utils_2.contractToRaw(payload), 'tx', 'wallet');
            }
        });
    }
    getTx(walletId, txHash) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    return this.connectionService.memberSocket.getTx(walletId, txHash);
                case 'rpc':
                    return this.jwt({ topic: 'repgettx', walletId, txHash }, 'request', 'member');
            }
        });
    }
    txReceipt(...payload) {
        return __awaiter(this, void 0, void 0, function* () {
            const promises = payload.map((payload) => __awaiter(this, void 0, void 0, function* () {
                let hash = '';
                if ('message' in payload) {
                    hash = payload.message.data.hash;
                }
                if ('hash' in payload) {
                    hash = payload.hash;
                }
                if ('data' in payload && 'hash' in payload.data) {
                    hash = payload.data.hash;
                }
                if (!hash.length) {
                    throw new Error('Invalid TX');
                }
                return Promise.race([this.connectionService.walletSocket.txReceipt({ message: { data: { hash } } }), utils_1.wait(15000)]);
                /** TODO: Wait for membernode patch so RPC works
                switch (this.stateService.transport) {
                    case 'socket':
                        if (!('message' in payload)) {
                            throw new Error('Invalid Tx');
                        }
                        return Promise.race([this.connectionService.walletSocket.txReceipt(payload), wait(15000)]);
                    case 'rpc':
                        let hash: string = '';
                        if ('hash' in payload) {
                            hash = payload.hash;
                        }
                        if ('data' in payload && 'hash' in payload.data) {
                            hash = payload.data.hash;
                        }
                        if (!hash.length) {
                            throw new Error('Invalid TX');
                        }
                        debug('Getting TX receipt for hash: %s', hash);
                        // Start 30 second loop
                        const start = Date.now();
                        let diff = start;
                        let found: false | any = false;
                        while (!found || diff > 15000) {
                            const response = await this.getTx(1, hash);
                            if (response.data.tx.length > 0) {
                                found = response.data.tx[0];
                            }
                            await wait(1000);
                            console.log('diff', diff);
                            diff = Date.now() - start;
                        }
                        if (found !== false) {
                            return found;
                        }
                        throw new Error('TX failed');
                }
                */
            }));
            return Promise.all(promises);
        });
    }
    contractAuthorise(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    return this.connectionService.walletSocket.contractAuthorise(payload.walletId, payload.address, payload.contractAddress, payload.authorisationId, payload.metadata || '');
                case 'rpc':
                    return this.jwt({
                        txType: 'cocom',
                        walletid: payload.walletId,
                        address: payload.address,
                        function: 'dvp_uk_commit',
                        contractaddress: payload.contractAddress,
                        contractdata: {
                            contractfunction: 'dvp_uk_commit',
                            authorise: [
                                [payload.address, payload.authorisationId, '', payload.metadata || '', false]
                            ]
                        }
                    }, 'tx', 'wallet');
            }
        });
    }
    signParty(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.stateService.transport) {
                case 'socket':
                    return this.connectionService.walletSocket.signParty(payload.walletId, payload.address, payload.contractAddress, payload.party);
                case 'rpc':
                    return this.jwt({
                        txType: 'cocom',
                        walletid: payload.walletId,
                        address: payload.address,
                        function: 'dvp_uk_commit',
                        contractaddress: payload.contractAddress,
                        contractdata: {
                            contractfunction: 'dvp_uk_commit',
                            commitment: payload.party.payList.map((payment, idx) => [idx, payment.namespace, payment.instrument, payment.quantity, '', '']),
                            receive: payload.party.receiveList.map((receipt, idx) => [idx, receipt.address]),
                            party: [payload.party.partyIdentifier, '', ''],
                        }
                    }, 'tx', 'wallet');
            }
        });
    }
}
exports.SetlClient = SetlClient;
