"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 socket_client_1 = require("./socket-client");
const fetch_json_poster_1 = require("./services/fetch-json-poster");
const callback_manager_1 = require("./callback-manager");
const node_aes_1 = require("./node-aes");
const pako_zlib_1 = require("./pako-zlib");
const emitter_1 = require("./emitter");
const rxjs_1 = require("rxjs");
const tx_factory_1 = require("./tx-factory");
const utils_1 = require("../../contract/utils");
const debug = require('debug')('wallet-client');
const partiesToRaw = (parties) => parties.map((party) => [
    party.partyIdentifier,
    party.address,
    party.payList.map((payee) => {
        return [
            payee.address,
            payee.namespace,
            payee.instrument,
            payee.quantity,
            payee.publicKey || '',
            payee.signatature || '',
            payee.issuance,
            payee.metadata || '',
            payee.encumbrance || ''
        ];
    }),
    party.receiveList.map((recipient) => {
        return [
            recipient.address,
            recipient.namespace,
            recipient.instrument,
            recipient.quantity,
        ];
    }),
    '',
    '',
    !!party.mustSign,
    party.metadata,
]);
/**
 * Wallet Node Client class
 */
class WalletNodeClient extends emitter_1.WalletNodeEmitter {
    /**
     * Constructor
     *
     * @param {SocketClient} walletNodeSocket Wallet Node Socket Client
     * @param {CallbackManager} callback CallBack Manager
     */
    constructor(walletNodeSocket, callback, poster, logger) {
        super();
        this.walletNodeSocket = walletNodeSocket;
        this.callback = callback;
        this.poster = poster;
        this.logger = logger;
        this.seenTransactions = {};
        this.events = new rxjs_1.Subject();
        this.walletNodeSocket.on('Update', (id, data) => {
            if (data.MessageType === 'block') {
                data.Data.Transactions.forEach((tx) => {
                    const niceTx = tx_factory_1.createTx(tx);
                    this.seenTransactions[tx[3]] = niceTx;
                    this.emit(`tx-${tx[3]}`, niceTx);
                });
                this.emit('block', data);
            }
            if (data.MessageType === 'blockchanges') {
                if ('contracts' in data.Data) {
                    data.Data.contracts.forEach((contractAddress) => this.emit(`blockchanges-${contractAddress}`));
                }
                this.emit('blockchanges', data);
            }
            if (data.MessageType === 'contractnew') {
                data.Data.forEach((contractAddress) => this.emit(`contractnew-${contractAddress}`));
            }
        });
    }
    get events$() {
        return this.events.asObservable();
    }
    txReceipt({ message: { data: { hash } } }) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve) => {
                if (hash in this.seenTransactions) {
                    resolve(this.seenTransactions[hash]);
                    return;
                }
                this.once(`tx-${hash}`, (data) => resolve(data));
            });
        });
    }
    /**
     * Connect to Wallet Node
     *
     * @return void
     */
    connect() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.walletNodeSocket.connect();
        });
    }
    disconnect() {
        this.walletNodeSocket.disconnect();
    }
    /**
     * Send Request to Wallet Node
     *
     * @param {any} request Request JSON Object
     *
     * @returns {Promise<any>}
     */
    sendRequest(request) {
        return new Promise((resolve) => {
            this.events.next({ type: 'wnSocketRequest', payload: request });
            const requestId = this.callback.uniqueIdValue;
            request.requestid = requestId;
            this.callback.addHandler(requestId, (id, message, userData) => {
                this.events.next({ type: 'wnSocketResponse', payload: message });
                if (message.status === 'OK') {
                    resolve({ id, message, userData, status: 'OK' });
                    return;
                }
                if (typeof message.data.status === 'string' || message.status === 'FAIL') {
                    resolve({ id, message, userData, status: 'FAIL' });
                    return;
                }
            });
            this.walletNodeSocket.sendRequest(Object.assign({}, request));
        });
    }
    /**
     * Authenticate
     *
     * @param {number} userId     User Id
     * @param {string} userApiKey User API Key
     *
     * @returns {Promise<any>}
     */
    authenticate(userId, userApiKey) {
        debug('authenticate %s %s', userId, userApiKey);
        return this.sendRequest({
            MessageType: 'authenticate',
            MessageBody: {
                userid: userId,
                apikey: userApiKey,
            },
        });
    }
    /**
     * Get Blockchain State
     *
     * @returns {Promise<any>}
     */
    state() {
        return this.sendRequest({
            MessageType: 'request',
            MessageBody: {
                topic: 'state',
            },
        });
    }
    ping() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'ping',
                },
            });
        });
    }
    block() {
        return this.sendRequest({
            MessageType: 'request',
            MessageBody: {
                topic: 'block',
            },
        });
    }
    access() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'access',
                },
            });
        });
    }
    /**
     * Subscribe to Wallet Node Events
     *
     * @param {string[]} events
     *
     * @return {void}
     */
    subscribe(...events) {
        this.sendRequest({
            MessageType: 'subscribe',
            MessageBody: {
                topic: events,
            },
        }).then((result) => {
            this.logger.info('Subscribe Request Response', result);
        });
    }
    newAddress(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('newaddress', Object.assign(Object.assign({}, payload), { walletid: payload.walletId, walletId: undefined }), 'tx');
        });
    }
    namespaces(walletid, namespace, address) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'issuers',
                    walletid,
                    namespace,
                    address,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            return response;
        });
    }
    instruments(walletid) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'instruments',
                    walletid,
                }
            });
            if (result.message.status !== 'OK') {
                throw new Error(result.message.data.status);
            }
            const mapFn = raw => ({
                address: raw[0],
                namespace: raw[1],
                instrument: raw[2],
                metadata: raw[3]
            });
            return result.message.data.map(mapFn);
        });
    }
    namespace(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('namespace', payload, 'tx');
        });
    }
    addresses(walletid) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'addresses',
                    walletid,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            const mapFn = raw => ({ address: raw[0], publicKey: raw[1] });
            return response.message.data.map(mapFn);
        });
    }
    contracts(walletid) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'contract',
                    walletid,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            return response;
        });
    }
    /**
     * Get a single Contract by address
     */
    contract(walletid, address) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'request',
                MessageBody: {
                    topic: 'contract',
                    walletid,
                    address,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            return response;
        });
    }
    // TX Requests
    registerNamespace(walletid, address, name, metadata = '') {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    txtype: 'nsreg',
                    walletid,
                    name,
                    address,
                    metadata,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            return response;
        });
    }
    registerAsset(walletid, address, namespace, instrument, metadata = '') {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    txtype: 'asreg',
                    walletid,
                    address,
                    namespace,
                    instrument,
                    metadata,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(response.message.data.status);
            }
            return response;
        });
    }
    issueAsset(walletid, address, namespace, instrument, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    txtype: 'asiss',
                    walletid,
                    address,
                    namespace,
                    instrument,
                    amount,
                }
            });
            if (response.status === 'FAIL') {
                if (response.message.data.status) {
                    throw new Error(response.message.data.status);
                }
                else {
                    throw new Error(response.message.data.error);
                }
            }
            return response;
        });
    }
    encumberAsset(walletid, address, subjectaddress, reference, namespace, instrument, amount, beneficiaries, administrators, iscumulative, metadata) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    txtype: 'encum',
                    walletid,
                    address,
                    subjectaddress,
                    reference,
                    namespace,
                    instrument,
                    amount,
                    beneficiaries,
                    administrators,
                    iscumulative,
                    metadata,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(`Encumbrance error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`Encumbrance error: ${response.message.data.error}`);
            }
            return response;
        });
    }
    unencumberAsset(walletid, address, subjectaddress, reference, namespace, instrument, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    txtype: 'unenc',
                    walletid,
                    address,
                    subjectaddress,
                    reference,
                    namespace,
                    instrument,
                    amount,
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(`Unencumber error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`Unencumber error: ${response.message.data.error}`);
            }
            return response;
        });
    }
    sigUpdate(userId, walletIds) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.poster.post('http://localhost:13535/siguserupdate', {
                MessageType: 'servermessage',
                MessageBody: {
                    MessageType: '',
                    Data: {
                        ResponseToken: '',
                        Token: 'Ngl4E28e0xI8tmkVjQsDmgt5K71gT/UY4oYvfASS7h/w11tHUg**nY+sHZ2PrBmdj6wVn',
                        updatemsg: {
                            userid: userId,
                            wallets: walletIds
                        }
                    }
                }
            });
            if (response.status !== 'OK') {
                throw new Error('Sigupdate failed');
            }
            return response;
        });
    }
    createContract(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: utils_1.contractToRaw(payload)
            });
            if (response.status === 'FAIL') {
                throw new Error(`Contract create error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`Contract create error: ${response.message.data.error}`);
            }
            return response;
        });
    }
    /**
     * Sign an authorisaion in given contract.
     * @param walletId Wallet ID of authoriser
     * @param address Address of authoriser
     * @param contractAddress Address of contract
     * @param authorisationId ID of authorisation
     * @param metadata User metadata
     */
    contractAuthorise(walletId, address, contractAddress, authorisationId, metadata) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    topic: 'cocom',
                    walletid: walletId,
                    address,
                    function: 'dvp_uk_commit',
                    contractaddress: contractAddress,
                    contractdata: {
                        contractfunction: 'dvp_uk_commit',
                        authorise: [
                            [address, authorisationId, '', metadata || '', false]
                        ]
                    }
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(`Contract commit error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`Contract commit error: ${response.message.data.error}`);
            }
            return response;
        });
    }
    /**
     * Sign a party, including all payments.
     * @param walletId Signing party's wallet ID
     * @param address Signing party's address
     * @param contractAddress Contract address to sign
     * @param party Party to sign
     */
    signParty(walletId, address, contractAddress, party) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: 'tx',
                MessageBody: {
                    topic: 'cocom',
                    walletid: walletId,
                    address,
                    function: 'dvp_uk_commit',
                    contractaddress: contractAddress,
                    contractdata: {
                        contractfunction: 'dvp_uk_commit',
                        commitment: party.payList.map((payment, idx) => [idx, payment.namespace, payment.instrument, payment.quantity, '', '']),
                        receive: party.receiveList.map((receipt, idx) => [idx, receipt.address]),
                        party: [party.partyIdentifier, '', ''],
                    }
                }
            });
            if (response.status === 'FAIL') {
                throw new Error(`Contract commit error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`Contract commit error: ${response.message.data.error}`);
            }
            return response;
        });
    }
    holders(walletId, namespace, instrument) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('holders', { walletid: walletId, namespace, classid: instrument });
        });
    }
    holdings(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('holdings', { walletid: payload.walletId, walletId: undefined });
        });
    }
    holdingsDetail(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('holdingsdetail', payload);
        });
    }
    holdingsDetails(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('holdingsdetails', payload);
        });
    }
    /**
     * Get the next block.
     */
    nextBlock() {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise(resolve => this.once('block', resolve));
        });
    }
    /**
     * Wait for contract to be created in state.
     */
    newContract(contractAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise(resolve => this.once(`contractnew-${contractAddress}`, resolve));
        });
    }
    /**
     * Listen for updates to contracts.
     */
    contractChanges(contractAddress) {
        return new rxjs_1.Observable(subscriber => {
            const cb = () => {
                subscriber.next(contractAddress);
            };
            if (contractAddress) {
                this.on(`blockchanges-${contractAddress}`, cb);
                return () => this.off(`blockchanges-${contractAddress}`, cb);
            }
            this.on('blockchanges', cb);
            return () => this.off('blockchanges', cb);
        });
    }
    transferAsset(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('astra', payload, 'tx');
        });
    }
    encumumberPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('encum', payload, 'tx');
        });
    }
    excersiseEncumbrancePayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('exenc', payload, 'tx');
        });
    }
    issueAssetPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('asiss', payload, 'tx');
        });
    }
    issuerTransferPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('istra', payload, 'tx');
        });
    }
    nullTxPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('txnul', Object.assign({}, payload), 'tx');
        });
    }
    registerAssetPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('asreg', payload, 'tx');
        });
    }
    registerNamespacePayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('nsreg', payload, 'tx');
        });
    }
    transferFromManyPayload(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.payloadRequest('istfm', payload, 'tx');
        });
    }
    payloadRequest(name, payload = {}, type = 'request') {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.sendRequest({
                MessageType: type,
                MessageBody: Object.assign(Object.assign(Object.assign({}, (type === 'request' && { topic: name })), (type === 'tx' && { txtype: name })), payload)
            });
            if (response.status === 'FAIL') {
                throw new Error(`${name} error: ${response.message.data.status}`);
            }
            if (response.status === 'OK' && response.message.data.error) {
                throw new Error(`${name} error: ${response.message.data.error}`);
            }
            return response.message.data;
        });
    }
}
exports.WalletNodeClient = WalletNodeClient;
function newClient(stateService) {
    return __awaiter(this, void 0, void 0, function* () {
        const logger = {
            info() { },
            error() { },
            debug() { },
        };
        const node = stateService.detectResults['wnSocket'];
        const callback = new callback_manager_1.CallbackManager();
        const socket = new socket_client_1.SocketClient(true, ['authenticate'], new node_aes_1.NodeAes(), new pako_zlib_1.PakoZlib(), logger, callback, node.scheme, stateService.endpoint, node.port, node.path);
        const fetchJsonPoster = new fetch_json_poster_1.FetchJsonPoster();
        const client = new WalletNodeClient(socket, callback, fetchJsonPoster, logger);
        socket.on('OnOpen', () => {
            client.authenticate(stateService.userId, stateService.apiKey)
                .then(({ message }) => __awaiter(this, void 0, void 0, function* () {
                if (message.status !== 'OK') {
                    throw new Error('Failed to authenticate');
                }
                socket.authenticate('token', message.data.token);
                client.subscribe('block', 'stateview', 'contractnew', 'blockchanges', 'contractexpire', 'contractcomplete', 'contractcompletedetails');
            }));
        });
        return client;
    });
}
exports.newClient = newClient;
