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());
    });
};
import { randomBytes } from "crypto";
import _ from "lodash";
import { logger } from "../..";
import { toBuffer, toString, toQueryObjectGTV } from "../formatter";
import { decodeValue, encodeValue } from "../gtx/serialization";
import { handleGetResponse, handlePostResponse, } from "../restclient/restclient";
import { requestWithRetry } from "../restclient/restclientutil";
import { Method } from "../restclient/types";
import * as gtxTool from "../gtx/gtx";
import { ResponseStatus, } from "./types";
import { LostMessageError, TxRejectedError, UnexpectedResponseError, UnvalidTxRidException, } from "../restclient/errors";
import { callbackPromiseBuilder, getConfig, getGTXFromBufferOrTransactionOrOperation, getSerializedGTX, handlePostResponsePromisified, validTxRID, } from "./utils";
import { Web3PromiEvent } from "../promiEvent/promiEvents";
import { NumberOfSignersAndSignaturesException } from "../gtx/errors";
import { makeKeyPair } from "../encryption/encryption";
import { getTransactionRIDException } from "./errors";
export function createClient(settings) {
    return __awaiter(this, void 0, void 0, function* () {
        return {
            config: yield getConfig(settings),
            query(nameOrQueryObject, args, callback) {
                return __awaiter(this, void 0, void 0, function* () {
                    let _name, _args;
                    if (typeof nameOrQueryObject === "string") {
                        _name = nameOrQueryObject;
                        _args = args;
                    }
                    else {
                        _name = nameOrQueryObject === null || nameOrQueryObject === void 0 ? void 0 : nameOrQueryObject.name;
                        _args = nameOrQueryObject === null || nameOrQueryObject === void 0 ? void 0 : nameOrQueryObject.args;
                    }
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.POST, `query_gtv/${this.config.blockchainRID}`, this.config, encodeValue(toQueryObjectGTV(_name, _args)));
                    return new Promise((resolve, reject) => {
                        handlePostResponse(error, statusCode, rspBody, callbackPromiseBuilder(reject, resolve, callback));
                    });
                });
            },
            signTransaction(transaction, signMethod, callback) {
                return __awaiter(this, void 0, void 0, function* () {
                    const signMethodIsPrivKey = "privKey" in signMethod;
                    logger.debug(`signing transaction with ${signMethodIsPrivKey
                        ? "privkey: " + toString(signMethod.privKey)
                        : "signature provider [pubkey: " + toString(signMethod.pubKey) + "]"}`);
                    const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRID);
                    try {
                        const signedTx = yield (signMethodIsPrivKey
                            ? gtxTool.sign(gtx, signMethod.privKey, signMethod.pubKey)
                            : gtxTool.sign(gtx, signMethod));
                        const gtxBytes = getSerializedGTX(signedTx);
                        if (typeof callback === "function") {
                            callback(null, gtxBytes);
                        }
                        return gtxBytes;
                    }
                    catch (error) {
                        if (typeof callback === "function") {
                            callback(error, null);
                        }
                        throw new Error(error);
                    }
                });
            },
            sendTransaction(transaction, callback) {
                const promiEvent = new Web3PromiEvent((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                    try {
                        const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRID);
                        if (gtx.signers.length !== gtx.signatures.length) {
                            reject(new NumberOfSignersAndSignaturesException());
                        }
                        const gtxBytes = getSerializedGTX(gtx);
                        const transactionObject = {
                            tx: gtxBytes.toString("hex"),
                        };
                        const { error, statusCode, rspBody } = yield requestWithRetry(Method.POST, "tx/" + this.config.blockchainRID, this.config, transactionObject);
                        const transactionRID = gtxTool.getDigestToSign(gtx);
                        try {
                            yield handlePostResponsePromisified(error, statusCode, rspBody);
                            if (typeof callback === "function") {
                                callback(null, {
                                    status: ResponseStatus.Waiting,
                                    statusCode,
                                    transactionRID,
                                });
                            }
                        }
                        catch (_error) {
                            if (typeof callback === "function") {
                                callback(_error, null);
                            }
                            return reject(_error);
                        }
                        promiEvent.emit("sent", {
                            status: ResponseStatus.Waiting,
                            statusCode: statusCode,
                            transactionRID,
                        });
                        const waitConfirmation = (txRID) => {
                            return new Promise((resolve, reject) => {
                                this.getTransactionStatus(txRID, (error, result) => {
                                    if (error) {
                                        reject(error);
                                    }
                                    else {
                                        const status = result.status;
                                        switch (status) {
                                            case ResponseStatus.Confirmed:
                                                resolve(ResponseStatus.Confirmed);
                                                break;
                                            case ResponseStatus.Rejected:
                                                reject(new TxRejectedError(result.rejectReason));
                                                break;
                                            case ResponseStatus.Unknown:
                                                reject(new LostMessageError());
                                                break;
                                            case ResponseStatus.Waiting:
                                                setTimeout(() => waitConfirmation(txRID).then(resolve, reject), this.config.pollingInterval);
                                                break;
                                            default:
                                                logger.error(status);
                                                reject(new UnexpectedResponseError());
                                        }
                                    }
                                });
                            });
                        };
                        const confirmationStatus = yield waitConfirmation(gtxTool.getDigestToSign(gtx));
                        resolve({
                            status: confirmationStatus,
                            statusCode: statusCode,
                            transactionRID,
                        });
                    }
                    catch (error) {
                        reject(error);
                    }
                }));
                return promiEvent;
            },
            signAndSendUniqueTransaction(transactionOrOperation, signMethod, callback) {
                const promiEvent = new Web3PromiEvent((resolve, reject) => {
                    const signMethodIsPrivKey = "privKey" in signMethod;
                    let _pubKey;
                    if (signMethodIsPrivKey) {
                        _pubKey = !signMethod.pubKey
                            ? makeKeyPair(signMethod.privKey).pubKey
                            : signMethod.pubKey;
                    }
                    const transaction = "name" in transactionOrOperation
                        ? {
                            operations: [transactionOrOperation],
                            signers: [signMethod.pubKey || _pubKey],
                        }
                        : transactionOrOperation;
                    const hasNop = transaction.operations.some((operation) => {
                        return operation.name === "nop";
                    });
                    const transactionWithNop = hasNop
                        ? transaction
                        : this.addNop(transaction);
                    this.signTransaction(transactionWithNop, signMethod)
                        .then((signedTx) => {
                        const sendTransactionPromiEvent = this.sendTransaction(signedTx, callback);
                        sendTransactionPromiEvent.on("sent", (receipt) => {
                            promiEvent.emit("sent", receipt);
                        });
                        resolve(sendTransactionPromiEvent);
                    })
                        .catch((error) => {
                        reject(error);
                    });
                });
                return promiEvent;
            },
            getTransaction(transactionRID, callback) {
                return __awaiter(this, void 0, void 0, function* () {
                    if (!validTxRID(transactionRID)) {
                        if (typeof callback === "function") {
                            callback(new UnvalidTxRidException(transactionRID), null);
                        }
                        throw new UnvalidTxRidException(transactionRID);
                    }
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.GET, "tx/" +
                        this.config.blockchainRID +
                        "/" +
                        transactionRID.toString("hex"), this.config);
                    return new Promise((resolve, reject) => {
                        handleGetResponse(error, statusCode, statusCode === 200 ? toBuffer(rspBody.tx) : rspBody, callbackPromiseBuilder(reject, resolve, callback));
                    });
                });
            },
            getTransactionStatus(transactionRID, callback) {
                return __awaiter(this, void 0, void 0, function* () {
                    if (!validTxRID(transactionRID)) {
                        const error = new UnvalidTxRidException(transactionRID);
                        if (typeof callback === "function") {
                            callback(error, null);
                        }
                        throw error;
                    }
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.GET, "tx/" +
                        this.config.blockchainRID +
                        "/" +
                        transactionRID.toString("hex") +
                        "/status", this.config);
                    return new Promise((resolve, reject) => {
                        handleGetResponse(error, statusCode, rspBody, callbackPromiseBuilder(reject, resolve, callback));
                    });
                });
            },
            addNop(transaction) {
                const _transaction = _.cloneDeep(transaction);
                const noOperation = {
                    name: "nop",
                    args: [randomBytes(32)],
                };
                _transaction.operations = [..._transaction.operations, noOperation];
                return _transaction;
            },
            getTransactionRID(transaction) {
                try {
                    const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRID);
                    return gtxTool.getDigestToSign(gtx);
                }
                catch (e) {
                    throw new getTransactionRIDException(e);
                }
            },
            encodeTransaction(transaction) {
                const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRID);
                return gtxTool.serialize(gtx);
            },
            decodeTransactionToGTX(encodedTransaction) {
                const gtx = gtxTool.deserialize(encodedTransaction);
                logger.debug(`Output from deserializing a raw transaction: ${JSON.stringify(gtx)}`);
                return gtx;
            },
            getClientNodeURLPool() {
                return this.config.endpointPool;
            },
            /**
             * Retrieves a confirmation proof for a transaction with the specified double-sha256
             * hash.
             * @param txRID A buffer of 32 bytes
             * @param callback parameters (error, responseObjectProof) if first
             * parameter is not null, an error occurred.
             * If first parameter is null, then the second parameter is an object
             * like the following:
             *
             * {hash: messageHashBuffer,
             *  blockHeader: blockHeaderBuffer,
             *  signatures: [{pubKey: pubKeyBuffer, signature: sigBuffer}, ...],
             *  merklePath: [{side: <0|1>, hash: <hash buffer level n-1>},
             *               ...
             *               {side: <0|1>, hash: <hash buffer level 1>}]}
             *
             * If no such transaction RID exists, the callback will be called with (null, null).
             *
             * The proof object can be validated using
             * postchain-common.util.validateMerklePath(proof.merklePath, proof.hash,
             * proof.blockHeader.slice(32, 64))
             *
             * The signatures must be validated agains some know trusted source for valid signers
             * at this specific block height.
             */
            getConfirmationProof: function (txRID, callback) {
                return __awaiter(this, void 0, void 0, function* () {
                    if (!validTxRID(txRID)) {
                        callback(new UnvalidTxRidException(txRID), null);
                        throw new UnvalidTxRidException(txRID);
                    }
                    else {
                        const { error, statusCode, rspBody } = yield requestWithRetry(Method.GET, "tx/" +
                            this.config.blockchainRID +
                            "/" +
                            txRID.toString("hex") +
                            "/confirmationProof", this.config);
                        const confirmationProof = {
                            hash: undefined,
                            blockHeader: undefined,
                            witness: undefined,
                            merkleProofTree: "",
                            txIndex: 0,
                        };
                        if (statusCode === 200) {
                            try {
                                const decodedProof = decodeValue(toBuffer(rspBody.proof));
                                confirmationProof.hash = decodedProof.hash;
                                confirmationProof.blockHeader = decodedProof.blockHeader;
                                confirmationProof.witness = decodedProof.witness;
                                confirmationProof.merkleProofTree = decodedProof.merkleProofTree;
                            }
                            catch (decodeError) {
                                if (callback) {
                                    callback(decodeError, null);
                                }
                                throw decodeError;
                            }
                        }
                        return new Promise((resolve, reject) => {
                            handleGetResponse(error, statusCode, confirmationProof, callbackPromiseBuilder(reject, resolve, callback));
                        });
                    }
                });
            },
        };
    });
}
//# sourceMappingURL=blockchainClient.js.map