/*
 * @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
*/

global.PROCESS_NAME = "TX";
const crypto = require('crypto');
const fs = require('fs');
require("../core/constant");
global.DATA_PATH = GetNormalPathString(global.DATA_PATH);
global.CODE_PATH = GetNormalPathString(global.CODE_PATH);
require("../core/library");
global.READ_ONLY_DB = 0;
var LastAlive = Date.now();
setTimeout(function ()
{
    setInterval(CheckAlive, 1000);
}, 20000);
setInterval(function ()
{
    process.send({cmd:"Alive"});
}, 1000);
setInterval(PrepareStatEverySecond, 1000);
process.send({cmd:"online", message:"OK"});
global.ToLogClient = function (Str,StrKey,bFinal)
{
    process.send({cmd:"ToLogClient", Str:"" + Str, StrKey:StrKey, bFinal:bFinal});
};
process.on('message', function (msg)
{
    LastAlive = Date.now();
    switch(msg.cmd)
    {
        case "ALive":
            break;
        case "Exit":
            process.exit(0);
            break;
        case "call":
            var Err = 0;
            var Ret;
            try
            {
                Ret = global[msg.Name](msg.Params);
            }
            catch(e)
            {
                Err = 1;
                Ret = "" + e;
            }
            if(msg.id)
                process.send({cmd:"retcall", id:msg.id, Err:Err, Params:Ret});
            break;
        case "FindTX":
            global.TreeFindTX.SaveValue(msg.TX, msg);
            break;
        case "SetSmartEvent":
            global.TreeFindTX.SaveValue("Smart:" + msg.Smart, 1);
            break;
        case "RewriteAllTransactions":
            RewriteAllTransactions(msg);
            break;
        case "ReWriteDAppTransactions":
            ReWriteDAppTransactions(msg);
            break;
        case "Eval":
            EvalCode(msg.Code);
            break;
        default:
            break;
    }
});
global.SetStatMode = function (Val)
{
    global.STAT_MODE = Val;
    return global.STAT_MODE;
};

function CheckAlive()
{
    if(global.NOALIVE)
        return ;
    var Delta = Date.now() - LastAlive;
    if(Delta > 100 * 1000)
    {
        ToLog("TX-PROCESS: ALIVE TIMEOUT Stop and exit: " + Delta + "/" + global.CHECK_STOP_CHILD_PROCESS);
        process.exit(0);
        return ;
    }
};
process.on('uncaughtException', function (err)
{
    ToError(err.stack);
    ToLog(err.stack);
    TO_ERROR_LOG("TX-PROCESS", 777, err);
    ToLog("-----------------TX-PROCESS EXIT------------------");
    process.exit();
});
process.on('error', function (err)
{
    ToError("TX-PROCESS:\n" + err.stack);
    ToLog(err.stack);
});
global.HTTP_PORT_NUMBER = 0;
var CServerDB = require("../core/transaction-validator");
var KeyPair = crypto.createECDH('secp256k1');
KeyPair.setPrivateKey(Buffer.from([77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77]));
global.SERVER = new CServerDB(KeyPair, undefined, undefined, false, true);
global.TreeFindTX = new STreeBuffer(30 * 1000, CompareItemHashSimple, "string");
setInterval(function ()
{
    if(SERVER)
        SERVER.ClearBufMap();
    global.BlockDB.CloseDBFile("block-header");
    global.BlockDB.CloseDBFile("block-body");
    DoTXProcess();
}, 10);
var BlockTree = new STreeBuffer(30 * 1000, CompareItemHashSimple, "number");
global.bShowDetail = 0;
var LastBlockNum = undefined;

function DoTXProcess()
{
    if(LastBlockNum === undefined)
        InitTXProcess();
    var BlockMin = FindMinimal();
    if(!BlockMin)
    {
        return ;
    }
    var StartTime = Date.now();
    var CountTX = 0;
    for(var Num = BlockMin.BlockNum; Num < BlockMin.BlockNum + 200; Num++)
    {
        var EndTime = Date.now();
        var Delta = EndTime - StartTime;
        if(Delta >= 1000)
            break;
        var Block = SERVER.ReadBlockDB(Num);
        if(!Block)
        {
            break;
        }
        if(!IsValidSumHash(Block))
        {
            break;
        }
        var Item = BlockTree.LoadValue(Block.BlockNum, 1);
        if(Item && CompareArr(Item.SumHash, Block.SumHash) === 0)
        {
            continue;
        }
        SERVER.BlockProcessTX(Block);
        if(Num % 100000 === 0)
            ToLog("CALC: " + Num);
        CountTX++;
        if(bShowDetail)
            ToLog("    CALC: " + Num + " SumHash: " + GetHexFromArr(Block.SumHash).substr(0, 12));
        BlockTree.SaveValue(Block.BlockNum, {BlockNum:Block.BlockNum, SumHash:Block.SumHash});
        LastBlockNum = Block.BlockNum;
    }
};

function FindMinimal()
{
    var MaxNumBlockDB = SERVER.GetMaxNumBlockDB();
    if(MaxNumBlockDB && MaxNumBlockDB < LastBlockNum)
    {
        LastBlockNum = MaxNumBlockDB - 1;
        BlockTree.Clear();
    }
    for(var Num = LastBlockNum; Num--; Num > 0)
    {
        var Block = SERVER.ReadBlockHeaderDB(Num);
        if(!Block)
        {
            continue;
        }
        if(!IsValidSumHash(Block))
        {
            continue;
        }
        if(Block.BlockNum % PERIOD_ACCOUNT_HASH === 0)
        {
            var Item = DApps.Accounts.GetAccountHashItem(Block.BlockNum);
            if(Item)
            {
                BlockTree.SaveValue(Block.BlockNum, Item);
            }
        }
        var Item = BlockTree.LoadValue(Block.BlockNum, 1);
        if(Item && CompareArr(Item.SumHash, Block.SumHash) === 0)
            return Block;
    }
    RewriteAllTransactions();
    Block = SERVER.ReadBlockHeaderDB(0);
    return Block;
};

function IsValidSumHash(Block)
{
    if(Block.BlockNum < 16)
        return 1;
    if(IsZeroArr(Block.SumHash))
        return 0;
    var PrevBlock = SERVER.ReadBlockHeaderDB(Block.BlockNum - 1);
    if(!PrevBlock)
        return 0;
    var SumHash2 = shaarr2(PrevBlock.SumHash, Block.Hash);
    if(CompareArr(SumHash2, Block.SumHash) === 0)
        return 1;
    return 0;
};

function InitTXProcess()
{
    var StateTX = DApps.Accounts.DBStateTX.Read(0);
    if(!StateTX)
    {
        LastBlockNum = 0;
        var MaxNum = DApps.Accounts.DBAccountsHash.GetMaxNum();
        if(MaxNum > 0)
        {
            var Item = DApps.Accounts.DBAccountsHash.Read(MaxNum);
            if(Item)
            {
                LastBlockNum = Item.BlockNum;
            }
        }
        ToLog("DETECT NEW VER on BlockNum=" + LastBlockNum);
        DApps.Accounts.DBStateTX.Write({Num:0, BlockNum:LastBlockNum});
    }
    StateTX = DApps.Accounts.DBStateTX.Read(0);
    LastBlockNum = StateTX.BlockNum;
    LastBlockNum = PERIOD_ACCOUNT_HASH * Math.trunc(LastBlockNum / PERIOD_ACCOUNT_HASH);
    if(LastBlockNum > 100)
    {
        LastBlockNum = 1 + LastBlockNum - 100;
    }
    if(LastBlockNum <= 0)
        RewriteAllTransactions();
    else
        ToLog("Start NUM = " + LastBlockNum);
    DApps.Accounts.CalcMerkleTree();
};

function RewriteAllTransactions()
{
    ToLog("*************RewriteAllTransactions");
    for(var key in DApps)
    {
        DApps[key].ClearDataBase();
    }
    LastBlockNum = 0;
    BlockTree.Clear();
    ToLog("Start num = " + LastBlockNum);
};

function ReWriteDAppTransactions(msg)
{
    var StartNum = msg.StartNum;
    var EndNum = msg.EndNum;
    ToLog("ReWriteDAppTransactions: " + StartNum + " - " + EndNum);
    BlockTree.Clear();
    if(StartNum < LastBlockNum)
        LastBlockNum = StartNum;
    ToLog("Start num = " + LastBlockNum);
};
global.EvalCode = function (Code)
{
    var Result;
    try
    {
        var ret = eval(Code);
        Result = JSON.stringify(ret, "", 4);
    }
    catch(e)
    {
        Result = "" + e;
    }
    return Result;
};
