"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 _ = require("lodash");
const log_1 = require("../../log/log");
const Chainable_1 = require("../../chainable/Chainable");
class SteemJsAccountHistorySupplier extends Chainable_1.ChainableSupplier {
    constructor(steem, username) {
        super();
        this.batchSize = 1000;
        this.onFinishCallback = () => { };
        /**
         * This is to prevent the situation in which a custom_json is placed at the end of one batch, and vote is placed at the start of the next batch.
         */
        this.previousBatchLeftoverCustomJson = undefined;
        this.steem = steem;
        this.username = username;
        if (!this.steem)
            throw new Error("Supplied steem object is null");
    }
    me() {
        return this;
    }
    withBatchSize(batchSize) {
        this.batchSize = batchSize;
        return this;
    }
    start() {
        return __awaiter(this, void 0, void 0, function* () {
            // load and iterate over blockchain
            this.loadFromOnlyIfConsumers(-1);
            yield new BluebirdPromise((resolve, reject) => {
                this.onFinishCallback = (error) => {
                    if (error)
                        reject(error);
                    else
                        resolve();
                };
            });
        });
    }
    loadFromOnlyIfConsumers(from) {
        if (this.shouldLoadNewItems()) {
            this.loadFrom(from);
        }
    }
    loadFrom(from) {
        // Sometimes at the end of account history "from" can be lower than 1000. In that case we should set limit to "from". It will simply load operations including the oldest one.
        const batchLimit = (from === -1 ? this.batchSize : Math.min(this.batchSize, from));
        log_1.Log.log().debug("STEEMJSACCOUNTHISTORYSUPPLIER_GET_ACCOUNT_HISTORY_ASYNC="
            + JSON.stringify({ username: this.username, from: from, batchLimit: batchLimit }));
        this.steem.getAccountHistoryAsync(this.username, from, batchLimit)
            .then((result) => {
            if (result.length == 0) {
                this.onFinishCallback(undefined);
            }
            else {
                result.reverse(); // loadFrom(from=-1) returns last "batchSize" of operations, but they are sorted from oldest to the newest.
                // So the newest operation index is 1000 (it is a little awkward, but when limit=1000, steem returns 1001
                // operations - it may be a bug, so I do not rely on this behavior - thats why I use result.length < batchSize instead of result.length <= batchSize) below.
                const loadNext = this.processBatch(result);
                if (loadNext && result.length >= this.batchSize) { // not all operations were loaded
                    const from = result[result.length - 1][0] - 1; // absolute number of oldest loaded operation, minus one (remember that the result array was previously reversed)
                    this.loadFromOnlyIfConsumers(from);
                }
                else {
                    this.onFinishCallback(undefined);
                }
            }
        }, error => {
            const continueOnThisError = this.give(error, undefined);
            if (!continueOnThisError)
                this.onFinishCallback(error);
        });
    }
    processBatch(ops) {
        let loadNext = true;
        if (this.previousBatchLeftoverCustomJson) {
            ops.unshift(this.previousBatchLeftoverCustomJson);
            this.previousBatchLeftoverCustomJson = undefined;
        }
        if (ops[ops.length - 1][1].op[0] === "custom_json") {
            const lastOp = ops.pop();
            this.previousBatchLeftoverCustomJson = lastOp;
        }
        const opsGroupedByTransactionNum = _.groupBy(ops, (op) => op[1].trx_id);
        /* we use trx_id as it is purely unique */
        const opsMappedToStemTransactions = _.values(opsGroupedByTransactionNum) // we count only single transactions
            .map((txOps) => {
            const transaction = {
                block_num: txOps[0][1].block,
                transaction_num: txOps[0][1].trx_in_block,
                transaction_id: txOps[0][1].trx_id,
                timestamp: new Date(txOps[0][1].timestamp + "Z"),
                ops: _.reverse(txOps.map((op) => op[1].op)) // map operations,
                // they have to be reversed (we want transactions from the newset to the oldest, but operations in reversed order: from the oldest to the newset)
            };
            return transaction;
        });
        const opsMappedToStemTransactionsSorted = _.reverse(_.sortBy(opsMappedToStemTransactions, ["block_num", "transaction_num"]));
        opsMappedToStemTransactionsSorted.forEach(trx => {
            if (log_1.Log.log().isDebug()) {
                let hasAcceptedConfirmVote = false;
                let hasVote = false;
                if (trx.ops.filter(op => op[0] === "vote").length > 0)
                    hasVote = true;
                if (trx.ops.filter(op => op[0] === "custom_json")
                    .map(op => op[1])
                    .filter((cjop) => cjop.id === "wise")
                    .map(cjop => JSON.parse(cjop.json))
                    .filter(o => o[0] === "v2:confirm_vote")
                    .filter(o => _.get(o[1], "accepted") === true).length > 0)
                    hasAcceptedConfirmVote = true;
                if (hasAcceptedConfirmVote && !hasVote) {
                    log_1.Log.log().debug("STEEM_JS_ACCOUNT_HISTORY_SUPPLIER_MISSING_VOTE=" + JSON.stringify(trx) + "\n"
                        + "STEEM_JS_ACCOUNT_HISTORY_SUPPLIER_MISSING_VOTE_ALL_OPS=" + JSON.stringify(ops));
                }
            }
            if (loadNext)
                loadNext = this.give(undefined, trx);
        });
        return loadNext;
    }
}
exports.SteemJsAccountHistorySupplier = SteemJsAccountHistorySupplier;
//# sourceMappingURL=SteemJsAccountHistorySupplier.js.map