"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNewestCycleInfoLogStr = exports.getCurrentCycleMarker = exports.getDebug = exports.computeCycleMarker = exports.prune = exports.getCycleNumberFromTimestamp = exports.getStoredCycleByTimestamp = exports.getCycleChain = exports.validate = exports.prepend = exports.append = exports.getNewest = exports.reset = exports.init = exports.newest = exports.oldest = exports.cyclesByMarker = exports.cycles = void 0;
const Context_1 = require("./Context");
const NodeList_1 = require("./NodeList");
const nestedCounters_1 = require("../utils/nestedCounters");
const logger_1 = require("../logger");
const network_1 = require("../network");
/** STATE */
let p2pLogger;
let currentCycleMarker;
reset();
/** FUNCTIONS */
function init() {
    p2pLogger = Context_1.logger.getLogger('p2p');
}
exports.init = init;
function reset() {
    exports.cycles = [];
    exports.cyclesByMarker = {};
    exports.oldest = null;
    exports.newest = null;
    currentCycleMarker = null;
}
exports.reset = reset;
function getNewest() {
    return exports.newest;
}
exports.getNewest = getNewest;
function append(cycle) {
    const marker = computeCycleMarker(cycle);
    if (!exports.cyclesByMarker[marker]) {
        exports.cycles.push(cycle);
        exports.cyclesByMarker[marker] = cycle;
        exports.newest = cycle;
        currentCycleMarker = marker;
        if (!exports.oldest)
            exports.oldest = cycle;
    }
}
exports.append = append;
function prepend(cycle) {
    const marker = computeCycleMarker(cycle);
    if (!exports.cyclesByMarker[marker]) {
        exports.cycles.unshift(cycle);
        exports.cyclesByMarker[marker] = cycle;
        exports.oldest = cycle;
        // this will happen only once in the lifetime of a node. we never set newest to null
        if (exports.newest == null) {
            exports.newest = cycle;
        }
        // if our cycle is newer than the newest lets update newest and current cycle marker
        // this should only be happening before we have started digesting cycles.
        // but this check will make the actions correct.
        if (cycle.counter > exports.newest.counter) {
            exports.newest = cycle;
            currentCycleMarker = marker;
        }
    }
}
exports.prepend = prepend;
function validate(prev, next) {
    const prevMarker = computeCycleMarker(prev);
    info('validate: prevMarker', prevMarker);
    info('validate: next.previous', next.previous);
    info('validate: prev.standbylist', prev.standbyNodeListHash);
    info('validate: next.standbylist', next.standbyNodeListHash);
    info('validate: prev.nodelist', prev.nodeListHash);
    info('validate: next.nodelist', next.nodeListHash);
    if (next.previous !== prevMarker) {
        info('validate: ERROR: next.previous !== prevMarker');
        return false;
    }
    // [TODO] More validation
    return true;
}
exports.validate = validate;
function getCycleChain(start, end = start + 100) {
    // Limit how many are returned
    if (end - start > 100)
        end = start + 100;
    if (!exports.oldest)
        return [];
    if (end < exports.oldest.counter)
        return [];
    if (start < exports.oldest.counter)
        start = exports.oldest.counter;
    if (start > end)
        return [];
    const offset = exports.oldest.counter;
    const relStart = start - offset;
    const relEnd = end - offset;
    return exports.cycles.slice(relStart, relEnd + 1);
}
exports.getCycleChain = getCycleChain;
/**
 * Find a stored cycle with a timestamp.
 * This will fail if a timestamp is newer or older than cycles stored on this node
 * getCycleNumberFromTimestamp() will allow for predictions about future or past cycles.
 * @param timestamp
 */
function getStoredCycleByTimestamp(timestamp) {
    let secondsTs = Math.floor(timestamp * 0.001);
    // search from end, to improve normal case perf
    for (let i = exports.cycles.length - 1; i >= 0; i--) {
        let cycle = exports.cycles[i];
        if (cycle.start < secondsTs && cycle.start + cycle.duration >= secondsTs) {
            return cycle;
        }
    }
    if (exports.cycles.length > 0 && timestamp === exports.cycles[0].start) {
        nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `getStoredCycleByTimestamp edge case 0`);
        return exports.cycles[0];
    }
    return null;
}
exports.getStoredCycleByTimestamp = getStoredCycleByTimestamp;
/**
 * Get a cycle from a timestamp.
 * Future timestamps are allowed. Timestamps for cycles older than stored are allow if the allowOlder flag is set
 * @param timestamp timestamp of the TX.  NOTE by default the sync settle time will be added.  set addSyncSettleTime = false to avoid this
 * @param allowOlder this allows calculating a cycle number for a cycle that is not stored.
 * @param addSyncSettleTime add in the sync settle time to the request.
 */
function getCycleNumberFromTimestamp(timestamp, allowOlder = true, addSyncSettleTime = true) {
    let currentCycleShardData = Context_1.stateManager.getCurrentCycleShardData();
    let offsetTimestamp = timestamp;
    if (addSyncSettleTime) {
        offsetTimestamp = timestamp + Context_1.stateManager.syncSettleTime;
    }
    if (timestamp < 1 || timestamp == null) {
        let stack = new Error().stack;
        /* prettier-ignore */ Context_1.stateManager.statemanager_fatal(`getCycleNumberFromTimestamp ${timestamp}`, `getCycleNumberFromTimestamp ${timestamp} ,  ${stack}`);
    }
    //currentCycleShardData
    if (currentCycleShardData.timestamp < offsetTimestamp && offsetTimestamp <= currentCycleShardData.timestampEndCycle) {
        if (currentCycleShardData.cycleNumber == null) {
            /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp failed. cycleNumber == null', 'currentCycleShardData.cycleNumber == null');
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'currentCycleShardData.cycleNumber fail');
            const cycle = getStoredCycleByTimestamp(offsetTimestamp);
            console.log('CycleChain.getCycleNumberFromTimestamp getStoredCycleByTimestamp', cycle, timestamp);
            if (cycle != null) {
                /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp failed fatal redeemed', 'currentCycleShardData.cycleNumber == null, fatal redeemed');
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'currentCycleShardData.cycleNumber redeemed');
                return cycle.counter;
            }
            else {
                //debug only!!!
                let cycle2 = getStoredCycleByTimestamp(offsetTimestamp);
                /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp failed fatal not redeemed', 'getStoredCycleByTimestamp cycleNumber == null not redeemed');
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'currentCycleShardData.cycleNumber failed to redeem');
            }
        }
        else {
            nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `current cycle`);
            if (currentCycleShardData.timestamp === offsetTimestamp) {
                nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `exact curent upper boundary`);
            }
            return currentCycleShardData.cycleNumber;
        }
    }
    if (currentCycleShardData.cycleNumber == null) {
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'currentCycleShardData.cycleNumber == null');
        /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp: currentCycleShardData.cycleNumber == null', `getCycleNumberFromTimestamp: currentCycleShardData.cycleNumber == null ${currentCycleShardData.cycleNumber} timestamp:${timestamp}`);
    }
    //is it in the future
    if (offsetTimestamp > currentCycleShardData.timestampEndCycle) {
        let cycle = getNewest();
        let timePastCurrentCycle = offsetTimestamp - currentCycleShardData.timestampEndCycle;
        const cyclesAheadNotAdjusted = timePastCurrentCycle / (cycle.duration * 1000);
        let cyclesAhead = Math.ceil(cyclesAheadNotAdjusted);
        //If we land on an exact boundary this would have been broken under past logic
        if (cyclesAhead === cyclesAheadNotAdjusted) {
            nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `exact future boundary`);
        }
        nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `+${cyclesAhead}`);
        return currentCycleShardData.cycleNumber + cyclesAhead;
    }
    if (allowOlder === true) {
        //cycle is in the past, by process of elimination
        // let offsetSeconds = Math.floor(offsetTimestamp * 0.001)
        const cycle = getStoredCycleByTimestamp(offsetTimestamp);
        if (cycle != null) {
            if (cycle.counter == null) {
                /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp  unexpected cycle.cycleNumber == null', 'getCycleNumberFromTimestamp unexpected cycle.cycleNumber == null');
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `getCycleNumberFromTimestamp unexpected cycle.cycleNumber == null  ${timestamp}`);
            }
            const cyclesBehind = currentCycleShardData.cycleNumber - cycle.counter;
            nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', `-${cyclesBehind}`);
            return cycle.counter;
        }
        else {
            //nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'p2p lookup fail -estimate cycle')
            //debug only!!!
            //let cycle2 = CycleChain.getCycleByTimestamp(offsetTimestamp)
            //stateManager.statemanager_fatal('getCycleNumberFromTimestamp getCycleByTimestamp failed', 'getCycleByTimestamp getCycleByTimestamp failed')
            let cycle = getNewest();
            let cycleEstimate = currentCycleShardData.cycleNumber -
                Math.ceil((currentCycleShardData.timestampEndCycle - offsetTimestamp) / (cycle.duration * 1000));
            if (cycleEstimate < 1) {
                cycleEstimate = 1;
            }
            if (logger_1.logFlags.verbose)
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('getCycleNumberFromTimestamp', 'p2p lookup fail -estimate cycle: ' + cycleEstimate);
            return cycleEstimate;
        }
    }
    //failed to match, return -1
    /* prettier-ignore */ Context_1.stateManager.statemanager_fatal('getCycleNumberFromTimestamp failed final', `getCycleNumberFromTimestamp failed final ${timestamp}`);
    return -1;
}
exports.getCycleNumberFromTimestamp = getCycleNumberFromTimestamp;
function prune(keep) {
    const drop = exports.cycles.length - keep;
    if (drop <= 0)
        return;
    exports.cycles.splice(0, drop);
    exports.oldest = exports.cycles[0];
}
exports.prune = prune;
/** HELPER FUNCTIONS */
function computeCycleMarker(fields) {
    const cycleMarker = Context_1.crypto.hash(fields);
    return cycleMarker;
}
exports.computeCycleMarker = computeCycleMarker;
const idToIpPort = {};
function getDebug() {
    const chain = exports.cycles.map((record) => {
        const ctr = record.counter;
        const prev = record.previous.slice(0, 4);
        const rhash = Context_1.crypto.hash(record).slice(0, 4);
        const actv = record.active;
        const exp = record.expired;
        const desr = record.desired;
        const joind = record.joinedConsensors.map((c) => `${c.externalIp}:${c.externalPort}`);
        const actvd = record.activated.map((id) => {
            if (idToIpPort[id])
                return idToIpPort[id];
            const node = NodeList_1.nodes.get(id);
            if (node && node.externalIp && node.externalPort) {
                idToIpPort[id] = `${node.externalIp}:${node.externalPort}`;
                return idToIpPort[id];
            }
            else {
                return `missing-${id.substring(0, 5)}`;
            }
        });
        //    const rmvd = record.removed.map(id => idToPort[id])
        const rmvd = record.removed[0] !== 'all'
            ? record.removed.map((id) => (idToIpPort[id] ? idToIpPort[id] : 'x' + id.slice(0, 3)))
            : record.removed;
        const lost = record.lost.map((id) => (idToIpPort[id] ? idToIpPort[id] : 'x' + id.slice(0, 3)));
        const refu = record.refuted.map((id) => (idToIpPort[id] ? idToIpPort[id] : 'x' + id.slice(0, 3)));
        const apopd = record.apoptosized.map((id) => (idToIpPort[id] ? idToIpPort[id] : 'x' + id.slice(0, 3)));
        const rfshd = record.refreshedConsensors.map((c) => `${c.externalIp}:${c.externalPort}-${c.counterRefreshed}`);
        const str = `      ${ctr}:${prev}:${rhash} { actv:${actv}, exp:${exp}, desr:${desr}, joind:[${joind.join()}], actvd:[${actvd.join()}], lost:[${lost.join()}] refu:[${refu.join()}] apop:[${apopd.join()}] rmvd:[${record.removed[0] !== 'all' ? rmvd.join() : rmvd}], rfshd:[${rfshd.join()}] }`;
        return str;
    });
    const output = `
    DIGESTED:   ${exports.newest ? exports.newest.counter : exports.newest}
    CHAIN:
  ${chain.join('\n')}`;
    return output;
}
exports.getDebug = getDebug;
/** Returns the last appended cycle's marker. */
function getCurrentCycleMarker() {
    return currentCycleMarker;
}
exports.getCurrentCycleMarker = getCurrentCycleMarker;
/**
 * this gets the most recently finished cycle and puts it in a logs str wth message and time
 * this cycle is never the most current one as we start working on the next as soon as we update
 * newest
 * @param msg
 * @returns
 */
function getNewestCycleInfoLogStr(msg) {
    let cycleNumber = exports.newest ? exports.newest.counter : -1;
    const res = `Cycle: ${cycleNumber} Time:${(0, network_1.shardusGetTime)()} ${msg}`;
    return res;
}
exports.getNewestCycleInfoLogStr = getNewestCycleInfoLogStr;
function info(...msg) {
    const entry = `CycleChain: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
//# sourceMappingURL=CycleChain.js.map