--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.

local socket = require ("socket")
local json = require ("dkjson")
local qsutils = {}

os.execute("mkdir \""..script_path.."\\logs\"")

--- Sleep that always works
function delay(msec)
    if sleep then
        pcall(sleep, msec)
    else
        -- pcall(socket.select, nil, nil, msec / 1000)
    end
end

-- high precision current time
function timemsec()
    local st, res = pcall(socket.gettime)
    if st then
        return (res) * 1000
    else
        log("unexpected error in timemsec", 3)
        error("unexpected error in timemsec")
    end
end

-- Returns the name of the file that calls this function (without extension)
function scriptFilename()
    -- Check that Lua runtime was built with debug information enabled
    if not debug or not debug.getinfo then
        return nil
    end
    local full_path = debug.getinfo(2, "S").source:sub(2)
    return string.gsub(full_path, "^.*\\(.*)[.]lua[c]?$", "%1")
end

-- when true will show QUIK message for log(...,0)
is_debug = false

-- log files

-- ������� log (��������� ����������)
function log(msg, level)
    if not msg then msg = "" end
    if not level then level = 0 end
    
    local logLine = "LOG " .. level .. ": " .. msg
    print(logLine)  -- ��� �������, ���� �����
    
    -- ����� � QUIK message ������ ��� ������ ������� ��� debug
    if (level == 1 or level == 2 or level == 3 or is_debug) and message then
        pcall(message, msg, level)
    end
    
    -- ���� � ����� (��� � ���������)
    local log_path = script_path .. "\\logs\\QUIK#_" .. os.date("%Y%m%d") .. ".log"
    
    -- ��������� ������ ������ ���
    local lf = io.open(log_path, "a")
    if not lf then
        -- Retry ���� ���, �� ������ ��������� ����������
        lf = io.open(log_path, "a")
        if not lf then
            -- Fallback: ������ � QUIK message, ���� ��������
            if message then
                pcall(message, "������ �����������: �� ������� ������� ���� " .. log_path, 3)
            end
            return  -- �������, ����� �� �������
        end
    end
    
    -- ������ � timestamp
    local msecs = math.floor(math.fmod(timemsec(), 1000))  -- timemsec() �� QUIK
    pcall(lf.write, lf, os.date("%Y-%m-%d %H:%M:%S") .. "." .. msecs .. " " .. logLine .. "\n")
    pcall(lf.flush, lf)
    pcall(lf.close, lf)  -- ��������� �����
end

--function openLog()
--    os.execute("mkdir \""..script_path.."\\logs\"")
--    local lf = io.open (script_path.. "\\logs\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
--    if not lf then
--        lf = io.open (script_path.. "\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
--    end
--    return lf
--end

-- Returns contents of config.json file or nil if no such file exists
function readConfigAsJson()
    local conf = io.open (script_path.. "\\config.json", "r")
    if not conf then
        return nil
    end
    local content = conf:read "*a"
    conf:close()
    return from_json(content)
end

function paramsFromConfig(scriptName)
    local params = {}
    -- just default values
    table.insert(params, "127.0.0.1") -- responseHostname
    table.insert(params, 34130)       -- responsePort
    table.insert(params, "127.0.0.1") -- callbackHostname
    table.insert(params, 34131)       -- callbackPort

    local config = readConfigAsJson()
    if not config or not config.servers then
        return nil
    end
    local found = false
    for i=1,#config.servers do
        local server = config.servers[i]
        if server.scriptName == scriptName then
            found = true
            if server.responseHostname then
                params[1] = server.responseHostname
            end
            if server.responsePort then
                params[2] = server.responsePort
            end
            if server.callbackHostname then
                params[3] = server.callbackHostname
            end
            if server.callbackPort then
                params[4] = server.callbackPort
            end
        end
    end

    if found then
        return params
    else
        return nil
    end
end


---- closes log
--function closeLog()
--    if logfile then
--        pcall(logfile:close(logfile))
--    end
--end
--
--logfile = openLog()
--
----- Write to log file and to Quik messages
--function log(msg, level)
--    if not msg then msg = "" end
--    if level == 1 or level == 2 or level == 3 or is_debug then
--        -- only warnings and recoverable errors to Quik
--        if message then
--            pcall(message, msg, level)
--        end
--    end
--    if not level then level = 0 end
--    local logLine = "LOG "..level..": "..msg
--    print(logLine)
--    local msecs = math.floor(math.fmod(timemsec(), 1000));
--    if logfile then
--        pcall(logfile.write, logfile, os.date("%Y-%m-%d %H:%M:%S").."."..msecs.." "..logLine.."\n")
--        pcall(logfile.flush, logfile)
--        pcall(logfile.close, logfile)
--    end
--end


-- Doesn't work if string contains empty values, eg. 'foo,,bar'. You get {'foo','bar'} instead of {'foo', '', 'bar'}
function split(inputstr, sep)
    if sep == nil then
        sep = "%s"
    end
    local t={}
    local i=1
    for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
        t[i] = str
        i = i + 1
    end
    return t
end

-- https://stackoverflow.com/questions/1426954/split-string-in-lua#comment73602874_7615129
function split2(inputstr, sep)
    sep = sep or '%s'
    local t = {}
    for field, s in string.gmatch(inputstr, "([^"..sep.."]*)("..sep.."?)") do
        table.insert(t, field)
        if s == "" then
            return t
        end
    end
end

function from_json(str)
    local status, msg= pcall(json.decode, str, 1, json.null) -- dkjson
    if status then
        return msg
    else
        return nil, msg
    end
end

function to_json(msg)
    local status, str= pcall(json.encode, msg, { indent = false }) -- dkjson
    if status then
        return str
    else
        error(str)
    end
end

-- current connection state
is_connected = false
local response_server
local callback_server
local response_client
local callback_client

--- accept client on server
local function getResponseServer()
    log('Waiting for a response client...', 0)
	if not response_server then
		log("Cannot bind to response_server, probably the port is already in use", 3)
	else
		while true do
			local status, client, err = pcall(response_server.accept, response_server )
			if status and client then
				return client
			else
				log(err, 3)
			end
		end
	end
end

local function getCallbackClient()
    log('Waiting for a callback client...', 0)
	if not callback_server then
		log("Cannot bind to callback_server, probably the port is already in use", 3)
	else
		while true do
			local status, client, err = pcall(callback_server.accept, callback_server)
			if status and client then
				return client
			else
				log(err, 3)
			end
		end
	end
end

function qsutils.connect(response_host, response_port, callback_host, callback_port)
    if not response_server then
        response_server = socket.bind(response_host, response_port, 1)
    end
    if not callback_server then
        callback_server = socket.bind(callback_host, callback_port, 1)
    end

    if not is_connected then
        log('QUIK# is waiting for client connection...', 1)
        if response_client then
            log("is_connected is false but the response client is not nil", 3)
            -- Quik crashes without pcall
            pcall(response_client.close, response_client)
        end
        if callback_client then
            log("is_connected is false but the callback client is not nil", 3)
            -- Quik crashes without pcall
            pcall(callback_client.close, callback_client)
        end
        response_client = getResponseServer()
        callback_client = getCallbackClient()
        if response_client and callback_client then
            is_connected = true
            log('QUIK# client connected', 1)
        end
    end
end

local function disconnected()
    is_connected = false
    log('QUIK# client disconnected', 1)
    if response_client then
        pcall(response_client.close, response_client)
        response_client = nil
    end
    if callback_client then
        pcall(callback_client.close, callback_client)
        callback_client = nil
    end
    OnQuikSharpDisconnected()
end

--- get a decoded message as a table
function receiveRequest()
    if not is_connected then
        return nil, "not conencted"
    end
    local status, requestString= pcall(response_client.receive, response_client)
    if status and requestString then
        local msg_table, err = from_json(requestString)
        if err then
            log(err, 3)
            return nil, err
        else
            return msg_table
        end
    else
        disconnected()
        return nil, err
    end
end

function sendResponse(msg_table)
    -- if not set explicitly then set CreatedTime "t" property here
    -- if not msg_table.t then msg_table.t = timemsec() end
    local responseString = to_json(msg_table)
    if is_connected then
        local status, res = pcall(response_client.send, response_client, responseString..'\n')
        if status and res then
            return true
        else
            disconnected()
            return nil, err
        end
    end
end

local counter = 0

function sendCallback(msg_table)
    -- if not set explicitly then set CreatedTime "t" property here
    -- if not msg_table.t then msg_table.t = timemsec() end
    local callback_string = to_json(msg_table)
    if is_connected then
        if math.fmod(counter, 1000) == 0 then
            collectgarbage()
            counter = 0
        end
        counter = counter + 1

        local status, res = pcall(callback_client.send, callback_client, callback_string..'\n')
        if status and res then
            return true
        else
            disconnected()
            return nil, err
        end
    end
end

return qsutils
