local net = require "minitel"
local syslog = require "syslog"
local fs = require "filesystem"
local event = require "event"
local serial = require "serialization"

local coro = {} -- table of coroutines, one per socket
local cfg = {}
local timer, listener
local isRunning = false -- simple lock

cfg.path = "/srv" -- default config
cfg.port = 70
cfg.looptimer = 0.5

local function log(msg,level)
 syslog(msg,level,"frequestd")
end

local function loadConfig() -- load config from file
 local fobj = io.open("/etc/fserv.cfg","rb")
 if fobj then
  local ncfg = serial.unserialize(fobj:read("*a"))
  if ncfg then
   for k,v in pairs(ncfg) do
    cfg[k] = v
   end
  end
  fobj:close()
 end
end

local function writeConfig()
 local fobj = io.open("/etc/fserv.cfg","wb")
 if fobj then
  fobj:write(serial.serialize(cfg))
  fobj:close()
 end
end

local function handleSocket(sock) -- create a coroutine for a new socket
 coro[#coro+1] = coroutine.create(function()
  local line
  repeat
   coroutine.yield()
   line = sock:read()
  until line
  local ttype, path = line:match("([ts])(.+)")
  if not ttype or not path then
   sock:write("fIncomplete request")
   sock:close()
   return false
  end
  path = fs.canonical(cfg.path.."/"..fs.canonical(path))
  sock.cname = sock.addr..":"..tostring(sock.port)
  log("["..sock.cname.."] "..ttype.." "..path,6)
  if ttype == "t" then -- transfer request
   if not fs.exists(path) then
    sock:write("nFile not found.")
    sock:close()
    log("["..sock.cname.."] Not found.",7)
    return
   end
   if fs.isDirectory(path) then
    local rs = "d"
    for file in fs.list(path) do
     rs = rs..file.."\n"
    end
    sock:write(rs)
    sock:close()
    log("["..sock.cname.."] Directory.",7)
    return
   end
   local f = io.open(path,"rb")
   if not f then
    sock:write("fUnable to open file for reading",7)
    sock:close()
    return
   end
   sock:write("y")
   log("["..sock.cname.."] Transferring file.",7)
   local chunk = f:read(net.mtu)
   repeat
    sock:write(chunk)
    coroutine.yield()
    chunk = f:read(net.mtu)
   until not chunk
   sock:close()
   f:close()
   log("["..sock.cname.."] file transferred.",7)
  elseif ttype == "s" then -- stat request
   if fs.exists(path) then
    local ftype = "f"
    if fs.isDirectory(path) then
     ftype = "d"
    end
    sock:write("y"..ftype..tostring(fs.size(path)))
    sock:close()
    log("["..sock.cname.."] stat request returned.",7)
   else
    sock:write("nFile not found.",7)
    sock:close()
    log("["..sock.cname.."] Not found.",7)
   end
  end
 end)
 log("New connection: "..sock.addr..":"..tostring(sock.port),7)
end

local function sched() -- run coroutines
 if isRunning then return end
 isRunning = true
 for i = #coro, 1, -1 do -- prune dead coroutines
  if coroutine.status(coro[i]) == "dead" then
   table.remove(coro,i)
  end
 end
 for k,v in pairs(coro) do
  coroutine.resume(v)
 end
 isRunning = false
end

function start()
 if timer or listener then return end
 loadConfig()
 writeConfig()
 timer = event.timer(cfg.looptimer, sched, math.huge)
 listener = net.flisten(cfg.port, handleSocket)
end
function stop()
 if not timer or not listener then return end
 event.cancel(timer)
 event.ignore("net_msg",listener)
end
function restart()
 stop()
 start()
end
