/*
 * @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";
var fs = require("fs");
const FORMAT_EVAL_SEND = "{MaxBlockNum:uint,Code:str,Sign:arr64}";
module.exports = class CCode extends require("./base")
{
    constructor(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
    {
        super(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
        if(!global.ADDRLIST_MODE && !this.VirtualMode)
        {
            setInterval(this.CheckLoadCodeTime.bind(this), 10 * 1000)
        }
        this.LastEvalCodeNum = 0
        CheckCreateDir(GetDataPath("Update"))
    }
    CheckLoadCodeTime()
    {
        if(START_LOAD_CODE.StartLoadNode && START_LOAD_CODE.StartLoadVersionNum)
        {
            var Delta = new Date() - START_LOAD_CODE.StartLoadVersionNumTime;
            if(Delta > 20 * 1000)
            {
                ToError("Cannot load code version:" + START_LOAD_CODE.StartLoadVersionNum + " from node: " + START_LOAD_CODE.StartLoadNode.ip + ":" + START_LOAD_CODE.StartLoadNode.port)
                this.ClearLoadCode()
            }
        }
    }
    ClearLoadCode()
    {
        START_LOAD_CODE.StartLoad = undefined
        START_LOAD_CODE.StartLoadVersionNum = 0
        START_LOAD_CODE.StartLoadVersionNumTime = 0
    }
    StartLoadCode(Node, CodeVersion)
    {
        var VersionNum = CodeVersion.VersionNum;
        START_LOAD_CODE.StartLoad = CodeVersion
        START_LOAD_CODE.StartLoadNode = Node
        START_LOAD_CODE.StartLoadVersionNum = VersionNum
        START_LOAD_CODE.StartLoadVersionNumTime = new Date()
        var fname = GetDataPath("Update/wallet-" + VersionNum + ".zip");
        if(fs.existsSync(fname))
        {
            this.UseCode(VersionNum, false)
            return ;
        }
        var Context = {"VersionNum":VersionNum};
        this.SendF(Node, {"Method":"GETCODE", "Context":Context, "Data":VersionNum})
    }
    static
    GETCODE_F()
    {
        return "uint";
    }
    RETCODE(Info)
    {
        var VersionNum = Info.Context.VersionNum;
        if(!VersionNum || !START_LOAD_CODE.StartLoad)
            return ;
        var fname = GetDataPath("Update/wallet-" + VersionNum + ".zip");
        if(!fs.existsSync(fname))
        {
            var Hash = shaarr(Info.Data);
            if(CompareArr(Hash, START_LOAD_CODE.StartLoad.Hash) === 0)
            {
                var file_handle = fs.openSync(fname, "w");
                fs.writeSync(file_handle, Info.Data, 0, Info.Data.length)
                fs.closeSync(file_handle)
                this.UseCode(VersionNum, global.USE_AUTO_UPDATE)
            }
            else
            {
                ToError("Error check hash of version code :" + START_LOAD_CODE.StartLoadVersionNum + " from node: " + Info.Node.ip + ":" + Info.Node.port)
                this.ClearLoadCode()
                this.AddCheckErrCount(Info.Node, 1, "Error check hash of version code")
            }
        }
    }
    UseCode(VersionNum, bUpdate)
    {
        if(bUpdate)
        {
            UpdateCodeFiles(VersionNum)
        }
        if(global.START_LOAD_CODE.StartLoad)
        {
            global.CODE_VERSION = START_LOAD_CODE.StartLoad
            this.ClearLoadCode()
        }
    }
    SetNewCodeVersion(Data, PrivateKey)
    {
        var fname = GetDataPath("ToUpdate/wallet.zip");
        if(fs.existsSync(fname))
        {
            var fname2 = GetDataPath("Update/wallet-" + Data.VersionNum + ".zip");
            if(fs.existsSync(fname2))
            {
                fs.unlinkSync(fname2)
            }
            var data = fs.readFileSync(fname);
            var Hash = shaarr(data);
            var file_handle = fs.openSync(fname2, "w");
            fs.writeSync(file_handle, data, 0, data.length)
            fs.closeSync(file_handle)
            var SignArr = arr2(Hash, GetArrFromValue(Data.VersionNum));
            var Sign = secp256k1.sign(shabuf(SignArr), PrivateKey).signature;
            global.CODE_VERSION = Data
            global.CODE_VERSION.Hash = Hash
            global.CODE_VERSION.Sign = Sign
            return "OK Set new code version=" + Data.VersionNum;
        }
        else
        {
            return "File not exist: " + fname;
        }
    }
};

function UpdateCodeFiles(StartNum)
{
    var fname = GetDataPath("Update");
    if(!fs.existsSync(fname))
        return 0;
    var arr = fs.readdirSync(fname);
    var arr2 = [];
    for(var i = 0; i < arr.length; i++)
    {
        if(arr[i].substr(0, 7) === "wallet-")
        {
            arr2.push(parseInt(arr[i].substr(7)));
        }
    }
    arr2.sort(function (a,b)
    {
        return a - b;
    });
    for(var i = 0; i < arr2.length; i++)
    {
        var Num = arr2[i];
        var Name = "wallet-" + Num + ".zip";
        var Path = fname + "/" + Name;
        ToLog("Check file:" + Name);
        if(fs.existsSync(Path))
        {
            if(StartNum === Num)
            {
                ToLog("UnpackCodeFile:" + Name);
                UnpackCodeFile(Path);
                if(StartNum % 2 === 0)
                {
                    global.RestartNode(1);
                }
                else
                {
                }
                return 1;
            }
            else
            {
                ToLog("Delete old file update:" + Name);
                fs.unlinkSync(Path);
            }
        }
    }
    return 0;
};
global.UnpackCodeFile = UnpackCodeFile;

function UnpackCodeFile(fname)
{
    var data = fs.readFileSync(fname);
    var reader = ZIP.Reader(data);
    reader.forEach(function (entry)
    {
        var Name = entry.getName();
        var Path = GetCodePath(Name);
        if(entry.isFile())
        {
            var buf = entry.getData();
            CheckCreateDir(Path, true, true);
            var file_handle = fs.openSync(Path, "w");
            fs.writeSync(file_handle, buf, 0, buf.length);
            fs.closeSync(file_handle);
        }
        else
        {
        }
    });
    reader.close();
};
global.RestartNode = function RestartNode(bForce)
{
    global.NeedRestart = 1;
    setTimeout(DoExit, 5000);
    if(global.nw || global.NWMODE)
    {
    }
    else
    {
        StopChildProcess();
        ToLog("********************************** FORCE RESTART!!!");
        return ;
    }
    if(this.ActualNodes)
    {
        var it = this.ActualNodes.iterator(), Node;
        while((Node = it.next()) !== null)
        {
            if(Node.Socket)
                CloseSocket(Node.Socket, "Restart");
        }
    }
    this.StopServer();
    this.StopNode();
    StopChildProcess();
    ToLog("****************************************** RESTART!!!");
    ToLog("EXIT 1");
};

function DoExit()
{
    ToLog("EXIT 2");
    if(global.nw || global.NWMODE)
    {
        ToLog("RESTART NW");
        var StrRun = '"' + process.argv[0] + '" .\n';
        StrRun += '"' + process.argv[0] + '" .\n';
        SaveToFile("run-next.bat", StrRun);
        const child_process = require('child_process');
        child_process.exec("run-next.bat", {shell:true});
    }
    ToLog("EXIT 3");
    process.exit(0);
};
