local l = require "luxio"

local serv = l.socket(l.AF_INET, l.SOCK_STREAM, 0)
local sa = l.make_sockaddr(l.AF_INET, 1234, "0.0.0.0")

l.setsockopt(serv, l.SOL_SOCKET, l.SO_REUSEADDR, 1)
l.bind(serv, sa)
l.listen(serv, 5)

local pfds = l.pollfds_new()

l.pollfds_resize(pfds, 1);

l.pollfds_setslot(pfds, 1, serv, l.POLLIN, 0)

local quit = false
local clients = { }
local clientslots = {}

function send_to_all(data, exclude)
   for fd, client in pairs(clients) do
      if fd ~= exclude then
	 client.sendbuf[#client.sendbuf+1] = data
	 l.pollfds_setslot(pfds, client.pfd, fd, l.bit.bor(l.POLLIN, l.POLLOUT))
      end
   end
end

function client_flush(client)
   local data = table.concat(client.sendbuf)
   local sent, errno = l.write(client.fd, data)

   if sent < 0 then
      print(("something wicked happened to fd %d"):format(client.fd))
      return
   end

   if sent < #data then
      client.sendbuf = { data:sub(sent + 1, -1) }
   else
      client.sendbuf = { }
      l.pollfds_setslot(pfds, client.pfd, client.fd, l.POLLIN)
   end
end

function client_recv(client, data)
   local line
   client.recvbuf[#client.recvbuf+1] = data
   if data:match "\n" then
      line = table.concat(client.recvbuf)
      client.recvbuf = { line:match "\n(.*)" }
      send_to_all(("%d: %s\n"):format(client.fd, line:match "(.*)\n"))
   end
end

repeat

   local nfds, err = l.poll(pfds, 5000)
   if nfds == 0 then
      print("Timed out")
   elseif nfds == -1 then
      print(l.strerror(err))
   else
      for i = 1, #pfds do
	 local fd, evt, revt = l.pollfds_getslot(pfds, i)
	 if revt ~= 0 then
	    if fd == serv then
	       -- New client!
	       local newclient, clientaddr = l.accept(serv)
	       l.fcntl(newclient, l.F_SETFL, l.O_NONBLOCK)
	       print("New client from", clientaddr)
	       l.pollfds_resize(pfds, #pfds + 1)
	       clients[newclient] = { sendbuf = {}, recvbuf = {}, pfd = #pfds, fd = newclient, addr = clientaddr }
	       clientslots[#pfds] = clients[newclient]
	       send_to_all("New client from " .. clientaddr.address .. "\n", newclient)
	       l.pollfds_setslot(pfds, #pfds, newclient, l.POLLIN, 0)
	    else
	       client = clients[fd]
	       if (l.bit.btest(revt, l.POLLOUT)) then
		  client_flush(client)
	       end
	       if (l.bit.btest(revt, l.POLLIN)) then
		  local s, err = l.read(fd, 1024)
		  if s == "" then
		     -- Tell everyone other than this client, it died!
		     send_to_all("Client on " .. client.addr.address .. " died!\n", fd)
		     -- Blank out (wasteful us!) this pollfd entry
		     l.pollfds_setslot(pfds, client.pfd, -1, 0, 0);
		     -- Remove us from the client list
		     clients[fd] = nil
		     -- And close the fd for fun and profit
		     l.close(fd)
		  elseif s == -1 then
		     print(("Something odd happened on client %d"):format(fd))
		  else
		     client_recv(client, s)
		  end
	       end
	    end
	    nfds = nfds - 1
	 end
	 if nfds == 0 then
	    break
	 end
      end
   end

until quit
