"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addInstruction = exports.prepareStorage = exports.getAuthorization = exports.prepareAddInstruction = exports.createAddInstructionResolver = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = require("lodash");
const types_1 = require("../../polkadot/types");
const utils_1 = require("./utils");
const internal_1 = require("../../internal");
const types_2 = require("../../types");
const utils_2 = require("../../types/utils");
const constants_1 = require("../../utils/constants");
const conversion_1 = require("../../utils/conversion");
const internal_2 = require("../../utils/internal");
/**
 * @hidden
 */
const createAddInstructionResolver = (context, previousInstructions) => (receipt) => {
    const events = internal_2.filterEventRecords(receipt, 'settlement', 'InstructionCreated');
    const result = events.map(({ data }) => new internal_1.Instruction({ id: conversion_1.u64ToBigNumber(data[2]) }, context));
    if (previousInstructions) {
        return previousInstructions.value.concat(result);
    }
    return result;
};
exports.createAddInstructionResolver = createAddInstructionResolver;
/**
 * @hidden
 */
function getTxArgsAndErrors(instructions, portfoliosToAffirm, latestBlock, venueId, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const addAndAffirmInstructionParams = [];
        const addInstructionParams = [];
        const legEmptyErrIndexes = [];
        const legLengthErrIndexes = [];
        const endBlockErrIndexes = [];
        /**
         * array of indexes of Instructions where the value date is before the trade date
         */
        const datesErrIndexes = [];
        yield bluebird_1.default.each(instructions, ({ legs, endBlock, tradeDate, valueDate }, i) => __awaiter(this, void 0, void 0, function* () {
            if (!legs.length) {
                legEmptyErrIndexes.push(i);
            }
            if (legs.length > constants_1.MAX_LEGS_LENGTH) {
                legLengthErrIndexes.push(i);
            }
            let endCondition;
            if (endBlock) {
                if (endBlock.lte(latestBlock)) {
                    endBlockErrIndexes.push(i);
                }
                endCondition = { type: types_2.InstructionType.SettleOnBlock, value: endBlock };
            }
            else {
                endCondition = { type: types_2.InstructionType.SettleOnAffirmation };
            }
            if (tradeDate && valueDate && tradeDate > valueDate) {
                datesErrIndexes.push(i);
            }
            if (!legEmptyErrIndexes.length &&
                !legLengthErrIndexes.length &&
                !endBlockErrIndexes.length &&
                !datesErrIndexes.length) {
                const rawVenueId = conversion_1.bigNumberToU64(venueId, context);
                const rawSettlementType = conversion_1.endConditionToSettlementType(endCondition, context);
                const rawTradeDate = internal_2.optionize(conversion_1.dateToMoment)(tradeDate, context);
                const rawValueDate = internal_2.optionize(conversion_1.dateToMoment)(valueDate, context);
                const rawLegs = [];
                yield Promise.all(legs.map(({ from, to, amount, asset }) => __awaiter(this, void 0, void 0, function* () {
                    const fromId = conversion_1.portfolioLikeToPortfolioId(from);
                    const toId = conversion_1.portfolioLikeToPortfolioId(to);
                    yield Promise.all([
                        utils_1.assertPortfolioExists(fromId, context),
                        utils_1.assertPortfolioExists(toId, context),
                    ]);
                    const rawFromPortfolio = conversion_1.portfolioIdToMeshPortfolioId(fromId, context);
                    const rawToPortfolio = conversion_1.portfolioIdToMeshPortfolioId(toId, context);
                    rawLegs.push({
                        from: rawFromPortfolio,
                        to: rawToPortfolio,
                        asset: conversion_1.stringToTicker(internal_2.getTicker(asset), context),
                        amount: conversion_1.bigNumberToBalance(amount, context),
                    });
                })));
                if (portfoliosToAffirm[i].length) {
                    addAndAffirmInstructionParams.push([
                        rawVenueId,
                        rawSettlementType,
                        rawTradeDate,
                        rawValueDate,
                        rawLegs,
                        portfoliosToAffirm[i].map(portfolio => conversion_1.portfolioIdToMeshPortfolioId(conversion_1.portfolioLikeToPortfolioId(portfolio), context)),
                    ]);
                }
                else {
                    addInstructionParams.push([
                        rawVenueId,
                        rawSettlementType,
                        rawTradeDate,
                        rawValueDate,
                        rawLegs,
                    ]);
                }
            }
        }));
        return {
            errIndexes: {
                legEmptyErrIndexes,
                legLengthErrIndexes,
                endBlockErrIndexes,
                datesErrIndexes,
            },
            addAndAffirmInstructionParams,
            addInstructionParams,
        };
    });
}
/**
 * @hidden
 */
function prepareAddInstruction(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context: { polymeshApi: { tx: { settlement }, }, }, context, storage: { portfoliosToAffirm }, } = this;
        const { instructions, venueId } = args;
        const [latestBlock] = yield Promise.all([
            context.getLatestBlock(),
            utils_1.assertVenueExists(venueId, context),
        ]);
        if (!instructions.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.ValidationError,
                message: 'The Instructions array cannot be empty',
            });
        }
        const { errIndexes: { legEmptyErrIndexes, legLengthErrIndexes, endBlockErrIndexes, datesErrIndexes }, addAndAffirmInstructionParams, addInstructionParams, } = yield getTxArgsAndErrors(instructions, portfoliosToAffirm, latestBlock, venueId, context);
        if (legEmptyErrIndexes.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.ValidationError,
                message: "The legs array can't be empty",
                data: {
                    failedInstructionIndexes: legEmptyErrIndexes,
                },
            });
        }
        if (legLengthErrIndexes.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.LimitExceeded,
                message: 'The legs array exceeds the maximum allowed length',
                data: {
                    maxLength: constants_1.MAX_LEGS_LENGTH,
                    failedInstructionIndexes: legLengthErrIndexes,
                },
            });
        }
        if (endBlockErrIndexes.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.ValidationError,
                message: 'End block must be a future block',
                data: {
                    failedInstructionIndexes: endBlockErrIndexes,
                },
            });
        }
        if (datesErrIndexes.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.ValidationError,
                message: 'Value date must be after trade date',
                data: {
                    failedInstructionIndexes: datesErrIndexes,
                },
            });
        }
        const addAndAffirmTx = settlement.addAndAffirmInstruction;
        const addTx = settlement.addInstruction;
        const transactions = internal_2.assembleBatchTransactions(utils_2.tuple({
            transaction: addTx,
            argsArray: addInstructionParams,
        }, {
            transaction: addAndAffirmTx,
            argsArray: addAndAffirmInstructionParams,
        }));
        const [resultInstructions] = this.addBatchTransaction({
            transactions,
            resolvers: [exports.createAddInstructionResolver(context)],
        });
        return resultInstructions;
    });
}
exports.prepareAddInstruction = prepareAddInstruction;
/**
 * @hidden
 */
function getAuthorization({ venueId }) {
    return __awaiter(this, void 0, void 0, function* () {
        const { storage: { portfoliosToAffirm }, } = this;
        let transactions = [];
        let portfolios = [];
        portfoliosToAffirm.forEach(portfoliosList => {
            transactions = lodash_1.union(transactions, [
                portfoliosList.length
                    ? types_1.TxTags.settlement.AddAndAffirmInstruction
                    : types_1.TxTags.settlement.AddInstruction,
            ]);
            portfolios = lodash_1.unionWith(portfolios, portfoliosList, lodash_1.isEqual);
        });
        return {
            roles: [{ type: types_2.RoleType.VenueOwner, venueId }],
            permissions: {
                assets: [],
                portfolios,
                transactions,
            },
        };
    });
}
exports.getAuthorization = getAuthorization;
/**
 * @hidden
 */
function prepareStorage({ instructions }) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context } = this;
        const identity = yield context.getSigningIdentity();
        const portfoliosToAffirm = yield bluebird_1.default.map(instructions, ({ legs }) => __awaiter(this, void 0, void 0, function* () {
            const portfolios = yield bluebird_1.default.map(legs, ({ from, to }) => __awaiter(this, void 0, void 0, function* () {
                const fromPortfolio = conversion_1.portfolioLikeToPortfolio(from, context);
                const toPortfolio = conversion_1.portfolioLikeToPortfolio(to, context);
                const result = [];
                const [fromCustodied, toCustodied] = yield Promise.all([
                    fromPortfolio.isCustodiedBy({ identity }),
                    toPortfolio.isCustodiedBy({ identity }),
                ]);
                if (fromCustodied) {
                    result.push(fromPortfolio);
                }
                if (toCustodied) {
                    result.push(toPortfolio);
                }
                return result;
            }));
            return lodash_1.flatten(portfolios);
        }));
        return {
            portfoliosToAffirm,
        };
    });
}
exports.prepareStorage = prepareStorage;
/**
 * @hidden
 */
const addInstruction = () => new internal_1.Procedure(prepareAddInstruction, getAuthorization, prepareStorage);
exports.addInstruction = addInstruction;
//# sourceMappingURL=addInstruction.js.map