"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
/* PROMISE_DEF */
const BluebirdPromise = require("bluebird");
/* END_PROMISE_DEF */
const steem = require("steem");
const _ = require("lodash");
const SteemOperationNumber_1 = require("../../blockchain/SteemOperationNumber");
const Chainable_1 = require("../../chainable/Chainable");
const Api_1 = require("../Api");
const V1Handler_1 = require("../../protocol/versions/v1/V1Handler");
const SteemJsAccountHistorySupplier_1 = require("./SteemJsAccountHistorySupplier");
const WiseOperationTypeFilter_1 = require("../../chainable/filters/WiseOperationTypeFilter");
const OperationNumberFilter_1 = require("../../chainable/filters/OperationNumberFilter");
const ToWiseOperationTransformer_1 = require("../../chainable/transformers/ToWiseOperationTransformer");
const ChainableLimiter_1 = require("../../chainable/limiters/ChainableLimiter");
const VoterFilter_1 = require("./VoterFilter");
const NotFoundException_1 = require("../../util/NotFoundException");
const DateLimiter_1 = require("./DateLimiter");
const log_1 = require("../../log/log");
class DirectBlockchainApi extends Api_1.Api {
    constructor(protocol, postingWif, steemOptions) {
        super();
        this.sendEnabled = true;
        this.protocol = protocol;
        if (postingWif)
            this.postingWif = postingWif;
        if (steemOptions) {
            this.steem = new steem.api.Steem(steemOptions);
        }
        else
            this.steem = new steem.api.Steem({
                url: DirectBlockchainApi.DEFAULT_STEEM_API_ENDPOINT_URL,
                logger: (...args) => {
                    log_1.Log.log().efficient(log_1.Log.level.silly, () => JSON.stringify(args));
                }
            });
    }
    name() {
        return "DirectBlockchainApi";
    }
    setSendEnabled(enabled) {
        log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_SET_SEND_ENABLED=" + enabled);
        this.sendEnabled = enabled;
    }
    loadPost(author, permlink) {
        return __awaiter(this, void 0, void 0, function* () {
            log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_API_LOAD_POST=" + JSON.stringify({ author: author, permlink: permlink }));
            const result = yield this.steem.getContentAsync(author, permlink);
            if (result.author.length === 0)
                throw new NotFoundException_1.NotFoundException("The post (@" + author + "/" + permlink + ") does not exist");
            return result;
        });
    }
    loadRulesets(forWhom, atMoment) {
        return __awaiter(this, void 0, void 0, function* () {
            log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_API_LOAD_RULESETS="
                + JSON.stringify({ forWhom: forWhom, atMoment: atMoment }));
            if (!forWhom.delegator)
                throw new Error("Direct blockchain api can only load rulesets for one voter."
                    + " Use other api to load rulesets from many delegators");
            const loadedRulesets = [];
            const supplier = new SteemJsAccountHistorySupplier_1.SteemJsAccountHistorySupplier(this.steem, forWhom.delegator)
                .branch((historySupplier) => {
                let chain = historySupplier
                    .chain(new OperationNumberFilter_1.OperationNumberFilter("<_solveOpInTrxBug", atMoment))
                    // this is limiter (restricts lookup to the period of wise presence):
                    .chain(new OperationNumberFilter_1.OperationNumberFilter(">", V1Handler_1.V1Handler.INTRODUCTION_OF_WISE_MOMENT).makeLimiter())
                    .chain(new ToWiseOperationTransformer_1.ToWiseOperationTransformer(this.protocol));
                if (forWhom.voter)
                    chain = chain.chain(new VoterFilter_1.VoterFilter(forWhom.voter));
                chain = chain.chain(new WiseOperationTypeFilter_1.WiseOperationTypeFilter(WiseOperationTypeFilter_1.WiseOperationTypeFilter.OperationType.SetRules));
                if (forWhom.voter)
                    chain = chain.chain(new ChainableLimiter_1.ChainableLimiter(1));
                chain.chain(new Chainable_1.SimpleTaker((item) => {
                    const setRules = item.command;
                    const esr = {
                        rulesets: setRules.rulesets,
                        voter: item.voter,
                        delegator: item.delegator,
                        moment: item.moment
                    };
                    loadedRulesets.push(esr);
                    return true;
                }))
                    .catch((error) => false); // if we return false on error, the Promise will be rejected
            });
            yield supplier.start();
            const allRulesGrouppedByVoter = _.groupBy(loadedRulesets, "voter");
            const out = [];
            _.forOwn(allRulesGrouppedByVoter, (esr, voter) => {
                out.push(esr.sort((a, b) => SteemOperationNumber_1.SteemOperationNumber.compare(a.moment, b.moment)).reverse()[0]);
            });
            return out;
        });
    }
    sendToBlockchain(operations) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.sendEnabled) {
                log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_API_SEND_TO_BLOCKCHAIN_DISABLED=" + JSON.stringify(operations));
                return SteemOperationNumber_1.SteemOperationNumber.NEVER;
            }
            log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_API_SEND_TO_BLOCKCHAIN_PENDING=" + JSON.stringify(operations));
            const result = yield steem.broadcast.sendAsync({ extensions: [], operations: operations }, { posting: this.postingWif });
            return new SteemOperationNumber_1.SteemOperationNumber(result.block_num, result.trx_num, operations.length - 1);
        });
    }
    getLastConfirmationMoment(delegator) {
        return __awaiter(this, void 0, void 0, function* () {
            log_1.Log.log().cheapDebug(() => "DIRECT_BLOCKCHAIN_API_GET_LAST_CONFIRMATION_MOMENT=" + JSON.stringify({ delegator: delegator }));
            if (typeof delegator === "undefined" || delegator.length == 0)
                throw new Error("Delegator must not be empty");
            let result = V1Handler_1.V1Handler.INTRODUCTION_OF_WISE_MOMENT;
            const supplier = new SteemJsAccountHistorySupplier_1.SteemJsAccountHistorySupplier(this.steem, delegator)
                .branch((historySupplier) => {
                historySupplier
                    .chain(new OperationNumberFilter_1.OperationNumberFilter(">", V1Handler_1.V1Handler.INTRODUCTION_OF_WISE_MOMENT).makeLimiter()) // this is limiter (restricts lookup to the period of wise presence)
                    .chain(new ToWiseOperationTransformer_1.ToWiseOperationTransformer(this.protocol))
                    .chain(new WiseOperationTypeFilter_1.WiseOperationTypeFilter(WiseOperationTypeFilter_1.WiseOperationTypeFilter.OperationType.ConfirmVote))
                    .chain(new ChainableLimiter_1.ChainableLimiter(1))
                    .chain(new Chainable_1.SimpleTaker((item) => {
                    result = item.moment;
                    return false;
                }))
                    .catch((error) => false); // if we do not continue on error the promise will be rejected with this error
            });
            yield supplier.start();
            return result;
        });
    }
    getWiseOperations(account, until) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof account === "undefined" || account.length == 0)
                throw new Error("Username must not be empty");
            const result = [];
            const supplier = new SteemJsAccountHistorySupplier_1.SteemJsAccountHistorySupplier(this.steem, account)
                .branch((historySupplier) => {
                historySupplier
                    .chain(new OperationNumberFilter_1.OperationNumberFilter(">", V1Handler_1.V1Handler.INTRODUCTION_OF_WISE_MOMENT).makeLimiter()) // this is limiter (restricts lookup to the period of wise presence)
                    .chain(new DateLimiter_1.DateLimiter(until))
                    .chain(new ToWiseOperationTransformer_1.ToWiseOperationTransformer(this.protocol))
                    .chain(new Chainable_1.SimpleTaker((item) => {
                    result.push(item);
                    return true;
                }))
                    .catch((error) => false); // if we do not continue on error the promise will be rejected with this error
            });
            yield supplier.start();
            return result;
        });
    }
    getWiseOperationsRelatedToDelegatorInBlock(delegator, blockNum, skipDelegatorCheck = false, delayOnNoBlockMs = 3100 /*§ §.*/) {
        return __awaiter(this, void 0, void 0, function* () {
            const block = yield this.steem.getBlockAsync(blockNum);
            if (!block) {
                return BluebirdPromise.delay(delayOnNoBlockMs)
                    .then(() => this.getWiseOperationsRelatedToDelegatorInBlock(delegator, blockNum, skipDelegatorCheck));
            }
            else {
                return this.getWiseOperationsRelatedToDelegatorInBlock_processBlock(delegator, blockNum, block, skipDelegatorCheck);
            }
        });
    }
    getWiseOperationsRelatedToDelegatorInBlock_processBlock(delegator, blockNum, block, skipDelegatorCheck) {
        let out = [];
        const block_num = blockNum;
        const timestampUtc = block.timestamp;
        for (let transaction_num = 0; transaction_num < block.transactions.length; transaction_num++) {
            const transaction = block.transactions[transaction_num];
            out = out.concat(this.getWiseOperationsRelatedToDelegatorInBlock_processTransaction(delegator, blockNum, transaction_num, transaction, new Date(timestampUtc + "Z" /* this is UTC date */), skipDelegatorCheck));
        }
        return out;
    }
    getWiseOperationsRelatedToDelegatorInBlock_processTransaction(delegator, blockNum, transactionNum, transaction, timestamp, skipDelegatorCheck) {
        const out = [];
        const steemTx = {
            block_num: blockNum,
            transaction_num: transactionNum,
            transaction_id: transaction.transaction_id,
            timestamp: timestamp,
            ops: transaction.operations
        };
        const handleResult = this.protocol.handleOrReject(steemTx);
        if (handleResult) {
            handleResult.forEach(wiseOp => {
                if (skipDelegatorCheck || wiseOp.delegator === delegator)
                    out.push(wiseOp);
            });
        }
        return out;
    }
    /**
     * This function is specific only to DirectBlockchainApi. It returns all wise operations in block
     * (without checking delegator).
     * @param blockNum - number of the block.
     * @param protocol - Protocol object.
     */
    getAllWiseOperationsInBlock(blockNum) {
        return this.getWiseOperationsRelatedToDelegatorInBlock("", blockNum, true /* skip delegator check */);
    }
    getDynamicGlobalProperties() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.steem.getDynamicGlobalPropertiesAsync();
        });
    }
    getAccountInfo(username) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield this.steem.getAccountsAsync([username]);
            if (result.length > 0) {
                return result[0];
            }
            else
                throw new NotFoundException_1.NotFoundException("Account " + username + " does not exist");
        });
    }
    getBlogEntries(username, startFrom, limit) {
        return this.steem.getBlogEntriesAsync(username, startFrom, limit);
    }
}
DirectBlockchainApi.DEFAULT_STEEM_API_ENDPOINT_URL = "https://anyx.io" /*§ ' "' + data.config.steem.defaultApiUrl + '" ' §.*/;
exports.DirectBlockchainApi = DirectBlockchainApi;
//# sourceMappingURL=DirectBlockchainApi.js.map