local skynet = require "skynet"
local log_define = require "zlog.log_define"
local queue = require "skynet.queue"
require "skynet.manager"

local LOG_LEVEL = log_define.LOG_LEVEL
local DEFAULT_CATEGORY = log_define.DEFAULT_CATEGORY
local log_format = log_define.format
local color = log_define.color
local string_match = string.match

local skynet_env = require("skynet.env")
local nodeid = skynet_env.get("nodeid")

local zenv = require("zenv.init")
local define = zenv.Logger

local log_root = define.log_root
local log_level = define.log_level or LOG_LEVEL.INFO
local log_console = define.log_console

local name = zenv.get_node_conf(nodeid).name
local log_prefix = name .. "_"

local last_day = -1
local category = ...
local is_master = not category
local category_addr = {}
local lock = queue()
local file

local function close_file()
    if not file then
        return
    end
    file:close()
    file = nil
end

local function open_file(date)
    date = date or os.date("*t")

    local dir = log_define.log_dir(log_root, date)
    if not os.rename(dir, dir) then
        os.execute("mkdir -p " .. dir)
    end

    if file then
        close_file()
    end

    local path = log_define.log_path(dir, log_prefix, category, date)
    local f, e = io.open(path, "a")
    if not f then
        print("logger error:", tostring(e))
        return
    end

    file = f
    last_day = date.day
end

local CMD = {}

function CMD.console(level, msg)
    print(color(level, msg))
end

function CMD.log(level, msg)
    if level < log_level then
        return
    end

    msg = msg or ""
    local date = os.date("*t")
    if not file or date.day ~= last_day then
        open_file(date)
    end

    file:write(msg .. '\n')
    file:flush()

    if log_console then
        if is_master then
            CMD.console(level, msg)
        else
            skynet.call(".logger", "lua", "console", level, msg)
        end
    end
end

function CMD.set_console(is_open)
    log_console = is_open
    if is_master then
        for _, addr in pairs(category_addr) do
            skynet.call(addr, "lua", "set_console", is_open)
        end
    end
end

function CMD.set_level(level)
    log_level = level
    if is_master then
        for _, addr in pairs(category_addr) do
            skynet.call(addr, "lua", "set_level", level)
        end
    end
end

function CMD.get_service(cate)
    if not is_master then
        return
    end

    local addr
    lock(function()
        addr = category_addr[cate]
        if not addr then
            addr = skynet.newservice("logger", cate)
            category_addr[cate] = addr
        end
    end)
    return addr
end

if is_master then
    skynet.info_func(function()
        return {
            log_console = log_console,
            log_level = log_level,
        }
    end)

    skynet.register_protocol {
        name = "text",
        id = skynet.PTYPE_TEXT,
        unpack = skynet.tostring,
        dispatch = function(_, addr, msg)
            local level = LOG_LEVEL.INFO
            if string_match(msg, "maybe in an endless loop") then
                level = LOG_LEVEL.WARN
            end
            if string_match(msg, "stack traceback:") then
                level = LOG_LEVEL.ERROR
            end
            msg = log_format(addr, level, nil, msg)
            CMD.log(level, msg)
        end,
    }

    local SIGHUP_CMD = {}
    local function get_first_line(filename)
        local f = io.open(filename, "r")
        if not f then
            return
        end

        local first_line = f:read("l")
        f:close()
        return first_line
    end

    local function get_sighup_cmd(sighup_file)
        local cmd = get_first_line(sighup_file)
        if not cmd then
            return
        end
        return SIGHUP_CMD[cmd]
    end

    local sighup_file = "./.sighup_file"

    -- 捕捉sighup信号(kill -1)
    skynet.register_protocol {
        name = "SYSTEM",
        id = skynet.PTYPE_SYSTEM,
        unpack = function(...)
            return ...
        end,
        dispatch = function(_, addr)
            -- https://blog.hanxi.cc/p/75/
            local cmd = get_sighup_cmd(sighup_file)
            if cmd then
                cmd = cmd:match("^%s*(.-)%s*$")
                local func = SIGHUP_CMD[cmd]
                if func then
                    func()
                else
                    skynet.error(
                        string.format("unknow sighup cmd, need set sighup file. sighup_file: '%s'", sighup_file))
                end
            else
                local level = LOG_LEVEL.FATAL
                local msg = log_format(addr, level, nil, "SIGHUP")
                CMD.log(level, msg)
            end
        end,
    }

    category_addr[DEFAULT_CATEGORY] = skynet.self()
    skynet.register(".logger")
end

open_file()

skynet.start(function()
    skynet.dispatch("lua", function(_, _, cmd, ...)
        local f = CMD[cmd]
        assert(f, cmd)
        return skynet.retpack(f(...))
    end)
end)
