local CBOR = require "cbor"

local skynet = {
	server = "wss://skynet.osmarks.net/connect/",
	socket = nil,
	open_channels = {},
	CBOR = CBOR
}

function skynet.connect(force)
	if not skynet.socket or force then
		-- If we already have a socket and are throwing it away, close old one.
		if skynet.socket then skynet.socket.close() end
		local sock = http.websocket(skynet.server)
		if not sock then error "Skynet server unavailable, broken or running newer protocol version." end
		skynet.socket = sock
		
		for _, c in pairs(skynet.open_channels) do
			skynet.open(c)	
		end
	end
end

function skynet.disconnect()
	if skynet.socket then skynet.socket.close() end
end

local function value_in_table(t, v)
	for k, tv in pairs(t) do if tv == v then return true, k end end
	return false
end

local function send_raw(data, tries)
	local tries = tries or 0
	skynet.connect()
	local ok, err = pcall(skynet.socket.send, CBOR.encode(data), true) -- send in binary mode
	if not ok then
		if tries > 0 then sleep(tries) end
		if tries > 5 then error("Max reconnection attempts exceeded. " .. err) end
		pcall(skynet.connect, true) -- attempt to force reconnect
		send_raw(data, tries + 1)
	end
end

-- Opens the given channel
function skynet.open(channel)
	-- Don't send unnecessary channel-open messages
	if not value_in_table(skynet.open_channels, channel) then
		send_raw {
			"open",
			channel
		}
		table.insert(skynet.open_channels, channel)
	end
end

local function recv_one(filter)
	skynet.connect()
	while true do
		-- weirdness with CC: Tweaked makes `receive` apparently not work?
		--local contents = (skynet.socket.receive or skynet.socket.result)()
		local _, u, contents = os.await_event "websocket_message"
		if u == skynet.server then
			local result = CBOR.decode(contents)
			if type(result) == "table" then
				if result[1] == "error" then error(result[2] .. ": " .. result[3]) end
				if filter(result) then
					return result
				end
			end
		end
	end
end

local function recv_message(channel)
	local m = recv_one(function(msg)
		return msg[1] == "message" and (channel == nil or msg[2].channel == channel)
	end)
	return m[2].channel, m[2].message, m[2]
end

function skynet.logs(start, end_)
	error "The Skynet server no longer supports log retrieval"
end

local listener_running = false
-- Converts "websocket_message"s into "skynet_message"s.
function skynet.listen(force_run)
	local function run()
		while true do
			os.queueEvent("skynet_message", recv_message())	
		end
	end
	if not listener_running or force_run then
		local ok, err = pcall(run)
		listener_running = false
		if not ok then
			error(err)
		end
	end
end

-- Receives one message on given channel
-- Will open channel if it is not already open
-- Returns the channel, message, and full message object
function skynet.receive(channel)
	if channel then skynet.open(channel) end
	return recv_message(channel)
end

-- Send given data on given channel
-- Can accept a third argument - an object of extra metadata to send
function skynet.send(channel, data, full)
	local obj = full or {}
	obj.message = data
	obj.channel = channel
	send_raw {
		"message",
		obj
	}
end

return skynet
