local host='127.0.0.1'
local port='80'
local path=''

-- lua + LÖVE threading websocket client
-- Original pure lua ver. by flaribbit and Particle_G
-- Threading version by MrZ

local type=type
local timer=love.timer.getTime

local WS={}
local wsList=setmetatable({},{
    __index=function(l,k)
        local ws={
            real=false,
            status='dead',
            lastPongTime=timer(),
            sendTimer=0,
            alertTimer=0,
            pongTimer=0,
        }
        l[k]=ws
        return ws
    end
})

function WS.switchHost(_1,_2,_3)
    for k in next,wsList do
        WS.close(k)
    end
    host=_1
    port=_2 or port
    path=_3 or path
end

function WS.connect(name,subPath,head,timeout)
    if head then
        local l=""
        for k,v in next,head do
            l=l..(k..": "..v..'\r\n')
        end
        head=l
    else
        head=""
    end
    if wsList[name] and wsList[name].thread then
        wsList[name].thread:release()
    end
    local ws={
        real=true,
        thread=love.thread.newThread('Zframework/websocket_thread.lua'),
        triggerCHN=love.thread.newChannel(),
        sendCHN=love.thread.newChannel(),
        readCHN=love.thread.newChannel(),
        lastPingTime=0,
        lastPongTime=timer(),
        pingInterval=6,
        status='connecting',-- 'connecting', 'running', 'dead'
        sendTimer=0,
        alertTimer=0,
        pongTimer=0,
    }
    wsList[name]=ws
    ws.thread:start(ws.triggerCHN,ws.sendCHN,ws.readCHN)
    ws.sendCHN:push(host)
    ws.sendCHN:push(port)
    ws.sendCHN:push(path..subPath)
    ws.sendCHN:push(head)
    ws.sendCHN:push(timeout or 2.6)
end

function WS.status(name)
    local ws=wsList[name]
    return ws.status or 'dead'
end

function WS.getTimers(name)
    local ws=wsList[name]
    return ws.pongTimer,ws.sendTimer,ws.alertTimer
end

function WS.setPingInterval(name,time)
    local ws=wsList[name]
    ws.pingInterval=math.max(time or 2.6,2.6)
end

function WS.alert(name)
    local ws=wsList[name]
    ws.alertTimer=2.6
end

local OPcode={
    continue=0,
    text=1,
    binary=2,
    close=8,
    ping=9,
    pong=10,
}
local OPname={
    [0]='continue',
    [1]='text',
    [2]='binary',
    [8]='close',
    [9]='ping',
    [10]='pong',
}
function WS.send(name,message,op)
    if type(message)=='string' then
        local ws=wsList[name]
        if ws.real and ws.status=='running' then
            ws.sendCHN:push(op and OPcode[op] or 2)-- 2=binary
            ws.sendCHN:push(message)
            ws.lastPingTime=timer()
            ws.sendTimer=1
        end
    else
        MES.new('error',"Attempt to send non-string value!")
        MES.traceback()
    end
end

function WS.read(name)
    local ws=wsList[name]
    if ws.real and ws.status~='connecting' and ws.readCHN:getCount()>=2 then
        local op,message=ws.readCHN:pop(),ws.readCHN:pop()
        if op==8 then-- 8=close
            ws.status='dead'
        elseif op==9 then-- 9=ping
            WS.send(name,message or "",'pong')
        end
        ws.lastPongTime=timer()
        ws.pongTimer=1
        return message,OPname[op] or op
    end
end

function WS.close(name)
    local ws=wsList[name]
    if ws.real then
        ws.sendCHN:push(8)-- 8=close
        ws.sendCHN:push("")
        ws.status='dead'
    end
end

function WS.update(dt)
    local time=timer()
    for name,ws in next,wsList do
        if ws.real and ws.status~='dead' then
            if ws.thread:isRunning() then
                if ws.triggerCHN:getCount()==0 then
                    ws.triggerCHN:push(0)
                end
                if ws.status=='connecting' then
                    local mes=ws.readCHN:pop()
                    if mes then
                        if mes=='success' then
                            ws.status='running'
                            ws.lastPingTime=time
                            ws.lastPongTime=time
                            ws.pongTimer=1
                        else
                            ws.status='dead'
                            MES.new('warn',text.wsFailed:repD(mes))
                        end
                    end
                elseif ws.status=='running' then
                    if time-ws.lastPingTime>ws.pingInterval then
                        WS.send(name,"",'pong')
                    end
                    if time-ws.lastPongTime>6+2*ws.pingInterval then
                        WS.close(name)
                    end
                end
                if ws.sendTimer>0 then ws.sendTimer=ws.sendTimer-dt end
                if ws.pongTimer>0 then ws.pongTimer=ws.pongTimer-dt end
                if ws.alertTimer>0 then ws.alertTimer=ws.alertTimer-dt end
            else
                ws.status='dead'
                local err=ws.thread:getError()
                if err then
                    MES.new('warn',text.wsClose:repD(err:match(":.-:(.-)\n")))
                    WS.alert(name)
                end
            end
        end
    end
end

return WS
