"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());
    });
};
import { Buffer } from "buffer";
import { ResponseStatus, Method, } from "./types";
import * as logger from "../logger";
import { EmptyListOfUrlsException, LostMessageError, SerializedTransactionFormatException, TxRejectedError, UnexpectedResponseError, UnexpectedStatusError, UnvalidTxRidException, } from "./errors";
import { toBuffer, toQueryObjectGTV } from "../formatter";
import { convertToPrintable, requestWithRetry } from "./restclientutil";
import { encodeValue } from "../gtx/serialization";
export function createRestClient(endpointPool, blockchainRID, maxSockets = 10, pollingInterval = 500, failOverConfig) {
    validateInput(endpointPool, failOverConfig);
    return {
        config: {
            endpointPool: endpointPool,
            pool: { maxSockets },
            pollingInterval,
            attemptsPerEndpoint: (failOverConfig === null || failOverConfig === void 0 ? void 0 : failOverConfig.attemptsPerEndpoint) || 3,
            attemptInterval: (failOverConfig === null || failOverConfig === void 0 ? void 0 : failOverConfig.attemptInterval) || 500,
        },
        getTransaction: function (txRID, callback) {
            return __awaiter(this, void 0, void 0, function* () {
                if (!validTxRID(txRID)) {
                    callback(new UnvalidTxRidException(txRID), null);
                }
                else {
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.GET, "tx/" + blockchainRID + "/" + txRID.toString("hex"), this.config);
                    handleGetResponse(error, statusCode, statusCode === 200 ? toBuffer(rspBody.tx) : rspBody, callback);
                }
            });
        },
        postTransaction: function (serializedTransaction, callback) {
            return __awaiter(this, void 0, void 0, function* () {
                if (!Buffer.isBuffer(serializedTransaction)) {
                    throw new SerializedTransactionFormatException();
                }
                const transactionObject = {
                    tx: serializedTransaction.toString("hex"),
                };
                const { error, statusCode, rspBody } = yield requestWithRetry(Method.POST, "tx/" + blockchainRID, this.config, transactionObject);
                handlePostResponse(error, statusCode, rspBody, callback);
            });
        },
        status: function (txRID, callback) {
            return __awaiter(this, void 0, void 0, function* () {
                if (!validTxRID(txRID)) {
                    callback(new UnvalidTxRidException(txRID), null);
                }
                else {
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.GET, "tx/" + blockchainRID + "/" + txRID.toString("hex") + "/status", this.config);
                    handleGetResponse(error, statusCode, rspBody, callback);
                }
            });
        },
        query: function (nameOrQueryObject, queryArguments) {
            return __awaiter(this, void 0, void 0, function* () {
                // eslint-disable-next-line no-async-promise-executor
                return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                    const callback = (error, result) => {
                        if (error) {
                            reject(error);
                        }
                        else {
                            resolve(result);
                        }
                    };
                    const { error, statusCode, rspBody } = yield requestWithRetry(Method.POST, `query_gtv/${blockchainRID}`, this.config, encodeValue(toQueryObjectGTV(nameOrQueryObject, queryArguments)));
                    handlePostResponse(error, statusCode, rspBody, callback);
                }));
            });
        },
        waitConfirmation(txRID) {
            return new Promise((resolve, reject) => {
                this.status(txRID, (error, result) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        const status = result.status;
                        switch (status) {
                            case ResponseStatus.Confirmed:
                                resolve(null);
                                break;
                            case ResponseStatus.Rejected:
                                reject(new TxRejectedError(result.rejectReason));
                                break;
                            case ResponseStatus.Unknown:
                                reject(new LostMessageError());
                                break;
                            case ResponseStatus.Waiting:
                                setTimeout(() => this.waitConfirmation(txRID).then(resolve, reject), this.config.pollingInterval);
                                break;
                            default:
                                logger.error(status);
                                reject(new UnexpectedResponseError());
                        }
                    }
                });
            });
        },
        postAndWaitConfirmation(serializedTransaction, txRID, validate) {
            if (validate === true) {
                return Promise.reject("Automatic validation is not yet implemented");
            }
            return new Promise((resolve, reject) => {
                this.postTransaction(serializedTransaction, (error) => {
                    if (error)
                        reject(error);
                    else {
                        setTimeout(() => this.waitConfirmation(txRID).then(resolve, reject), 1011);
                    }
                });
            });
        },
        getEndpointPool() {
            return this.config.endpointPool;
        },
    };
}
/**
 * Validates that txRID is a Buffer of 32 bytes.
 * @param txRID A buffer of 32 bytes
 */
function validTxRID(txRID) {
    if (txRID.length != 32) {
        const error = new UnvalidTxRidException(txRID);
        logger.error(error.toString());
        return false;
    }
    return true;
}
function validateInput(endpointPool, failOverConfig) {
    if (!endpointPool.length) {
        throw new EmptyListOfUrlsException();
    }
    if ((failOverConfig === null || failOverConfig === void 0 ? void 0 : failOverConfig.attemptsPerEndpoint) < 1) {
        logger.debug("Attempts can not be 0 or below, setting it to 1");
        failOverConfig.attemptsPerEndpoint = 1;
    }
}
/**
 * @param error response error
 * @param statusCode response status code
 * @param responseObject the responsebody from the server
 * @param callback the callback function to propagate the error and response back to the caller
 */
export function handleGetResponse(error, statusCode, responseObject, callback) {
    try {
        const responseObjectPrintable = convertToPrintable(responseObject);
        logger.debug(`error: ${error}, status code: ${statusCode}, response body: ${responseObjectPrintable}`);
        if (error) {
            callback(error, null);
        }
        else if (statusCode === 404) {
            //TODO remove
            logger.error("404 received");
            callback(null, null);
        }
        else if (statusCode !== 200) {
            callback(new UnexpectedStatusError(statusCode, responseObjectPrintable), responseObject);
        }
        else {
            callback(null, responseObject);
        }
    }
    catch (error) {
        logger.error("restclient.handleGetResponse(): Failed to call the callback function. " +
            error);
    }
}
/**
 * @param error response error
 * @param statusCode response status code
 * @param responseObject the responsebody from the server
 * @param callback the callback function to propagate the error and response back to the caller
 */
export function handlePostResponse(error, statusCode, responseObject, callback) {
    const responseObjectPrintable = convertToPrintable(responseObject);
    logger.debug(`error: ${error}, status code: ${statusCode}, response body: ${responseObjectPrintable}`);
    try {
        if (error) {
            logger.error(`In restclient post(). ${error}`);
            callback(error);
        }
        else if (statusCode != 200) {
            let errorMessage = `Unexpected status code from server. Code: ${statusCode}.`;
            if (responseObjectPrintable) {
                errorMessage += ` Message: ${responseObjectPrintable}.`;
            }
            logger.error(errorMessage);
            callback(new UnexpectedStatusError(statusCode, responseObjectPrintable), responseObject);
        }
        else {
            logger.info(`Calling responseCallback with responseObject: ${responseObjectPrintable}`);
            callback(null, responseObject);
        }
    }
    catch (error) {
        logger.error("restclient.handlePostResponse(): Failed to call callback function " +
            error);
    }
}
//# sourceMappingURL=restclient.js.map