"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 SetRules_1 = require("../protocol/SetRules");
const SteemOperationNumber_1 = require("../blockchain/SteemOperationNumber");
const Api_1 = require("../api/Api");
const V1Handler_1 = require("../protocol/versions/v1/V1Handler");
const NotFoundException_1 = require("../util/NotFoundException");
const ConfirmVote_1 = require("../protocol/ConfirmVote");
class FakeApi extends Api_1.Api {
    constructor(protocol, posts, dynamicGlobalProperties, accounts, transactions, blogEntries) {
        super();
        this.currentBlock = 0;
        this.pushedOperations = [];
        this.fakeTime = undefined;
        this.fakeDelayMs = 5;
        this.transactionsByBlock = {};
        this.protocol = protocol;
        this.posts = posts;
        this.dynamicGlobalProperties = dynamicGlobalProperties;
        this.accounts = accounts;
        this.transactions = transactions;
        this.transactions = this.transactions.map((trx) => {
            trx.timestamp = new Date(trx.timestamp); // prototype timestamp loaded from json
            return trx;
        });
        /*this.currentBlock = Math.max(
            this.transactions.map(trx => trx.block_num).reduce((maxBlockNum, thisBlockNum) => maxBlockNum = Math.max(maxBlockNum, thisBlockNum), 0),
            this.dynamicGlobalProperties.head_block_number
        );*/
        this.currentBlock = this.transactions.map(trx => trx.block_num)
            .reduce((maxBlockNum, thisBlockNum) => maxBlockNum = Math.max(maxBlockNum, thisBlockNum), 0) + 1;
        this.blogEntries = blogEntries;
        this.transactions.forEach(trx => {
            if (this.transactionsByBlock.hasOwnProperty(trx.block_num + "")) {
                this.transactionsByBlock[trx.block_num + ""].push(trx);
            }
            else
                this.transactionsByBlock[trx.block_num + ""] = [trx];
        });
    }
    static fromDataset(protocol, dataset) {
        return new FakeApi(protocol, dataset.posts, dataset.dynamicGlobalProperties, dataset.accounts, dataset.transactions, dataset.blogEntries);
    }
    name() {
        return "FakeApi";
    }
    loadPost(author, permlink) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            for (let i = 0; i < this.posts.length; i++) {
                if (this.posts[i].author === author && this.posts[i].permlink === permlink) {
                    return this.posts[i];
                }
            }
            throw new NotFoundException_1.NotFoundException("No such post @" + author + "/" + permlink);
        });
    }
    /*public loadRulesets(forWh, at: SteemOperationNumber, protocol: Protocol): Promise<EffectuatedSetRules> {
        return this.loadAllRulesets(delegator, at, protocol).then((rulesets: EffectuatedSetRules []) => {
            const votersRulesets: EffectuatedSetRules[] =  rulesets
                .filter((ruleset: EffectuatedSetRules) => ruleset.voter == voter);

            if (votersRulesets.length == 0) return { rulesets: [], moment: SteemOperationNumber.NEVER, voter: "", delegator: "" } as EffectuatedSetRules;
            else return votersRulesets.reduce((result: EffectuatedSetRules, ruleset: EffectuatedSetRules) => {
                if (ruleset.moment.isGreaterThan(result.moment)) return ruleset;
                else return result;
            });
        });
    }*/
    sendToBlockchain(operationsInTransaction) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            const blockNum = this.currentBlock + 1;
            const steemTrx = {
                block_num: blockNum,
                transaction_num: 0,
                transaction_id: "",
                timestamp: (this.fakeTime ? this.fakeTime : new Date()),
                ops: _.reverse(operationsInTransaction)
            };
            this.transactions.push(steemTrx);
            this.pushedOperations.push(steemTrx);
            this.transactionsByBlock[blockNum + ""] = [steemTrx];
            this.currentBlock = blockNum;
            log_1.Log.log().cheapDebug(() => "FAKE_API_PUSHED_TRX=" + JSON.stringify(steemTrx));
            return new SteemOperationNumber_1.SteemOperationNumber(blockNum, 0, operationsInTransaction.length - 1);
        });
    }
    loadRulesets(forWhom, at) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            if (!forWhom.voter && !forWhom.delegator)
                throw new Error("You have to specify either a voter, a delegator or both of them.");
            const allRules = [];
            for (let i = 0; i < this.transactions.length; i++) {
                const trx = this.transactions[i];
                const handleResult = this.protocol.handleOrReject(trx);
                if (handleResult) {
                    for (let j = 0; j < handleResult.length; j++) {
                        const effSo = handleResult[j];
                        if (SetRules_1.SetRules.isSetRules(effSo.command)) {
                            if ((forWhom.delegator && forWhom.voter && effSo.delegator === forWhom.delegator && effSo.voter === forWhom.voter) ||
                                (forWhom.delegator && !forWhom.voter && effSo.delegator === forWhom.delegator) ||
                                (!forWhom.delegator && forWhom.voter && effSo.voter === forWhom.voter)) {
                                if (at.isGreaterOrEqual(effSo.moment)) {
                                    const effSetRules = {
                                        rulesets: effSo.command.rulesets,
                                        voter: effSo.voter,
                                        delegator: effSo.delegator,
                                        moment: effSo.moment
                                    };
                                    allRules.push(effSetRules);
                                }
                            }
                        }
                    }
                }
            }
            const allRulesGrouppedByVoter = _.groupBy(allRules, "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;
        });
    }
    getLastConfirmationMoment(delegator) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            return this.transactions
                .map((trx) => this.protocol.handleOrReject(trx))
                .filter((handledOrRejected) => (!!handledOrRejected))
                .map((handled) => handled)
                .reduce((allOps, nextOps) => allOps.concat(nextOps))
                .filter((effSop) => ConfirmVote_1.ConfirmVote.isConfirmVote(effSop.command))
                .map((effSop) => effSop.moment)
                .reduce((newest, current) => {
                if (current.isGreaterThan(newest))
                    return current;
                else
                    return newest;
            }, V1Handler_1.V1Handler.INTRODUCTION_OF_WISE_MOMENT);
        });
    }
    getAllWiseOperationsInBlock(blockNum, delegatorFilter) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            if (blockNum > this.currentBlock + 1)
                throw new Error("Cannot get block that has number (" + blockNum + ") greater than next block (" + (this.currentBlock + 1) + ") (blockNum must be <= this.currentBlockNum+1)");
            while (blockNum > this.currentBlock)
                yield BluebirdPromise.delay(this.fakeDelayMs);
            yield BluebirdPromise.delay(this.fakeDelayMs);
            if (this.transactionsByBlock.hasOwnProperty(blockNum + "")) {
                return this.transactionsByBlock[blockNum + ""]
                    .filter((trx) => trx.block_num === blockNum)
                    .map((trx) => this.protocol.handleOrReject(trx))
                    .filter((handledOrRejected) => !!handledOrRejected)
                    .map((handled) => handled)
                    .reduce((allOps, nextOps) => allOps.concat(nextOps), [])
                    .filter((effSop) => delegatorFilter ? effSop.delegator === delegatorFilter : true);
            }
            else
                return [];
        });
    }
    getWiseOperationsRelatedToDelegatorInBlock(delegator, blockNum) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getAllWiseOperationsInBlock(blockNum, delegator);
        });
    }
    getDynamicGlobalProperties() {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            this.dynamicGlobalProperties.time = new Date().toISOString().replace("Z", "");
            this.dynamicGlobalProperties.head_block_number = this.currentBlock;
            return _.cloneDeep(this.dynamicGlobalProperties);
        });
    }
    getAccountInfo(username) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            const result = this.accounts.filter((info) => info.name === username);
            if (result.length === 0)
                throw new NotFoundException_1.NotFoundException("Account " + username + " does not exist");
            else
                return result[0];
        });
    }
    getWiseOperations(username, until) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            const result = [];
            for (let i = 0; i < this.transactions.length; i++) {
                const op = this.transactions[i];
                const handleResult = this.protocol.handleOrReject(op);
                if (handleResult) {
                    for (let j = 0; j < handleResult.length; j++) {
                        const effSo = handleResult[j];
                        if ((effSo.delegator === username && ConfirmVote_1.ConfirmVote.isConfirmVote(effSo.command))
                            || (effSo.voter === username)) {
                            // if (isConfirmVote(effSo.command) && effSo.command.accepted && !isConfirmVoteBoundWithVote(effSo.command)) Log.log().cheapDebug(() => JSON.stringify(effSo));
                            // (up) fake blockchain does not provide
                            // information on who pushed the operation to blockchain,
                            // so this hacky way is the only way to get this information.
                            // fortunately FakeApi is only used for unit testing.
                            if (effSo.timestamp.getTime() >= until.getTime()) {
                                result.push(effSo);
                            }
                        }
                    }
                }
            }
            return result;
        });
    }
    getBlogEntries(username, startFrom, limit) {
        return __awaiter(this, void 0, void 0, function* () {
            yield BluebirdPromise.delay(this.fakeDelayMs);
            const result = [];
            let userI = 0;
            for (let i = 0; i < this.blogEntries.length; i++) {
                const entry = this.blogEntries[i];
                if (entry.blog == username) {
                    if (userI >= startFrom && userI < startFrom + limit)
                        result.push(entry);
                    userI++;
                }
            }
            return result;
        });
    }
    getCurrentBlockNum() {
        return this.currentBlock;
    }
    pushFakeBlock() {
        const op = ["fake", {}];
        return this.sendToBlockchain([op]);
    }
    /**
     * Returns all operations that were pushed using #sentToBlockchain() after initialization of FakeApi.
     */
    getPushedTransactions() {
        return this.pushedOperations;
    }
    setFakeTime(fakeTime_) {
        this.fakeTime = fakeTime_;
    }
    setFakeDelayMs(delayMs) {
        this.fakeDelayMs = delayMs;
    }
}
exports.FakeApi = FakeApi;
//# sourceMappingURL=FakeApi.js.map