/**
 * Copyright 2016 the WHATAP project authors. All rights reserved.
 * Use of this source code is governed by a license that
 * can be found in the LICENSE file.
 */

var IntKeyMap       = require('../util/intkey-map'),
    TraceContext    = require('./trace-context'),
    Logger          = require('../logger');
var Long            = require('long');
var { AsyncLocalStorage } = require('async_hooks');
var crypto = require('crypto');
var { v1: uuidv1 } = require('uuid');
const KeyGen = require('../util/keygen');

function TraceContextManager() {
    this.initialized = false;
    this.nextId = 1;
    this.currentId = null;
    this._asyncLocalStorage = new AsyncLocalStorage();
    this.entry = new IntKeyMap(1021,1).setMax(10000);
    this.node = null;
    this.clock_seq = null;
}

TraceContextManager.prototype.keys = function () {
    return this.entry.keys();
};
TraceContextManager.prototype.getContextEnumeration = function () {
    return this.entry.values();
};
TraceContextManager.prototype.size = function () {
    return this.entry.size();
};
TraceContextManager.prototype.getContext = function (key) {
    return this._asyncLocalStorage.getStore();
};
TraceContextManager.prototype.getCurrentContext = function () {
    return this.getContext();
};
TraceContextManager.prototype.start = function () {

    if(this.initialized === false) { return null; }

    // var conf = require('../conf/configure');
    // if(this.size() > conf.profile_max_count) { return null; }

    var ctx = new TraceContext(KeyGen.next());
    ctx.start_time = Date.now();
    this.entry.put(ctx.id, ctx);
    this.currentId = ctx.id;

    return ctx;
};
TraceContextManager.prototype.end = function (id) {
    if(id == null) {
        this.entry.remove(this.currentId); // important: high risky
        this.currentId = null;
    } else {
        this.entry.remove(id);
        // return;
    }
    // this._asyncLocalStorage.disable();
};
TraceContextManager.prototype.getNextId = function () {
    var length = 16
    var buffer = crypto.randomBytes(Math.ceil(length / 2));

    this.nextId =  buffer.toString('hex').substring(0, length);
    return this.nextId;
};
TraceContextManager.prototype.getCurrentId = function () {
    return this.currentId;
};
TraceContextManager.prototype.resume = function (id) {
    if(id == null) { return null; }
    if(id instanceof TraceContext) {
        id = id._id;
    }
    var c = this.getContext(id);
    if(c){
        this.currentId = id;
        return c;
    } else {
        this.currentId = null;
        return null;
    }
};
TraceContextManager.prototype.isExist = function (id) {
    return this.entry.containsKey(id);
};

TraceContextManager.prototype.getId = function () {
    if (!this.node) {
        this.node = Math.floor(Math.random() * 0xffffffffffff);
    }

    if (!this.clock_seq) {
        this.clock_seq = Math.floor(Math.random() * (1 << 14));
    }

    const uuid1 = uuidv1({
        node: this.node,
        clockseq: this.clock_seq
    });

    const buffer = Buffer.from(uuid1.replace(/-/g, ''), 'hex');
    const uuidInt = BigInt('0x' + buffer.toString('hex'));

    const upperBits = (uuidInt >> 64n) & 0xffffffffffffffffn;
    const lowerBits = (uuidInt << 64n) & 0xffffffffffffffffn;
    const key = upperBits ^ lowerBits;

    return Long.fromString(key.toString()).toNumber();
};

module.exports = new TraceContextManager();