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 * as gtv from "../gtv";
import { checkDigestSignature, createPublicKey, makeKeyPair, signDigest, } from "../encryption/encryption";
import { AlreadySignedTransactionException, MissingGtxException, MissingSignerException, } from "./errors";
import { encode as gtxEncode, decode as gtxDecode } from "./index";
import { ensureBuffer } from "../formatter";
export function emptyGtx(blockchainRid) {
    return { blockchainRid, operations: [], signers: [] };
}
/**
 * Adds a function call to a GTX. Creates a new GTX if none specified.
 * This function will throw Error if gtx is already signed
 * @param opName the name of the function to call
 * @param args the array of arguments of the function call. If no args, this must be an empty array
 * @param gtx the function call will be added to this gtx
 * @returns the gtx
 * @throws if gtx is null or if gtx is already signed
 */
export function addTransactionToGtx(opName, args, gtx) {
    if (gtx == null) {
        throw new MissingGtxException();
    }
    if (gtx.signatures) {
        throw new AlreadySignedTransactionException("function");
    }
    gtx.operations.push({ opName: opName, args: args });
    return gtx;
}
export function addSignerToGtx(signer, gtx) {
    if (gtx.signatures) {
        throw new AlreadySignedTransactionException("signer");
    }
    gtx.signers.push(signer);
}
/**
 * Serializes the gtx to get tx hash
 * @param gtx the gtx to serialize
 */
export function getDigest(gtx) {
    return gtv.gtvHash(gtxToRawGtx(gtx));
}
/**
 * Serializes the gtx for signing
 * @param gtx the gtx to serialize
 */
export function getDigestToSign(gtx) {
    return gtv.gtvHash(gtvTxBody(gtx));
}
export function getDigestToSignFromBody(gtxBody) {
    return gtv.gtvHash(gtxBody);
}
export function gtvTxBody(gtx) {
    return [
        gtx.blockchainRid,
        gtx.operations.map((op) => [op.opName, op.args]),
        gtx.signers,
    ];
}
export function gtxToRawGtx(gtx) {
    return [
        [
            gtx.blockchainRid,
            gtx.operations.map((op) => [op.opName, op.args]),
            gtx.signers,
        ],
        gtx.signatures,
    ];
}
export function sign(gtx, privOrSigProv, pubKey) {
    return __awaiter(this, void 0, void 0, function* () {
        if (privOrSigProv instanceof Buffer) {
            const digestToSign = getDigestToSign(gtx);
            const signature = signDigest(digestToSign, privOrSigProv);
            return addSignature(pubKey || makeKeyPair(privOrSigProv).pubKey, signature, gtx);
        }
        else {
            const digestToSign = getDigestToSign(gtx);
            const signature = yield privOrSigProv.sign(digestToSign);
            return addSignature(privOrSigProv.pubKey, signature, gtx);
        }
    });
}
export function signRawTransaction(_keyPair, _rawTransaction) {
    throw Error("TODO");
    //TODO
    //const gtx = module.exports.deserialize(rawTransaction);
    //module.exports.sign(keyPair.privKey, keyPair.pubKey, gtx);
    // return module.exports.serialize(gtx)
}
/**
 * Adds a signature to the gtx
 */
export function addSignature(pubKeyBuffer, signatureBuffer, gtx) {
    if (!gtx.signatures) {
        gtx.signatures = Array(gtx.signers.length);
    }
    const signerIndex = gtx.signers.findIndex((signer) => pubKeyBuffer.equals(signer));
    if (signerIndex === -1) {
        throw new MissingSignerException();
    }
    gtx.signatures[signerIndex] = signatureBuffer;
    return gtx;
}
export function serialize(gtx) {
    if (!gtx.signatures) {
        // TODO
        // The gtx is not signed, but we must include
        // the signatures attribute, so let's add that.
        gtx.signatures = [];
    }
    return gtxEncode([gtvTxBody(gtx), gtx.signatures]);
}
export function deserialize(gtxBytes) {
    const deserializedTx = gtxDecode(gtxBytes);
    const body = deserializedTx[0];
    const gtvTxBody = {
        blockchainRid: body[0],
        operations: body[1].map((operation) => ({
            opName: operation[0],
            args: operation[1],
        })),
        signers: body[2],
    };
    const signatures = deserializedTx[1];
    return {
        blockchainRid: gtvTxBody.blockchainRid,
        operations: gtvTxBody.operations,
        signers: gtvTxBody.signers,
        signatures,
    };
}
export function checkGTXSignatures(txHash, gtx) {
    for (const i in gtx.signers) {
        const signValid = checkDigestSignature(txHash, gtx.signers[i], gtx.signatures[i]);
        if (!signValid)
            return signValid;
    }
    return true;
}
export function checkExistingGTXSignatures(txHash, gtx) {
    for (const i in gtx.signers) {
        if (gtx.signatures[i]) {
            const signValid = checkDigestSignature(txHash, gtx.signers[i], gtx.signatures[i]);
            if (!signValid)
                return signValid;
        }
    }
    return true;
}
export function newSignatureProvider(keyPair) {
    let pub, priv;
    if (keyPair) {
        priv = ensureBuffer(keyPair.privKey);
        pub = keyPair.pubKey ? ensureBuffer(keyPair.pubKey) : createPublicKey(priv);
    }
    else {
        ({ privKey: priv, pubKey: pub } = makeKeyPair());
    }
    return {
        pubKey: pub,
        sign: (digest) => __awaiter(this, void 0, void 0, function* () { return signDigest(digest, priv); }),
    };
}
export function rawGtvToGtx(gtv) {
    if (Array.isArray(gtv) && gtv.length !== 2) {
        throw new Error("Gtv must be an array of size 2");
    }
    const gtvArray = gtv;
    if (!Array.isArray(gtvArray[0])) {
        throw new Error("First element must be an array");
    }
    if (!Array.isArray(gtvArray[1])) {
        throw new Error("Second element must be an array");
    }
    gtvArray[1].forEach((element) => {
        if (!Buffer.isBuffer(element)) {
            throw new Error("Element must be a buffer");
        }
    });
    const gtxBody = rawGtvToGtxBody(gtvArray[0]);
    return Object.assign(Object.assign({}, gtxBody), { signatures: gtvArray[1] });
}
function rawGtvToGtxBody(gtv) {
    if (Array.isArray(gtv) && gtv.length !== 3) {
        throw new Error("Gtv must be an array of size 3");
    }
    const array = gtv;
    if (!Buffer.isBuffer(array[0])) {
        throw new Error("First element must be a byte array");
    }
    if (!Array.isArray(array[1])) {
        throw new Error("Second element must be an array");
    }
    if (!Array.isArray(array[2])) {
        throw new Error("Third element must be an array");
    }
    array[2].forEach((element) => {
        if (!Buffer.isBuffer(element)) {
            throw new Error("Element must be a buffer");
        }
    });
    return {
        blockchainRid: array[0],
        operations: array[1].map((element) => rawGtvToRellOp(element)),
        signers: array[2],
    };
}
function rawGtvToRellOp(gtv) {
    if (Array.isArray(gtv) && gtv.length !== 2) {
        throw new Error("Gtv must be an array of size 2");
    }
    const array = gtv;
    if (typeof array[0] !== "string") {
        throw new Error("First element must be a string");
    }
    if (!Array.isArray(array[1])) {
        throw new Error("Second element must be an array");
    }
    return { opName: array[0], args: array[1] };
}
//# sourceMappingURL=gtx.js.map