local ev = require 'luxio.event'
local sio = require 'luxio.simple'

local clients = {}
local clientnr = 0

local listener = assert(sio.bind("localhost", 1234))

local function client_say(client, str)
   client.sendbuf[#client.sendbuf+1] = str
   ev.set_events(client.socket, nil, true)
end

local function client_send(client)
   if #client.sendbuf > 1 then
      client.sendbuf = { table.concat(client.sendbuf) ; _ = client.sendbuf._}
   end
   local written, err = client.socket:write(client.sendbuf[1] or "", client.sendbuf._)
   if written >= 0 then
      client.sendbuf._ = (client.sendbuf._ or 0) + written
   else
      print("Erm?", err)
      return false
   end

   if client.sendbuf._ and (client.sendbuf._ == #(client.sendbuf[1] or "")) then
      client.sendbuf = {}
   end

   if client.quitting and not client.sendbuf._ then
      ev.unregister_fd(client.socket)
      client.socket:close()
      clients[client.socket] = nil
      return false
   end

   return client.sendbuf._
end

local function do_line(client, line)
   local cmd = line:match("^%.(.-)\r?\n$")
   if cmd then
      if cmd == "quit" then
	 client:say("S: bye\n");
	 client.quitting = true
	 for sock, _client in pairs(clients) do
	    if client ~= _client then
	       _client:say(("S: Client %d is quitting\n"):format(client.nr))
	    end
	 end
	 return
      end
      client:say(("S: Unknown command: %s\n"):format(cmd))
   else
      for sock, _client in pairs(clients) do
	 _client:say(("%d: %s"):format(client.nr, line))
      end
   end
end

local function client_recv(client)
   local s, err = client.socket:read(1024)
   if (s == "") then
      -- client has closed
      for sock, _client in pairs(clients) do
	 if client ~= _client then
	    _client:say(("S: Client %d disconnected\n"):format(_client.nr))
	 end
      end
      clients[client.socket] = nil
      ev.unregister_fd(client.socket)
      client.socket:close()
      return
   end
   if (s == -1) then
      print("Erm?", err)
      return false
   end
   client.recvbuf[#client.recvbuf+1] = s
   if (s:match("\n")) then
      -- At least one newline, so process lines
      local full_s = table.concat(client.recvbuf)
      local line = full_s:match("^(.-\n)")
      full_s = full_s:match(".-\n(.*)$")
      while line do
	 do_line(client, line)
	 line = full_s:match("^(.-\n)")
	 full_s = full_s:match(".-\n(.*)$")
      end
      client.recvbuf = { full_s }
   end
   -- Always want to read more
   return true
end

function new_client()
   local newsock, addr = listener:accept()
   clientnr = clientnr + 1
   for sock, client in pairs(clients) do
      client:say(("S: New client %d from %s\n"):format(clientnr, addr))
   end
   clients[newsock] = {
      socket = newsock,
      sendbuf = {},
      recvbuf = {},
      say = client_say,
      nr = clientnr
   }
   ev.register_fd(newsock, clients[newsock], client_recv, client_send, client_err)
   ev.set_events(newsock, true)
   clients[newsock]:say("S: Welcome, say something\n")
   -- And tell us about more clients
   return true
end

ev.register_fd(listener, nil, new_client)
ev.set_events(listener, true)

function show_time()
   for sock, client in pairs(clients) do
      client:say(("S: The time, sponsored by luxio, is %s\n"):format(os.date()))
   end
   return os.time() + 30
end

ev.alarm(os.time() + 30, show_time)

ev.run()
