"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.now = exports.Profiler = exports.Logger = void 0;
class NoOpLoggerProvider {
    log(_severity, _content, _category) {
        // do nothing
    }
}
class ConsoleLoggerProvider {
    log(severity, content, category) {
        // eslint-disable-next-line no-console
        console.log(`${this.color(severity)} ${category ? '\x1b[35m' + category + '\x1b[0m ' : ''}${content}`);
    }
    color(severity) {
        switch (severity) {
            case 'verbose':
                return '\x1b[34;40mv\x1b[0m';
            case 'info':
                return '\x1b[32mi\x1b[0m';
            case 'warning':
                return '\x1b[30;43mw\x1b[0m';
            case 'error':
                return '\x1b[31;40me\x1b[0m';
            case 'fatal':
                return '\x1b[101mf\x1b[0m';
            default:
                throw new Error(`unsupported severity: ${severity}`);
        }
    }
}
const SEVERITY_VALUE = {
    verbose: 1000,
    info: 2000,
    warning: 4000,
    error: 5000,
    fatal: 6000
};
const LOGGER_PROVIDER_MAP = {
    ['none']: new NoOpLoggerProvider(),
    ['console']: new ConsoleLoggerProvider()
};
const LOGGER_DEFAULT_CONFIG = {
    provider: 'console',
    minimalSeverity: 'warning',
    logDateTime: true,
    logSourceLocation: false
};
let LOGGER_CONFIG_MAP = { ['']: LOGGER_DEFAULT_CONFIG };
function log(arg0, arg1, arg2, arg3) {
    if (arg1 === undefined) {
        // log(category: string): Logger.CategorizedLogger;
        return createCategorizedLogger(arg0);
    }
    else if (arg2 === undefined) {
        // log(severity, content);
        logInternal(arg0, arg1, 1);
    }
    else if (typeof arg2 === 'number' && arg3 === undefined) {
        // log(severity, content, stack)
        logInternal(arg0, arg1, arg2);
    }
    else if (typeof arg2 === 'string' && arg3 === undefined) {
        // log(severity, category, content)
        logInternal(arg0, arg2, 1, arg1);
    }
    else if (typeof arg2 === 'string' && typeof arg3 === 'number') {
        // log(severity, category, content, stack)
        logInternal(arg0, arg2, arg3, arg1);
    }
    else {
        throw new TypeError('input is valid');
    }
}
function createCategorizedLogger(category) {
    return {
        verbose: log.verbose.bind(null, category),
        info: log.info.bind(null, category),
        warning: log.warning.bind(null, category),
        error: log.error.bind(null, category),
        fatal: log.fatal.bind(null, category)
    };
}
// NOTE: argument 'category' is put the last parameter beacause typescript
// doesn't allow optional argument put in front of required argument. This
// order is different from a usual logging API.
function logInternal(severity, content, stack, category) {
    const config = LOGGER_CONFIG_MAP[category || ''] || LOGGER_CONFIG_MAP[''];
    if (SEVERITY_VALUE[severity] < SEVERITY_VALUE[config.minimalSeverity]) {
        return;
    }
    if (config.logDateTime) {
        content = `${new Date().toISOString()}|${content}`;
    }
    if (config.logSourceLocation) {
        // TODO: calculate source location from 'stack'
    }
    LOGGER_PROVIDER_MAP[config.provider].log(severity, content, category);
}
// eslint-disable-next-line @typescript-eslint/no-namespace
(function (log) {
    function verbose(arg0, arg1) {
        log('verbose', arg0, arg1);
    }
    log.verbose = verbose;
    function info(arg0, arg1) {
        log('info', arg0, arg1);
    }
    log.info = info;
    function warning(arg0, arg1) {
        log('warning', arg0, arg1);
    }
    log.warning = warning;
    function error(arg0, arg1) {
        log('error', arg0, arg1);
    }
    log.error = error;
    function fatal(arg0, arg1) {
        log('fatal', arg0, arg1);
    }
    log.fatal = fatal;
    function reset(config) {
        LOGGER_CONFIG_MAP = {};
        set('', config || {});
    }
    log.reset = reset;
    function set(category, config) {
        if (category === '*') {
            reset(config);
        }
        else {
            const previousConfig = LOGGER_CONFIG_MAP[category] || LOGGER_DEFAULT_CONFIG;
            LOGGER_CONFIG_MAP[category] = {
                provider: config.provider || previousConfig.provider,
                minimalSeverity: config.minimalSeverity || previousConfig.minimalSeverity,
                logDateTime: (config.logDateTime === undefined) ? previousConfig.logDateTime : config.logDateTime,
                logSourceLocation: (config.logSourceLocation === undefined) ? previousConfig.logSourceLocation :
                    config.logSourceLocation
            };
        }
        // TODO: we want to support wildcard or regex?
    }
    log.set = set;
    function setWithEnv(env) {
        const config = {};
        if (env.logLevel) {
            config.minimalSeverity = env.logLevel;
        }
        set('', config);
    }
    log.setWithEnv = setWithEnv;
})(log || (log = {}));
// eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
exports.Logger = log;
// TODO
// class WebGLEvent implements Profiler.Event {}
class Event {
    constructor(category, name, startTime, endCallback, timer, ctx) {
        this.category = category;
        this.name = name;
        this.startTime = startTime;
        this.endCallback = endCallback;
        this.timer = timer;
        this.ctx = ctx;
    }
    end() {
        return this.endCallback(this);
    }
    async checkTimer() {
        if (this.ctx === undefined || this.timer === undefined) {
            throw new Error('No webgl timer found');
        }
        else {
            this.ctx.endTimer();
            return this.ctx.waitForQueryAndGetTime(this.timer);
        }
    }
}
class EventRecord {
    constructor(category, name, startTime, endTime) {
        this.category = category;
        this.name = name;
        this.startTime = startTime;
        this.endTime = endTime;
    }
}
class Profiler {
    static create(config) {
        if (config === undefined) {
            return new this();
        }
        return new this(config.maxNumberEvents, config.flushBatchSize, config.flushIntervalInMilliseconds);
    }
    constructor(maxNumberEvents, flushBatchSize, flushIntervalInMilliseconds) {
        this._started = false;
        this._flushPointer = 0;
        this._started = false;
        this._maxNumberEvents = maxNumberEvents === undefined ? 10000 : maxNumberEvents;
        this._flushBatchSize = flushBatchSize === undefined ? 10 : flushBatchSize;
        this._flushIntervalInMilliseconds = flushIntervalInMilliseconds === undefined ? 5000 : flushIntervalInMilliseconds;
    }
    // start profiling
    start() {
        this._started = true;
        this._timingEvents = [];
        this._flushTime = (0, exports.now)();
        this._flushPointer = 0;
    }
    // stop profiling
    stop() {
        this._started = false;
        for (; this._flushPointer < this._timingEvents.length; this._flushPointer++) {
            this.logOneEvent(this._timingEvents[this._flushPointer]);
        }
    }
    event(category, name, func, ctx) {
        const event = this._started ? this.begin(category, name, ctx) : undefined;
        let isPromise = false;
        const res = func();
        // we consider a then-able object is a promise
        if (res && typeof res.then === 'function') {
            isPromise = true;
            return new Promise((resolve, reject) => {
                res
                    .then(async (value) => {
                    if (event) {
                        await event.end();
                    }
                    resolve(value);
                }, async (reason) => {
                    if (event) {
                        await event.end();
                    }
                    reject(reason);
                });
            });
        }
        if (!isPromise && event) {
            const eventRes = event.end();
            if (eventRes && typeof eventRes.then === 'function') {
                return new Promise((resolve, reject) => {
                    (eventRes).then(() => {
                        resolve(res);
                    }, (reason) => {
                        reject(reason);
                    });
                });
            }
        }
        return res;
    }
    // begin an event
    begin(category, name, ctx) {
        if (!this._started) {
            throw new Error('profiler is not started yet');
        }
        if (ctx === undefined) {
            const startTime = (0, exports.now)();
            this.flush(startTime);
            return new Event(category, name, startTime, e => this.endSync(e));
        }
        else {
            const timer = ctx.beginTimer();
            return new Event(category, name, 0, async (e) => this.end(e), timer, ctx);
        }
    }
    // end the specific event
    async end(event) {
        const endTime = await event.checkTimer();
        if (this._timingEvents.length < this._maxNumberEvents) {
            this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
            this.flush(endTime);
        }
    }
    endSync(event) {
        const endTime = (0, exports.now)();
        if (this._timingEvents.length < this._maxNumberEvents) {
            this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
            this.flush(endTime);
        }
    }
    logOneEvent(event) {
        exports.Logger.verbose(`Profiler.${event.category}`, `${(event.endTime - event.startTime).toFixed(2)}ms on event '${event.name}' at ${event.endTime.toFixed(2)}`);
    }
    flush(currentTime) {
        if (this._timingEvents.length - this._flushPointer >= this._flushBatchSize ||
            currentTime - this._flushTime >= this._flushIntervalInMilliseconds) {
            // should flush when either batch size accumlated or interval elepsed
            for (const previousPointer = this._flushPointer; this._flushPointer < previousPointer + this._flushBatchSize &&
                this._flushPointer < this._timingEvents.length; this._flushPointer++) {
                this.logOneEvent(this._timingEvents[this._flushPointer]);
            }
            this._flushTime = (0, exports.now)();
        }
    }
    get started() {
        return this._started;
    }
}
exports.Profiler = Profiler;
/**
 * returns a number to represent the current timestamp in a resolution as high as possible.
 */
exports.now = (typeof performance !== 'undefined' && performance.now) ? () => performance.now() : Date.now;
//# sourceMappingURL=instrument.js.map