local signedZip=ba.mkio()
local vmio = ba.openio("vm")
local fmt=string.format
local tins=table.insert
local apptab={}

do
   local v=ba.version()
   local min,max=vmio:dofile".version"
   local function e(msg,mm)
      tracep(false,0,fmt("\nError (mako.zip): Server %d %s required %d",v,msg,mm))
      mako.exit(1)
   end
   if min and v < min then e("<",min) end
   if max and v > max then e(">",max) end
end

local dos2unix=mako.dos2unix
local function emptytab(tab) return next(tab) == nil end
local function print(...) tracep(false,5,...) end

local function setSecH(dir)
   dir:header{
      ["x-xss-protection"]="1; mode=block",
      ["x-content-type"]="nosniff",
   }
end

local conf=require"loadconf"
local rtldir=ba.create.resrdr("rtl",conf.rtlprio or 127,vmio)
setSecH(rtldir)
mako.rtldir=rtldir
rtldir:insert()

if mako.execpath then
   local _,op=ba.openio"disk":resourcetype()
   if op ~= "windows" and op ~= "ce" then
      package.cpath = fmt("%s/?.so;%s",mako.execpath,package.cpath)
   end
end

local function createAuth(create, authtype)
   if create and mako.udb then
      local udb,users=mako.udb()
      return ba.create.authenticator(udb,{type=authtype,realm=conf.realm}),users
   end
end

do
   local users=0
   if type(conf.users) == "table" then
      local userdb={}
      for name,pwd in pairs(conf.users) do
	 if type(name) == 'string' then
	    users=users+1
	    userdb[name]={pwd=pwd,roles={},recycle=true}
	 end
      end
      if users > 0 then
	 local u=ba.create.jsonuser()
	 u:set(userdb)
	 mako.udb=function() return u,users end
      end
   end
   if type(conf.fileserver) == "table" then
      require"wfs"
      local t=conf.fileserver
      if users > 0 or t.noauth==true then
	 local io=ba.openio(t.ioname or "home") or ba.openio"disk"
	 if t.path then io = ba.mkio(io, t.path) or io end
	 local n=t.fsname or "fs"
	 local ld=t.lockdir or '.LOCK'
	 if not io:stat(ld) and not io:mkdir(ld) then
	    local ldn = (io:realpath(ld) or ld).." (invalid name)"
	    print(fmt("Err: cannot open or create lockdir: %s",ldn))
	 end
	 local ok,fs=pcall(ba.create.wfs,n,t.priority or 0,io,ld)
	 print(fmt("Creating file server at /%s/ -> %s %s",n,
		   io:realpath"" or '/', ok and "ok" or "failed: "..fs))
	 if ok then
	    if t.noauth==true then
	       print"\tWarning: unprotected file server."
	    else
	       local auth=createAuth(true, 'digest')
	       fs:setauth(auth)
	    end
	    mako.fs=fs
	    fs:insert()
	 end
      else
	 print"Ignoring 'mako.conf fileserver' settings."
      end
   end
end

local function mkio(path,cfg)
   local function dupio(ioname)
      local io = ba.openio(ioname)
      if not io then
	 return "notfound", fmt('I/O "%s" not installed',ioname)
      end
      local ssl = path:find("https://",1,true)
      if ssl then
	 cfg = cfg or {}
	 cfg.shark = cfg.shark or ba.sharkclient()
	 io:netconf(cfg)
      end
      local st,errCode,errMsg,dpio=io:stat(path)
      if st then
	 dpio,errCode=ba.mkio(io, path)
	 if dpio then
	    if ssl then
	       dpio:netconf(cfg)
	    end
	    return dpio
	 end
	 return nil, (errCode or "ioerror"), "Cannot duplicate IO"
      end
      return nil,errCode,errMsg
   end
   local io,errCode,errMsg
   if path:find"https?://" then
      io,errCode,errMsg=dupio"net"
      if io then return io end
   else
      io,errCode = dupio"disk"
      if io then return io end
      if errCode == "invalidname" and path:find('..',1,true) then
	 path=fmt("%s/%s",ba.openio"home":realpath"",path)
	 return ba.mkio(ba.openio"disk",dos2unix(path):gsub("//","/"))
      end
      io,errCode,errMsg = dupio"home"
      if io then return io end
   end
   return nil,errCode,errMsg
end

local function errh(emsg)
   return debug and debug.traceback and debug.traceback(emsg,2) or emsg
end

local function appreq(mT,env,n)
   local ln,f,err,m,ok
   m=mT[n]
   if m then return m end
   ln=fmt(".lua/%s.lua",n:gsub("%.","/"))
   if not env.io:stat(ln) then error(fmt("%s not found",n),3) end
   local f,err=env.io:loadfile(ln,env)
   if f then
      ok,err = xpcall(f,errh)
      if ok then mT[n]=err return err end -- err is now mod.
   end
   error(fmt("%s failed: %s",n,err or "?"),3)
end

local function execPreload(appenv,path)
   local ok,err
   if appenv.io:stat".preload" then
      local mT,f={}
      function appenv.appreq(n) return appreq(mT,appenv,n) end
      f,err = appenv.io:loadfile(".preload",appenv)
      if f then
	 ok, err = xpcall(f,errh)
	 if ok then err=nil end
      end
   else
      print(fmt("Info: %s/.preload not found",path))
   end
   if err then
      return nil,err
   end
   return true
end

local function execOnunload(appenv)
   local ok,e=true
   local f = rawget(appenv,"onunload")
   if type(f) == "function" then
      ok, e = pcall(f)
      if not ok then
	 local err=fmt("Stopping app %s failed: %s\n",apptab[appenv].path,e or "?")
	 print(err)
	 return false,err
      end
   end
   return ok
end

local function start(appname,prio,io,path,nocheck)
   local err
   if not nocheck and signedZip and "zip" ~= io:resourcetype() then
      err="Only ZIP files are allowed in signature verification mode"
   else
      local rootapp = #appname == 0 and true or false
      local dir=ba.create.resrdr(not rootapp and appname or nil,prio,io)
      if dir then
	 setSecH(dir)
	 dir:insert()
	 local appenv=setmetatable({io=io,dir=dir},{__index=_G})
	 appenv.app = appenv
	 local ok
	 ok,err=execPreload(appenv,path)
	 if ok then
	    dir:lspfilter(appenv)
	    apptab[appenv]={io=io,dir=dir,path=path}
	    return appenv,dir
	 end
	 dir:unlink()
	 if not err then
	    err= ".preload failed"
	 end
      else
	 err="Cannot create resrdr"
      end
   end
   return nil,err
end

local function lApp(appname,prio,path,cfg)
   local io,err
   if "string" == type(path) then
      path=dos2unix(path)
      io,err = mkio(path,cfg)
   else
      assert("userdata" == type(path) and path.mkdir)
      io=path
      path=dos2unix(io:realpath"")
   end
   path=path:gsub('/$', '')
   local appenv
   if io then
      appenv,err=start(appname,prio,io,path)
   end
   local msg=appenv and ": ok" or ": failed: "..err
   print(fmt('Loading %s as "%s" %s',
	     path,#appname > 0 and appname or "root application",msg))
   return appenv,err
end

mako.sharkclient = ba.sharkclient -- Deprec.

function mako.createloader(io)
   local function loader(name)
      name=name:gsub("%.","/")
      local lname=fmt(".lua/%s.lua",name)
      if not io:stat(lname) then return nil end
      local res,err=io:loadfile(lname)
      if not res then tracep(false,1,err) end
      return res
   end
   tins(package.searchers, loader)
   return loader
end


function mako.getapps()
   local list={}
   for _,t in pairs(apptab) do
      tins(list,t.path)
   end
   return list
end

local function findapp(x)
   local appenv
   if type(x) == "number" then
      local ix=1
      for env in pairs(apptab) do
	 if ix == x then
	    appenv=env
	    break
	 end
	 ix=ix+1
      end
   else
      appenv = apptab[x] and x
   end
   if not appenv then error("No such app",3) end
   if not appenv.io then error("No io for app",3) end
   return appenv
end

function mako.stopapp(x)
   local appenv = findapp(x)
   if emptytab(appenv) then error("Already stopped",2) end
   local ok,err = execOnunload(appenv)
   appenv.dir:unlink()
   for k in pairs(appenv) do appenv[k]=nil end
   return ok,err
end


function mako.startapp(x)
   local appenv = findapp(x)
   if not emptytab(appenv) then error("Already running",2) end
   local app=apptab[appenv]
   appenv.io,appenv.dir,appenv.app=app.io,app.dir,appenv
   app.dir:insert()
   return execPreload(appenv,app.path)
end


function mako.reloadapp(x)
   x = findapp(x)
   mako.stopapp(x)
   return mako.startapp(x)
end

mako.createapp=lApp

function mako.removeapp(x)
   x = findapp(x)
   if not emptytab(x) then mako.stopapp(x) end
   apptab[x] = nil
end


if conf.tracelogger then
   if vmio:stat"tracelogger/index.html" then
      mako.tldir=ba.create.tracelogger()
      mako.rtldir:insert(mako.tldir)
      local tl=conf.tracelogger
      local users,auth
      if type(tl) == "table" then
	 mako.tldir:configure(tl)
	 auth,users = createAuth(tl.auth, 'digest')
	 if auth then mako.tldir:setauth(auth) end
      end
      print(users and
	    fmt("Enabling a %u user tracelogger",users) or
	    "Enabling unprotected tracelogger!")
   else
      print"Ignoring tracelogger config option!"
   end
end

mako.log=function() return false end
if conf.log then require"log" end

return function()
if 1==#mako.argv and "-"~=mako.argv[1]:sub(1,1) then
   local fn=mako.argv[1]
   local function l()
      local f = _G.io.open(fn, "r")
      if not f then error("Cannot open file: " .. fn) end
      local line = f:read("*l")
      if line:sub(1, 2) ~= "#!" then f:seek("set", 0) end
      return load(f:read("*a"), fn)
   end
   local function eh(e) print(debug.traceback(e,2)) end
   xpcall(function() l()() end, eh)
   mako.exit()
else
   if "boolean"==type(conf.acmetpm) and ba.tpm then require"acme/engine".tpm(conf.acmetpm) end
   require"acme/dns".loadcert()
   if type(conf.acme) == "table" then
      local a=conf.acme
      if a.acceptterms == true then
	 require(type(a.challenge) == "table" and a.challenge.type == "dns-01" and
		 "acme/dns" or "acme/bot").cfgFileActivation()
      end
   end
   local exitFuncT={}
   function mako.onexit(func, set)
      exitFuncT[func] = set and true or nil
   end
   local st=vmio:stat"apps"
   if st and st.isdir then vmio:dofile".embapps"(vmio,start) end
   for _,v in pairs(mako.argv) do
      if v:find"$-l" then
	 local appname, prio, path = v:match"$-l([^:]*):([^:]*):(.*)"
	 prio = prio and tonumber(#prio == 0 and 0 or prio) or nil
	 if appname and prio and path then
	    if #path > 0 then
	       lApp(appname, prio, path)
	    else
	       print(fmt("Invalid load command '%s': no path",v))
	    end
	 end
      end
   end
   if conf.apps then
      local function extname(path)
	 local x=path:match".-/([^/]+)$"
	 x=x or path
	 local y=x:match"(.-)%.[^%.]+$"
	 return y or x
      end
      for _,v in pairs(conf.apps) do
	 if type(v) == "string" then
	    lApp(extname(v), 0, v)
	 elseif type(v) == "table" and v.path then
	    local appenv,pdir=lApp(v.name or extname(v.path),
				   v.prio or 0, v.path, v.conf)
	    if v.dav and appenv then
	       local io=appenv.io
	       local name = type(v.dav) == "string" and v.dav or "dav"
	       local ldir = ".LOCK"
	       if not io:stat(ldir) then
		  local pOK,mOK=pcall(function() return io:mkdir(ldir) end)
		  if not pOK or not mOK then
		     ldir=nil
		  end
	       end
	       local davdir=ba.create.dav(name,io,ldir,50,100)
	       local auth,davusers = createAuth(v.auth, 'dav')
	       if auth then davdir:setauth(auth) end
	       pdir:insert(davdir,true)
	       local msg = davusers and
		  fmt("Enabling a %u user",davusers) or
		  "WARNING: Enabling unprotected"
	       print(fmt('%s DAV server%s for "%s": DAV URI=%s\n',
			 msg,
			 ldir and '' or ' in read only mode',
			 v.name,davdir:baseuri()))
	    end
	 end
      end
   end
   if not next(apptab) then pcall(function() vmio:dofile".noapp" end) end
   return function() -- onunload
      for env in pairs(apptab) do execOnunload(env) end
      for func in pairs(exitFuncT) do
	 local ok, e = pcall(func)
	 if not ok then
	    print(fmt("'onexit' func failed: %s\n",e or "?"))
	 end
      end
      apptab=nil
      for _,k in ipairs{"lcon6","lcon","slcon6","slcon"} do
	 pcall(function() ba[k]:close() end)
      end
      collectgarbage()
      collectgarbage()
      collectgarbage()
      collectgarbage()
   end
end
end,1--ret func,ver
