local net = require("net")
local cmd = require("multi.integration.networkManager.cmds")
local multi,thread = require("multi"):init()
local node = {}
node.__index = node
local rand = {}
for i = 65,90 do
    rand[#rand+1] = string.char(i)
end
local function randName(n)
    local str = {}
    for i=1,(n or 10) do
        str[#str+1] = rand[math.random(1,#rand)]
    end
    return table.concat(str)
end
local getNames = thread:newFunction(function(names)
    local listen = socket.udp() -- make a new socket
	listen:setsockname(net.getLocalIP(), 11111)
	listen:settimeout(0)
    local data, ip, port = listen:receivefrom()
    thread.holdWithin(1,function()
        if data then
            local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)")
            if n then
                names[n]=true
            end
        end
    end)
    return multi.NIL
end)
local function setName(ref,name)
    if name then
        ref.name = "NODE_"..name
        ref.connection:broadcast(name)
        return
    end
    local names = {}
    getNames(names).wait() -- Prevents duplicate names from spawning!
    local name = randName()
    while names["NODE_"..name] do
        name = randName()
    end
    ref.name = "NODE_"..name
    ref.connection:broadcast(ref.name)
end
node.ServerCode = require("multi.integration.networkManager.serverSide")
node.ClientCode = require("multi.integration.networkManager.clientSide")
function node.random()
    return randName(12)
end
function node:registerWithManager(ip,port)
    if self.type ~= "server" then return end
    if not self.manager then
        self.manager = net:newTCPClient(ip,port)
        if not self.manager then
            error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!")
        end
    end
    thread:newFunction(function()
        thread.hold(function() return self.name end)
        self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port)
    end)()
end
function node:new(host,port,name)
    local c = {}
    c.links = {}
    setmetatable(c,node)
    if type(host)=="number" or type(host)=="nil" then
        c.connection = net:newTCPServer(host or cmd.defaultPort)
        c.connection:enableBinaryMode()
        c.type = "server"
        c.connection.node = c
        c.connection.OnDataRecieved(self.ServerCode)
        setName(c)
    elseif type(host)=="table" and host.Type == "tcp" then
        c.connection = host
        c.connection:enableBinaryMode()
        c.type = "client"
        c.connection.node = c
        c.connection.OnDataRecieved(self.ClientCode)
        c.name = "MASTER_NODE"
    elseif type(host) == "string" and type(port)=="number" then
        c.connection = net:newTCPClient(host, port)
        c.connection:enableBinaryMode()
        c.type = "client"
        c.connection.node = c
        c.connection.OnDataRecieved(self.ClientCode)
        c.name = "MASTER_NODE"
    else
        error("Invalid arguments!")
    end
    return c
end
function node:ping()
    if self.type ~= "client" then return end
    self:send("!PING!")
    return {pong=self.connection.OnDataRecieved}
end
function node:send(data)
    if self.type ~= "client" then return end
    self.connection:send(data)
end
return node