"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.Asset = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const internal_1 = require("../../../internal");
const queries_1 = require("../../../middleware/queries");
const types_1 = require("../../../middleware/types");
const types_2 = require("../../../types");
const internal_2 = require("../../../types/internal");
const constants_1 = require("../../../utils/constants");
const conversion_1 = require("../../../utils/conversion");
const internal_3 = require("../../../utils/internal");
const AssetHolders_1 = require("./AssetHolders");
const Checkpoints_1 = require("./Checkpoints");
const Compliance_1 = require("./Compliance");
const CorporateActions_1 = require("./CorporateActions");
const Documents_1 = require("./Documents");
const Issuance_1 = require("./Issuance");
const Offerings_1 = require("./Offerings");
const Permissions_1 = require("./Permissions");
const Settlements_1 = require("./Settlements");
const TransferRestrictions_1 = require("./TransferRestrictions");
/**
 * Class used to manage all Asset functionality
 */
class Asset extends internal_1.Entity {
    /**
     * @hidden
     */
    constructor(identifiers, context) {
        super(identifiers, context);
        const { ticker } = identifiers;
        this.ticker = ticker;
        this.did = (0, conversion_1.tickerToDid)(ticker);
        this.documents = new Documents_1.Documents(this, context);
        this.settlements = new Settlements_1.Settlements(this, context);
        this.assetHolders = new AssetHolders_1.AssetHolders(this, context);
        this.issuance = new Issuance_1.Issuance(this, context);
        this.compliance = new Compliance_1.Compliance(this, context);
        this.transferRestrictions = new TransferRestrictions_1.TransferRestrictions(this, context);
        this.offerings = new Offerings_1.Offerings(this, context);
        this.checkpoints = new Checkpoints_1.Checkpoints(this, context);
        this.corporateActions = new CorporateActions_1.CorporateActions(this, context);
        this.permissions = new Permissions_1.Permissions(this, context);
        this.transferOwnership = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.transferAssetOwnership, Object.assign({ ticker }, args)] }, context);
        this.modify = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.modifyAsset, Object.assign({ ticker }, args)] }, context);
        this.freeze = (0, internal_3.createProcedureMethod)({
            getProcedureAndArgs: () => [internal_1.toggleFreezeTransfers, { ticker, freeze: true }],
            voidArgs: true,
        }, context);
        this.unfreeze = (0, internal_3.createProcedureMethod)({
            getProcedureAndArgs: () => [internal_1.toggleFreezeTransfers, { ticker, freeze: false }],
            voidArgs: true,
        }, context);
        this.modifyPrimaryIssuanceAgent = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.modifyPrimaryIssuanceAgent, Object.assign({ ticker }, args)] }, context);
        this.removePrimaryIssuanceAgent = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: () => [internal_1.removePrimaryIssuanceAgent, { ticker }], voidArgs: true }, context);
        this.redeem = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.redeemTokens, Object.assign({ ticker }, args)] }, context);
        this.controllerTransfer = (0, internal_3.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.controllerTransfer, Object.assign({ ticker }, args)] }, context);
    }
    /**
     * @hidden
     * Check if a value is of type {@link UniqueIdentifiers}
     */
    static isUniqueIdentifiers(identifier) {
        const { ticker } = identifier;
        return typeof ticker === 'string';
    }
    // eslint-disable-next-line require-jsdoc
    details(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { asset, externalAgents }, }, }, ticker, context, } = this;
            const assembleResult = ({ totalSupply, divisible, ownerDid, assetType: rawAssetType }, agentGroups, assetName, iuDisabled) => __awaiter(this, void 0, void 0, function* () {
                const primaryIssuanceAgents = [];
                const fullAgents = [];
                agentGroups.forEach(([storageKey, agentGroup]) => {
                    const rawAgentGroup = agentGroup.unwrap();
                    if (rawAgentGroup.isPolymeshV1PIA) {
                        primaryIssuanceAgents.push(new internal_1.Identity({ did: (0, conversion_1.identityIdToString)(storageKey.args[1]) }, context));
                    }
                    else if (rawAgentGroup.isFull) {
                        fullAgents.push(new internal_1.Identity({ did: (0, conversion_1.identityIdToString)(storageKey.args[1]) }, context));
                    }
                });
                const owner = new internal_1.Identity({ did: (0, conversion_1.identityIdToString)(ownerDid) }, context);
                const type = (0, conversion_1.assetTypeToKnownOrId)(rawAssetType);
                let assetType;
                if (typeof type === 'string') {
                    assetType = type;
                }
                else {
                    const customType = yield asset.customTypes((0, conversion_1.bigNumberToU32)(type, context));
                    assetType = (0, conversion_1.bytesToString)(customType);
                }
                return {
                    assetType,
                    isDivisible: (0, conversion_1.boolToBoolean)(divisible),
                    name: (0, conversion_1.bytesToString)(assetName),
                    owner,
                    totalSupply: (0, conversion_1.balanceToBigNumber)(totalSupply),
                    primaryIssuanceAgents,
                    fullAgents,
                    requiresInvestorUniqueness: !(0, conversion_1.boolToBoolean)(iuDisabled),
                };
            });
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            const groupOfAgentPromise = externalAgents.groupOfAgent.entries(rawTicker);
            const namePromise = asset.assetNames(rawTicker);
            const disabledIuPromise = asset.disableInvestorUniqueness(rawTicker);
            if (callback) {
                const groupEntries = yield groupOfAgentPromise;
                const assetName = yield namePromise;
                const disabledInvestorUniqueness = yield disabledIuPromise;
                return asset.tokens(rawTicker, (securityToken) => __awaiter(this, void 0, void 0, function* () {
                    const result = yield assembleResult(securityToken, groupEntries, assetName, disabledInvestorUniqueness);
                    // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback errors should be handled by the caller
                    callback(result);
                }));
            }
            const [token, groups, name, disabledIu] = yield Promise.all([
                asset.tokens(rawTicker),
                groupOfAgentPromise,
                namePromise,
                disabledIuPromise,
            ]);
            return assembleResult(token, groups, name, disabledIu);
        });
    }
    // eslint-disable-next-line require-jsdoc
    currentFundingRound(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { asset }, }, }, ticker, context, } = this;
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            const assembleResult = (roundName) => (0, conversion_1.bytesToString)(roundName) || null;
            if (callback) {
                return asset.fundingRound(rawTicker, round => {
                    // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback errors should be handled by the caller
                    callback(assembleResult(round));
                });
            }
            const fundingRound = yield asset.fundingRound(rawTicker);
            return assembleResult(fundingRound);
        });
    }
    // eslint-disable-next-line require-jsdoc
    getIdentifiers(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { asset }, }, }, ticker, context, } = this;
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            if (callback) {
                return asset.identifiers(rawTicker, identifiers => {
                    // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback errors should be handled by the caller
                    callback(identifiers.map(conversion_1.assetIdentifierToSecurityIdentifier));
                });
            }
            const assetIdentifiers = yield asset.identifiers(rawTicker);
            return assetIdentifiers.map(conversion_1.assetIdentifierToSecurityIdentifier);
        });
    }
    /**
     * Retrieve the identifier data (block number, date and event index) of the event that was emitted when the token was created
     *
     * @note uses the middleware
     * @note there is a possibility that the data is not ready by the time it is requested. In that case, `null` is returned
     */
    createdAt() {
        return __awaiter(this, void 0, void 0, function* () {
            const { ticker, context } = this;
            const { data: { eventByIndexedArgs: event }, } = yield context.queryMiddleware((0, queries_1.eventByIndexedArgs)({
                moduleId: types_1.ModuleIdEnum.Asset,
                eventId: types_1.EventIdEnum.AssetCreated,
                eventArg1: (0, internal_3.padString)(ticker, constants_1.MAX_TICKER_LENGTH),
            }));
            return (0, internal_3.optionize)(conversion_1.middlewareEventToEventIdentifier)(event);
        });
    }
    // eslint-disable-next-line require-jsdoc
    isFrozen(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { ticker, context: { polymeshApi: { query: { asset }, }, }, context, } = this;
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            if (callback) {
                return asset.frozen(rawTicker, frozen => {
                    // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback errors should be handled by the caller
                    callback((0, conversion_1.boolToBoolean)(frozen));
                });
            }
            const result = yield asset.frozen(rawTicker);
            return (0, conversion_1.boolToBoolean)(result);
        });
    }
    // eslint-disable-next-line require-jsdoc
    investorCount(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { statistics }, }, }, context, ticker, } = this;
            const tickerKey = (0, conversion_1.stringToTickerKey)(ticker, context);
            const activeStats = yield statistics.activeAssetStats(tickerKey);
            const activeCountStatExists = !!activeStats.find(s => {
                return (0, conversion_1.meshStatToStatisticsOpType)(s) === internal_2.StatisticsOpType.Count;
            });
            if (!activeCountStatExists) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.DataUnavailable,
                    message: 'The Issuer of the Asset must first enable the statistics for the Asset before this method can be used',
                });
            }
            const secondKey = (0, conversion_1.createStat2ndKey)(context);
            if (callback) {
                return statistics.assetStats({ asset: tickerKey }, secondKey, count => {
                    // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback errors should be handled by the caller
                    callback((0, conversion_1.u128ToBigNumber)(count));
                });
            }
            const result = yield statistics.assetStats({ asset: tickerKey }, secondKey);
            return (0, conversion_1.u128ToBigNumber)(result);
        });
    }
    /**
     * Retrieve this Asset's Operation History
     *
     * @note Operations are grouped by the agent Identity who performed them
     *
     * @note uses the middleware
     */
    getOperationHistory() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { system }, }, }, context, ticker, } = this;
            const { data: { tickerExternalAgentHistory: tickerExternalAgentHistoryResult }, } = yield context.queryMiddleware((0, queries_1.tickerExternalAgentHistory)({
                ticker,
            }));
            const multiParams = [];
            const results = [];
            tickerExternalAgentHistoryResult.forEach(({ did, history }) => {
                const historyResult = [];
                history.forEach(({ block_id: blockId, datetime, event_idx: eventIndex }) => {
                    const blockNumber = new bignumber_js_1.default(blockId);
                    multiParams.push((0, conversion_1.bigNumberToU32)(blockNumber, context));
                    historyResult.push({
                        blockNumber,
                        blockDate: new Date(datetime),
                        eventIndex: new bignumber_js_1.default(eventIndex),
                    });
                });
                results.push({
                    identity: new internal_1.Identity({ did }, context),
                    history: historyResult,
                });
            });
            let hashes = [];
            if (multiParams.length) {
                hashes = yield system.blockHash.multi(multiParams);
            }
            const finalResults = [];
            results.forEach(({ identity, history }) => {
                const historyWithHashes = [];
                history.forEach(event => {
                    /*
                     * Since we filled the params array in the order in which the events appeared and this is being done
                     *   synchronously, the order and amount of results should be the same and the array should never be empty
                     *   until all the data is in place
                     */
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const blockHash = (0, conversion_1.hashToString)(hashes.shift());
                    historyWithHashes.push(Object.assign(Object.assign({}, event), { blockHash }));
                });
                finalResults.push({
                    identity,
                    history: historyWithHashes,
                });
            });
            return finalResults;
        });
    }
    /**
     * Determine whether this Asset exists on chain
     */
    exists() {
        return __awaiter(this, void 0, void 0, function* () {
            const { ticker, context } = this;
            const tokenSize = yield context.polymeshApi.query.asset.tokens.size((0, conversion_1.stringToTicker)(ticker, context));
            return !tokenSize.isZero();
        });
    }
    /**
     * Return the Asset's ticker
     */
    toHuman() {
        return this.ticker;
    }
}
exports.Asset = Asset;
//# sourceMappingURL=index.js.map