"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const utils = __importStar(require("../utils"));
const logger_1 = require("../logger");
const nestedCounters_1 = require("../utils/nestedCounters");
const InternalRouteEnum_1 = require("../types/enum/InternalRouteEnum");
const TypeIdentifierEnum_1 = require("../types/enum/TypeIdentifierEnum");
const GlobalAccountReportResp_1 = require("../types/GlobalAccountReportResp");
const RequestErrorEnum_1 = require("../types/enum/RequestErrorEnum");
const Helpers_1 = require("../types/Helpers");
const ResponseError_1 = require("../types/ResponseError");
class AccountGlobals {
    /** Need the ablity to get account copies and use them later when applying a transaction. how to use the right copy or even know when to use this at all? */
    /** Could go by cycle number. if your cycle matches the one in is list use it? */
    /** What if the global account is transformed several times durring that cycle. oof. */
    /** ok best thing to do is to store the account every time it changes for a given period of time. */
    /** how to handle reparing a global account... yikes that is hard. */
    //globalAccountRepairBank: Map<string, Shardus.AccountsCopy[]>
    constructor(stateManager, profiler, app, logger, storage, p2p, crypto, config) {
        this.crypto = crypto;
        this.app = app;
        this.logger = logger;
        this.config = config;
        this.profiler = profiler;
        this.p2p = p2p;
        this.storage = storage;
        this.stateManager = stateManager;
        this.mainLogger = logger.getLogger('main');
        this.fatalLogger = logger.getLogger('fatal');
        this.shardLogger = logger.getLogger('shardDump');
        this.statsLogger = logger.getLogger('statsDump');
        this.statemanager_fatal = stateManager.statemanager_fatal;
        this.globalAccountSet = new Set();
        this.hasknownGlobals = false;
    }
    setupHandlers() {
        // this.p2p.registerInternal(
        //   'get_globalaccountreport',
        //   async (
        //     _payload: unknown,
        //     respond: (arg0: GlobalAccountReportResp | { error: string }) => Promise<number>,
        //     _sender: unknown,
        //     _tracker: string,
        //     msgSize: number
        //   ) => {
        //     this.profiler.scopedProfileSectionStart('get_globalaccountreport', false, msgSize)
        //     let responseSize = cUninitializedSize
        //     try {
        //       const result = {
        //         combinedHash: '',
        //         accounts: [],
        //         ready: this.stateManager.appFinishedSyncing,
        //       } as GlobalAccountReportResp
        //       const globalAccountKeys = this.globalAccountSet.keys()
        //       const toQuery: string[] = []
        //       console.log(
        //         'Running get_globalaccountreport',
        //         this.stateManager.accountSync.globalAccountsSynced,
        //         this.stateManager.appFinishedSyncing
        //       )
        //       console.log('Running get_globalaccountreport result', result)
        //       // not ready
        //       if (
        //         this.stateManager.accountSync.globalAccountsSynced === false ||
        //         this.stateManager.appFinishedSyncing === false
        //       ) {
        //         result.ready = false
        //         await respond(result)
        //       }
        //       //TODO: Perf  could do things faster by pulling from cache, but would need extra testing:
        //       // let notInCache:string[]
        //       // for(let key of globalAccountKeys){
        //       //   let report
        //       //   if(this.globalAccountRepairBank.has(key)){
        //       //     let accountCopyList = this.globalAccountRepairBank.get(key)
        //       //     let newestCopy = accountCopyList[accountCopyList.length-1]
        //       //     report = {id:key, hash:newestCopy.hash, timestamp:newestCopy.timestamp }
        //       //   } else{
        //       //     notInCache.push(key)
        //       //   }
        //       //   result.accounts.push(report)
        //       // }
        //       for (const key of globalAccountKeys) {
        //         toQuery.push(key)
        //       }
        //       if (result.ready === false) {
        //         nestedCountersInstance.countEvent(`sync`, `HACKFIX - forgot to return!`)
        //         await respond({ error: 'Result not ready' })
        //         return
        //       }
        //       let accountData: Shardus.WrappedData[]
        //       let ourLockID = -1
        //       try {
        //         ourLockID = await this.stateManager.fifoLock('accountModification')
        //         // TODO: if we have more than 900 keys to query in this list must split this into multiple queries!.. ok technically this will not impact liberdus but it could impact
        //         //       a dapp that uses sqlite
        //         accountData = await this.app.getAccountDataByList(toQuery)
        //       } finally {
        //         this.stateManager.fifoUnlock('accountModification', ourLockID)
        //       }
        //       if (accountData != null) {
        //         for (const wrappedAccount of accountData) {
        //           const report = {
        //             id: wrappedAccount.accountId,
        //             hash: wrappedAccount.stateId,
        //             timestamp: wrappedAccount.timestamp,
        //           }
        //           result.accounts.push(report)
        //         }
        //       }
        //       //TODO: SOON. PERF Disiable this in production or performance testing. (we need a global flag to specify if it is a release or debug build, could then implement this, along with turning off debug endpoints)
        //       this.stateManager.testAccountDataWrapped(accountData)
        //       result.accounts.sort(utils.sort_id_Asc)
        //       result.combinedHash = this.crypto.hash(result)
        //       responseSize = await respond(result)
        //     } finally {
        //       this.profiler.scopedProfileSectionEnd('get_globalaccountreport', responseSize)
        //     }
        //   }
        // )
        const globalAccountReportBinaryHandler = {
            name: InternalRouteEnum_1.InternalRouteEnum.binary_get_globalaccountreport,
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            handler: async (payload, respond, header, sign) => {
                const route = InternalRouteEnum_1.InternalRouteEnum.binary_get_globalaccountreport;
                nestedCounters_1.nestedCountersInstance.countEvent('internal', route);
                this.profiler.scopedProfileSectionStart(route);
                const errorHandler = (errorType, opts) => (0, Helpers_1.requestErrorHandler)(route, errorType, header, opts);
                try {
                    const requestStream = (0, Helpers_1.getStreamWithTypeCheck)(payload, TypeIdentifierEnum_1.TypeIdentifierEnum.cGlobalAccountReportReq);
                    if (!requestStream) {
                        errorHandler(RequestErrorEnum_1.RequestErrorEnum.InvalidRequest);
                        return respond((0, ResponseError_1.BadRequest)('invalid request stream'), ResponseError_1.serializeResponseError);
                    }
                    const result = {
                        combinedHash: '',
                        accounts: [],
                        ready: this.stateManager.appFinishedSyncing,
                    };
                    const globalAccountKeys = this.globalAccountSet.keys();
                    const toQuery = [];
                    /* prettier-ignore */ if (logger_1.logFlags.debug)
                        console.log(`Running ${route}`, this.stateManager.accountSync.globalAccountsSynced, this.stateManager.appFinishedSyncing);
                    /* prettier-ignore */ if (logger_1.logFlags.debug)
                        console.log(`Running ${route} result`, result);
                    //not ready
                    if (this.stateManager.accountSync.globalAccountsSynced === false ||
                        this.stateManager.appFinishedSyncing === false) {
                        result.ready = false;
                        return respond(result, GlobalAccountReportResp_1.serializeGlobalAccountReportResp);
                    }
                    for (const key of globalAccountKeys) {
                        toQuery.push(key);
                    }
                    if (result.ready === false) {
                        nestedCounters_1.nestedCountersInstance.countEvent(`sync`, `Server not ready to respond: ${route}!`);
                        return respond((0, ResponseError_1.BadRequest)('Result not ready'), ResponseError_1.serializeResponseError);
                    }
                    let accountData;
                    let ourLockID = -1;
                    try {
                        ourLockID = await this.stateManager.fifoLock('accountModification');
                        // TODO: if we have more than 900 keys to query in this list must split this into multiple queries!.. ok technically this will not impact liberdus but it could impact
                        //       a dapp that uses sqlite
                        accountData = await this.app.getAccountDataByList(toQuery);
                    }
                    finally {
                        this.stateManager.fifoUnlock('accountModification', ourLockID);
                    }
                    if (accountData != null) {
                        for (const wrappedAccount of accountData) {
                            const report = {
                                id: wrappedAccount.accountId,
                                hash: wrappedAccount.stateId,
                                timestamp: wrappedAccount.timestamp,
                            };
                            result.accounts.push(report);
                        }
                    }
                    //TODO: SOON. PERF Disiable this in production or performance testing. (we need a global flag to specify if it is a release or debug build, could then implement this, along with turning off debug endpoints)
                    this.stateManager.testAccountDataWrapped(accountData);
                    result.accounts.sort(utils.sort_id_Asc);
                    result.combinedHash = this.crypto.hash(result);
                    respond(result, GlobalAccountReportResp_1.serializeGlobalAccountReportResp);
                }
                catch (e) {
                    nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-exception`);
                    /* prettier-ignore */ if (logger_1.logFlags.error)
                        this.mainLogger.error(`${route}: Exception executing request: ${utils.errorToStringFull(e)}`);
                    return respond((0, ResponseError_1.InternalError)('Exception executing request'), ResponseError_1.serializeResponseError);
                }
                finally {
                    this.profiler.scopedProfileSectionEnd(route);
                }
            },
        };
        this.p2p.registerInternalBinary(globalAccountReportBinaryHandler.name, globalAccountReportBinaryHandler.handler);
    }
    /**
     * isGlobalAccount
     * is the account global
     * @param accountID
     */
    isGlobalAccount(accountID) {
        return this.globalAccountSet.has(accountID);
    }
    setGlobalAccount(accountID) {
        this.globalAccountSet.add(accountID);
    }
    /**
     * getGlobalListEarly
     * sync requires having knowlege of what accounts are global very early in the process.
     * This will get an early global report (note does not have account data, just id,hash,timestamp)
     */
    async getGlobalListEarly(syncFromArchiver = false) {
        let retriesLeft = 10;
        if (syncFromArchiver) {
            retriesLeft = 100;
        }
        //This will try up to 10 times to get the global list
        //if that fails we will throw an error that shoul cause an apop
        while (this.hasknownGlobals === false) {
            if (retriesLeft === 0) {
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('sync', 'DATASYNC: getGlobalListEarly: failed to get global list after 10 retries');
                this.fatalLogger.fatal(`DATASYNC: getGlobalListEarly: failed to get global list after 10 retries`);
                throw new Error(`DATASYNC: getGlobalListEarly: failed to get global list after 10 retries`);
            }
            try {
                const globalReport = await this.stateManager.accountSync.getRobustGlobalReport('getGlobalListEarly', syncFromArchiver);
                const temp = [];
                for (const report of globalReport.accounts) {
                    temp.push(report.id);
                    //set this as a known global
                    this.globalAccountSet.add(report.id);
                }
                /* prettier-ignore */ if (logger_1.logFlags.debug)
                    this.mainLogger.debug(`DATASYNC: getGlobalListEarly: ${utils.stringifyReduce(temp)}`);
                this.hasknownGlobals = true;
            }
            catch (err) {
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('sync', 'DATASYNC: getGlobalListEarly: getRobustGlobalReport results === null');
                await utils.sleep(10000);
                if (syncFromArchiver) {
                    await utils.sleep(60000);
                }
            }
            finally {
                retriesLeft--;
            }
        }
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('sync', `DATASYNC: getGlobalListEarly success:${this.hasknownGlobals}`);
    }
    getGlobalDebugReport() {
        const globalAccountSummary = [];
        for (const globalID in this.globalAccountSet.keys()) {
            const accountHash = this.stateManager.accountCache.getAccountHash(globalID);
            const summaryObj = { id: globalID, state: accountHash.h, ts: accountHash.t };
            globalAccountSummary.push(summaryObj);
        }
        const globalStateHash = this.crypto.hash(globalAccountSummary);
        return { globalAccountSummary, globalStateHash };
    }
}
exports.default = AccountGlobals;
//# sourceMappingURL=AccountGlobals.js.map