/*
 * @project: TERA
 * @version: Development (beta)
 * @license: MIT (not for evil)
 * @copyright: Yuriy Ivanov 2017-2019 [progr76@gmail.com]
 * Web: https://terafoundation.org
 * Twitter: https://twitter.com/terafoundation
 * Telegram: https://web.telegram.org/#/im?p=@terafoundation
*/

"use strict";
require("./library.js");
require("./crypto-library");
require("./terahashmining");
const crypto = require('crypto');
const os = require('os');
global.glStopNode = false;
const MAX_TIME_NETWORK_TRANSPORT = 1 * 1000;
var GlSumUser;
var GlSumSys;
var GlSumIdle;
global.CountAllNode = 0;
module.exports = class CCommon
{
    constructor(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
    {
        global.SERVER = this
        this.VirtualMode = bVirtual
        this.KeyPair = SetKeyPair
        var PubKey = SetKeyPair.getPublicKey('', 'compressed');
        this.PubKeyType = PubKey[0]
        this.addrArr = PubKey.slice(1)
        this.addrStr = GetHexFromArr(this.addrArr)
        this.HashDBArr = shaarr2(this.KeyPair.getPrivateKey(), [0, 0, 0, 0, 0, 0, 0, 1])
        this.ServerSign = []
    }
    AddStatOnTimer()
    {
        var CountAll = 0;
        var CurTime = GetCurrentTime() - 0;
        for(var i = 0; i < this.NodesArr.length; i++)
        {
            var Item = this.NodesArr[i];
            if(Item.LastTime && (CurTime - Item.LastTime) < 3600 * 1000)
                CountAll++
            else
                if(Item.LastTimeGetNode && (CurTime - Item.LastTimeGetNode) < 3600 * 1000)
                    CountAll++
        }
        global.CountAllNode = CountAll
        if(!global.STAT_MODE)
            return ;
        var StateTX = DApps.Accounts.DBStateTX.Read(0);
        if(StateTX)
        {
            var Delta = this.CurrentBlockNum - StateTX.BlockNum;
            ADD_TO_STAT("MAX:DELTA_TX", Delta)
        }
        var bHasCP = 0;
        if(CHECK_POINT.BlockNum)
        {
            var Block = this.ReadBlockHeaderDB(CHECK_POINT.BlockNum);
            if(Block && CompareArr(CHECK_POINT.Hash, Block.Hash) === 0)
                bHasCP = 1
        }
        var MinVer = global.MIN_VER_STAT;
        if(MinVer === 0)
            MinVer = global.UPDATE_CODE_VERSION_NUM
        var BufMap = {}, BufMap2 = {};
        var arr = this.GetActualNodes();
        var Count = 0, CountHot = 0, CountHotOK = 0, CountActualOK = 0, SumDeltaHot = 0, SumDeltaActual = 0, CountCP = 0, CountLH = 0,
        CountHash = 0, CountVer = 0, CountStop = 0;
        var CountAutoCorrectTime = 0;
        var SumAvgDeltaTime = 0;
        for(var i = 0; i < arr.length; i++)
        {
            var Node = arr[i];
            if(!Node || Node.IsAddrList)
                continue;
            var INFO = Node.INFO;
            if(!INFO)
                INFO = {}
            if(bHasCP && CHECK_POINT.BlockNum && INFO.CheckPointHashDB && CompareArr(CHECK_POINT.Hash, INFO.CheckPointHashDB) === 0)
            {
                CountCP++
            }
            if(INFO.LoadHistoryMode)
                CountLH++
            if(Node.StopGetBlock)
                CountStop++
            Count++
            SumAvgDeltaTime += Node.DeltaGlobTime
            if(Node.VersionNum >= MinVer)
                CountVer++
            if(INFO && INFO.BlockNumDB && INFO.BlockNumDB <= this.BlockNumDB)
            {
                var HashDB = ReadHashFromBufDB(BufMap2, INFO.BlockNumDB);
                if(HashDB && CompareArr(HashDB, INFO.HashDB) === 0)
                    CountHash++
            }
            var StrChk = GetCheckAccHash(BufMap, INFO.AccountBlockNum, INFO.AccountsHash);
            var Chck = 0;
            if(StrChk.indexOf("=OK=") >= 0)
            {
                Chck = 1
            }
            var DeltaTime = Node.DeltaTime;
            if(!DeltaTime)
                DeltaTime = 0
            CountActualOK += Chck
            SumDeltaActual += DeltaTime
            if(Node.Hot)
            {
                CountHot++
                CountHotOK += Chck
                SumDeltaHot += DeltaTime
            }
            if(INFO.AutoCorrectTime)
                CountAutoCorrectTime++
        }
        ADD_TO_STAT("MAX:ALL_NODES", CountAll)
        ADD_TO_STAT("MAX:CONNECTED_NODES", Count)
        ADD_TO_STAT("MAX:HOT_NODES", CountHot)
        ADD_TO_STAT("MAX:HOT_OK", CountHotOK)
        ADD_TO_STAT("MAX:ACTUAL_OK", CountActualOK)
        ADD_TO_STAT("MAX:CHECK_POINT_OK", CountCP)
        ADD_TO_STAT("MAX:COUNTLH", CountLH)
        ADD_TO_STAT("MAX:HASH_OK", CountHash)
        ADD_TO_STAT("MAX:MIN_VERSION", CountVer)
        ADD_TO_STAT("MAX:STOP_GET", CountStop)
        ADD_TO_STAT("MAX:AUTOCORRECT", CountAutoCorrectTime)
        ADD_TO_STAT("MAX:TIME_DELTA", DELTA_CURRENT_TIME)
        if(!Count)
            Count = 1
        if(!CountHot)
            CountHot = 1
        if(Count >= 20)
        {
            var SumDeltaAvg = 0;
            var AvgGlobTime = SumAvgDeltaTime / Count;
            for(var i = 0; i < arr.length; i++)
            {
                var Node = arr[i];
                if(!Node || Node.IsAddrList)
                    continue;
                var Delta = AvgGlobTime - Node.DeltaGlobTime;
                SumDeltaAvg += Delta * Delta
            }
            SumDeltaAvg = Math.sqrt(SumDeltaAvg / Count)
            ADD_TO_STAT("MAX:DELTA_GLOB_TIME", 100 + AvgGlobTime)
            ADD_TO_STAT("MAX:DISP_DELTA_GLOB_TIME", SumDeltaAvg)
            arr.sort(function (a,b)
            {
                return a.DeltaGlobTime - b.DeltaGlobTime;
            })
            var SumDeltaAvgM = 0;
            var AvgGlobTimeM = arr[Math.trunc(arr.length / 2)].DeltaGlobTime;
            var Length = arr.length;
            var Start = Math.trunc(Length * 0.05);
            var End = Math.trunc(Length * 0.95);
            var NodesCount = 0;
            for(var i = Start; i < End; i++)
            {
                var Node = arr[i];
                if(!Node || Node.IsAddrList)
                    continue;
                NodesCount++
                var Delta = AvgGlobTimeM - Node.DeltaGlobTime;
                SumDeltaAvgM += Delta * Delta
            }
            if(!NodesCount)
                NodesCount = 1
            SumDeltaAvgM = Math.sqrt(SumDeltaAvgM / NodesCount)
            ADD_TO_STAT("MAX:MEDIAN_GLOB_TIME", 100 + AvgGlobTimeM)
            ADD_TO_STAT("MAX:DISP_MEDIAN_GLOB_TIME", SumDeltaAvgM)
        }
        ADD_TO_STAT("MAX:DELTA_TIME_HOT", SumDeltaHot / CountHot)
        ADD_TO_STAT("MAX:DELTA_TIME_ACTUAL", SumDeltaActual / Count)
        ADD_TO_STAT("MAX:MEMORY_USAGE", process.memoryUsage().heapTotal / 1024 / 1024)
        ADD_TO_STAT("MAX:MEMORY_FREE", os.freemem() / 1024 / 1024)
        var SumUser = 0;
        var SumSys = 0;
        var SumIdle = 0;
        var cpus = os.cpus();
        for(var i = 0; i < cpus.length; i++)
        {
            var cpu = cpus[i];
            SumUser += cpu.times.user
            SumSys += cpu.times.sys + cpu.times.irq
            SumIdle += cpu.times.idle
        }
        if(GlSumUser !== undefined)
        {
            var maxsum = cpus.length * 1000;
            ADD_TO_STAT("MAX:CPU_USER_MODE", Math.min(maxsum, SumUser - GlSumUser))
            ADD_TO_STAT("MAX:CPU_SYS_MODE", Math.min(maxsum, SumSys - GlSumSys))
            ADD_TO_STAT("MAX:CPU_IDLE_MODE", Math.min(maxsum, SumIdle - GlSumIdle))
            ADD_TO_STAT("MAX:CPU", Math.min(maxsum, SumUser + SumSys - GlSumUser - GlSumSys))
        }
        GlSumUser = SumUser
        GlSumSys = SumSys
        GlSumIdle = SumIdle
    }
    GetNewMeta()
    {
        return crypto.randomBytes(32);
    }
};
class SMemBuffer
{
    constructor(MaxTime, CheckName)
    {
        this.MetaMap1 = {}
        this.MetaMap2 = {}
        this.CheckName = CheckName
        setInterval(this.ShiftMapDirect.bind(this), MaxTime)
    }
    GetStrKey(Arr)
    {
        if(typeof Arr === "number" || typeof Arr === "string")
        {
            return Arr;
        }
        else
        {
            return GetHexFromAddres(Arr);
        }
        throw "NOT RET!";
    }
    LoadValue(Arr, bStay)
    {
        if(!Arr)
            return undefined;
        var Key = this.GetStrKey(Arr);
        var Value = this.MetaMap1[Key];
        if(Value !== undefined)
        {
            if(!bStay)
                delete this.MetaMap1[Key]
            return Value;
        }
        Value = this.MetaMap2[Key]
        if(Value !== undefined)
        {
            if(!bStay)
                delete this.MetaMap2[Key]
        }
        return Value;
    }
    SaveValue(Arr, Value)
    {
        var Key = this.GetStrKey(Arr);
        if(Value !== undefined)
            this.MetaMap1[Key] = Value
    }
    ShiftMapDirect()
    {
        if(glStopNode)
            return ;
        if(this.CheckName)
        {
            var Count = 0;
            for(var key in this.MetaMap2)
            {
                Count++
            }
            if(Count)
            {
                ADD_TO_STAT(this.CheckName, 1, 1)
            }
        }
        this.MetaMap2 = this.MetaMap1
        this.MetaMap1 = {}
    }
    Clear()
    {
        this.MetaMap2 = {}
        this.MetaMap1 = {}
    }
};
class STreeBuffer
{
    constructor(MaxTime, CompareFunction, KeyType, CheckName)
    {
        this.KeyType = KeyType
        this.MetaTree1 = new RBTree(CompareFunction)
        this.MetaTree2 = new RBTree(CompareFunction)
        this.CheckName = CheckName
        setInterval(this.ShiftMapDirect.bind(this), MaxTime)
    }
    LoadValue(Hash, bStay)
    {
        if(!Hash)
            return undefined;
        if(typeof Hash !== this.KeyType)
            throw "MUST ONLY HASH ARRAY: " + Hash;
        var element = this.MetaTree1.find({hash:Hash});
        if(element)
        {
            if(!bStay)
                this.MetaTree1.remove(element)
            return element.value;
        }
        element = this.MetaTree2.find({hash:Hash})
        if(element)
        {
            if(!bStay)
                this.MetaTree2.remove(element)
            return element.value;
        }
        return undefined;
    }
    SaveValue(Hash, Value)
    {
        if(typeof Hash !== this.KeyType)
            throw "MUST ONLY TYPE=" + this.KeyType + " in " + Hash;
        if(Value !== undefined)
        {
            var element = this.MetaTree1.find({hash:Hash});
            if(element)
                element.value = Value
            else
                this.MetaTree1.insert({hash:Hash, value:Value})
        }
    }
    ShiftMapDirect()
    {
        if(this.CheckName && this.MetaTree2.size)
        {
            ADD_TO_STAT(this.CheckName, this.MetaTree2.size, 1)
            var it = this.MetaTree2.iterator(), Item;
            while((Item = it.next()) !== null)
            {
                var Name = Item.value.Name;
                ADD_TO_STAT(this.CheckName + ":" + Name, 1, 1)
            }
        }
        this.MetaTree2.clear()
        var empty_tree = this.MetaTree2;
        this.MetaTree2 = this.MetaTree1
        this.MetaTree1 = empty_tree
    }
    Clear()
    {
        this.MetaTree1.clear()
        this.MetaTree2.clear()
    }
};

function ReadHashFromBufDB(Map,BlockNum)
{
    var MyHash = Map[BlockNum];
    if(!MyHash)
    {
        var Block = SERVER.ReadBlockHeaderDB(BlockNum);
        if(Block)
            MyHash = Block.Hash;
        else
            MyHash = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        Map[BlockNum] = MyHash;
    }
    return MyHash;
};

function GetCheckAccHash(Map,BlockNum,Hash)
{
    var MyHash = Map[BlockNum];
    if(!MyHash)
    {
        MyHash = DApps.Accounts.GetHashOrUndefined(BlockNum);
        Map[BlockNum] = MyHash;
    }
    if(MyHash)
    {
        if(!Hash)
            return "=ERR:NO=";
        if(CompareArr(Hash, MyHash) !== 0)
            return "=ERR:BAD=";
        else
            return "=OK=";
    }
    else
    {
        if(!Hash)
            return "=OK=:NO";
        else
            return "=MY:NO=";
    }
};
global.GetCheckAccHash = GetCheckAccHash;
global.ReadHashFromBufDB = ReadHashFromBufDB;
global.STreeBuffer = STreeBuffer;
global.TestCreateTr = TestCreateTr;

function TestCreateTr()
{
    const FORMAT_CREATE = "{\
        Type:byte,\
        Currency:uint,\
        PubKey:arr33,\
        Description:str40,\
        Adviser:uint,\
        Reserve:arr7,\
        POWCreate:arr12,\
        }";
    var TR = {Type:100, Currency:0, PubKey:[2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0], Description:"Description", Adviser:10, };
    var Body = BufLib.GetBufferFromObject(TR, FORMAT_CREATE, 1000, {});
    var startTime = process.hrtime();
    var StartData = Date.now();
    var nonce = CreateHashBodyPOWInnerMinPower(Body, 1000, 17);
    var Time = process.hrtime(startTime);
    var power = GetPowPower(shaarr(Body));
    var deltaTime = (Time[0] * 1000 + Time[1] / 1e6) / 1000;
    var DeltaData = (new Date() - StartData) / 1000;
    ToLog("power=" + power + "  nonce=" + nonce + " TIME=" + deltaTime + " sec" + "  DeltaData=" + DeltaData + " sec");
    return {time1:deltaTime, time2:DeltaData};
};

function CreateHashBody(body,Num,Nonce)
{
    body.writeUIntLE(Num, body.length - 12, 6);
    body.writeUIntLE(Nonce, body.length - 6, 6);
    return shaarr(body);
};

function CreateHashBodyPOWInnerMinPower(arr,BlockNum,MinPow)
{
    var nonce = 0;
    while(1)
    {
        var arrhash = CreateHashBody(arr, BlockNum, nonce);
        var power = GetPowPower(arrhash);
        if(power >= MinPow)
        {
            return nonce;
        }
        nonce++;
    }
};
