------------------------------------------------------------------------
--
-- Módulo SGA Unix
--
------------------------------------------------------------------------
--
-- Especificação das funções públicas
--

TRASHDIR="PBStrash/"
PBSERRORFILE="PBSError.log"
PBSOUTPUTFILE="PBSOutput.log"

--
-- Funções de controle do módulo.
--

------------------------------------------------------------------------
-- Incializa o módulo para execução. Essa função deve ser chamada antes
-- de qualquer outra função desta biblioteca.  Retorna verdadeiro caso o
-- módulo seja inicializado com sucesso. Em caso de erro, retorna nil e
-- a mensagem de erro.
--
-- ok, err = open()
------------------------------------------------------------------------
open = function ()
  servermanager.localhost = SGAD_CONF.localhost -- XXX
  servermanager.name = SGAD_CONF.name           -- XXX
  if os.execute("test -d "..TRASHDIR) ~= 0 and
     not os.execute("mkdir "..TRASHDIR) then
     print("Não foi possível criar o diretório "..TRASHDIR..".")
     print("Usando ./")
     TRASHDIR=""
  end
  return 1
end

------------------------------------------------------------------------
-- Termina a execução do módulo. Os processos disparados que ainda
-- estejam rodando não são afetados por essa chamada. Essa função não
-- retorna valores.
--
-- close()
------------------------------------------------------------------------
close = function()
  servermanager._nodes = nil
end

------------------------------------------------------------------------
--
-- Funções de consulta à configuração de servidor.
--
------------------------------------------------------------------------
-- Retorna uma tabela com os nós do servidor especificado.
-- Consulta o PBS. Caso o
-- servidor seja nil, assume localhost. Em caso de erro, retorna nil
-- e a mensagem de erro.
--
-- nodes, err = getnodes(server)
------------------------------------------------------------------------
getnodes = function (server)
--
-- PROBLEMA: OVO/GALINHA! :-)
--
-- ninfo = servermanager._getnode(server)
-- if not ninfo then
--    return nil, servermanager.NO_SERVER
-- end
   local ids = {}
   local fname = os.tmpname()
   local command = string.format('pbsnodes -a > %s', fname)
   os.execute( command )
   local fd = io.open(fname, "r")
   if not fd then
     return nil, "Falha na abertura do arquivo temporário.".." ["..fname.."]".." de saída do comando: '"..command.."'."
   end
   local str = fd:read("*a")
   if not str then
     return nil, "Falha na leitura do arquivo temporário.".." ["..fname.."]".." de saída do comando: '"..command.."'."
   end	   
   fd:close()
   os.remove(fname)

   for host in string.gmatch("\n"..str, "\n(%S+)\n") do
     local hostname = string.gsub(host,"%..*","")
     table.insert(ids, hostname)
   end

  if #ids > 0 then
     return ids, nil
  else
     return nil, "Não foi possível obter os nós do PBS"
   end
end

------------------------------------------------------------------------
-- Retorna o número de processadores no servidor especificado. Caso o
-- servidor seja nil, assume localhost. Em caso de erro, retorna nil
-- e a mensagem de erro.
--
-- cpus, err = getnumcpus(server)
------------------------------------------------------------------------
getnumcpus = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.num_processors, nil
end

------------------------------------------------------------------------
-- Retorna a ordem de bytes no servidor especificado. O retorno pode ser
-- LITTLE_ENDIAN ou BIG_ENDIAN. Caso o servidor seja nil, assume
-- localhost. Em caso de erro, retorna nil e a mensagem de erro.
--
-- order, err = getbyteorder(server)
------------------------------------------------------------------------
getbyteorder = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.byteorder, nil
end

------------------------------------------------------------------------
--
-- Funções de monitoração de servidor.
--
------------------------------------------------------------------------
-- Retorna a quantidade em bytes de memória no servidor especificado.
-- O retorno é uma tabela contendo o total de memória RAM e de Swap, 
-- no formato:
--    { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- mem, err = getmemory(server)
------------------------------------------------------------------------
getmemory = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.memory, nil
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto no servidor
-- especificado. Esse valor considera o número de processadores que o
-- servidor possui.
--
-- Por exemplo, caso a métrica de ocupação seja o número de processos
-- na fila de prontos, este número estará dividido pela quantidade 
-- de processadores. Caso o servidor seja nil, assume localhost. 
-- 
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor em questão. Em caso de erro, retorna nil e a mensagem de
-- erro.
--
-- load, err = getcpuload(server, maxold)
------------------------------------------------------------------------
getcpuload = function (server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de CPU)!"
   end
   if not ninfo.cpuload then
     return nil, "Falha de aquisição de carga de CPU!"
   end
   return ninfo.cpuload, nil
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto no
-- servidor especificado. O retorno é uma tabela contendo o total de
-- memória RAM e de Swap, no formato:
--   { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor em questão. Em caso de erro, retorna nil e a mensagem de
-- erro.
--
-- load, err = getmemoryload(server, maxold)
------------------------------------------------------------------------
getmemoryload = function (server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de memória)!"
   end
   if not ninfo.memoryload then
     return nil, "Falha de aquisição de carga da memória!"
   end
   return ninfo.memoryload, nil
end

----------------------------------------------------------------------
-- Retorna o número de jobs que estão sendo executados no servidor
-- especificado.
----------------------------------------------------------------------
getnumberofjobs = function(server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de número de jobs)!"
   end
   if not ninfo.njobs then
     return nil, "Falha de aquisição do número de jobs!"
   end

   return ninfo.njobs, nil
end

------------------------------------------------------------------------
--
-- Funções de execução, monitoração e controle de processos.
--
------------------------------------------------------------------------
-- Executa um comando no servidor especificado.
--
-- * Opcionalmente, pode ser passado o nome de um arquivo de onde a 
-- entrada padrão será direcionada.
-- * Opcionalmente, pode ser passado o nome de um arquivo para onde a 
-- saída padrão será direcionada. 
-- * Opcionalmente, pode ser passado o nome de um arquivo para onde a 
-- saída de erro será direcionada.
-- * A saída padrão e de erro podem ser direcionadas para o mesmo
-- arquivo.
-- * Caso o servidor seja nil, assume localhost.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- handle, err = executecommand(identifier, command, infile, outfile,
--   errorfile, server)
------------------------------------------------------------------------
executecommand = function(id, command, infile, outfile, errorfile, host,
  startdir, output_path)
  output_path = output_path or TRASHDIR
  local errorlog = (not errorfile) and output_path.."/"..PBSERRORFILE
  local tmpfile = os.tmpname()

  local cmd = command
  if infile then
     cmd = cmd .. " < " .. infile
  end
  if startdir then
    local exec_string = string.format("%s > %s ", cmd, tmpfile)
    servermanager.writeMsg( "Tentando execução:\n\t"..exec_string.."\n" )
    os.execute(exec_string)
    errorlog = nil
  else
  --
  --outfile = outfile or "/dev/null"
  --errorfile = errorfile or "/dev/null"
  --
  --O scp dá um erro!
  --Também não serve usar o tmpfile em /tmp/xxx
  --Unable to copy file 17.tissot.OU to tissot:/dev/null
  --
  --VAMOS TER QUE LIMPAR DEPOIS!!! XXX
  --
    local extraParams = ""
    if SGAD_CONF.queue_name then
      extraParams = extraParams .. " -q " .. SGAD_CONF.queue_name
    end
    if host then
      extraParams = extraParams .. " -l host=" .. host
    end
    local _outfile = outfile or output_path.."/"..PBSOUTPUTFILE
    local _errorfile = errorfile or errorlog
    local exec_string = string.format( [[qsub -N %s -V -o "%s" -e "%s" %s > %s]],
      id, _outfile, _errorfile, extraParams, tmpfile)
    local cmd_string = string.format( [[ksh -c 'cd "%s" && %s']], SGAD_CONF.path, cmd)
    servermanager.writeCmd( "Tentando execução:\n\t"..exec_string.."\n" ..
                            "\t\t"..cmd_string.."\n" )
    local fd = io.popen(exec_string, "w")
    fd:write(cmd_string)
    fd:close()
  end  

  local fd = io.open(tmpfile, "r")
  if not fd then
     local strerror = "Erro ao abrir arquivo temporário:\n"..
         "\t["..tostring(tmpfile).."]\n"..
         "\tpara aquisição de dados de execução\n" 
     servermanager.writeError( strerror)
     return nil, strerror
  end

  local str = fd:read("*a")
  str = string.gsub( str, "%c", "" )
  fd:close()

  os.remove(tmpfile)
  servermanager._updatecommands()

  local handle ={
     cmdid = id,
     command = command,
     execHost = nil,
     errorfile = errorlog,
     last_update = 0,
--   pid = string.gsub( str, "(%S+)%.%S+","%1"),
--   server = string.gsub( str, "%S+%.(%S+)","%1"),
     pid = string.gsub( str, "([^%.]+)%..*","%1"),
     server = string.gsub( str, "[^%.]+%.(.*)","%1"),
     start_time = servermanager.now()
  }
  handle.pid = tonumber(handle.pid)
  return handle
end

------------------------------------------------------------------------
-- Retorna uma tabela Lua com os dados do comando que devem ser
-- persistentes. Em caso de erro, retorna nil e a mensagem de erro.
--
-- pdata, err = getcommandpersistentdata(handle)
------------------------------------------------------------------------
getcommandpersistentdata = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end
   return handle, nil
end

------------------------------------------------------------------------
-- Retorna um handle com os dados do comando que devem ser persistentes.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- handle, err = retrievecommandhandle(pdata)
------------------------------------------------------------------------
retrievecommandhandle = function (pdata)
   if type(pdata) ~= "table" or not pdata.cmdid then
      return nil, "Dados inválidos."
   end

   servermanager._updatecommands()

   local cmdhTable, err = servermanager._getcommandall( pdata.cmdid, pdata.server)
   if err then
      return nil, err
   end

   -- Diferente do pid de um processo, o jobid que usamos no pbs é
   -- gerado pelo nosso sistema. Devemos considerar que qualquer
   -- job que tenha este id faz parte da mesma tarefa...
   if #cmdhTable == 0 then
      return nil, "Comando não encontrado."
   end

   pdata.last_update = 0 

   return pdata, nil
end

------------------------------------------------------------------------
-- Retorna o identificador do processo recebido na função
-- executecommand.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- id, err = getcommandid(handle)
------------------------------------------------------------------------
getcommandid = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end

   return handle.cmdid, nil
end

------------------------------------------------------------------------
-- Retorna um identificador unico do processo iniciado através da função
-- executecommand.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- pid, err = getcommandpid(handle)
------------------------------------------------------------------------
getcommandpid = function (handle)
   if type(handle) ~= "table" or not handle.pid or
      not tonumber(handle.pid) then
      return nil, "Handle inválido."
   end

   return tonumber(handle.pid), nil
end

------------------------------------------------------------------------
-- Retorna o estado de um processo iniciado através da função 
-- executecommand.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.
--
-- O retorno pode ser RUNNING, NOT_RESPONDING, WAITING ou FINISHED. 
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- job_state The state of the job.
-- 'Q'    /* Job Queued record */
-- 'S'    /* Job run (Started) */
-- 'R'    /* Job Rerun record */
-- 'C'    /* Job Checkpointed and held */
-- 'T'    /* Job resTart (from chkpnt) record */
-- 'E'    /* Job Ended/usage record */
-- 'D'    /* Job Deleted by request */
-- 'A'    /* Job Abort by server */
--
-- status, err = getcommandstatus(handle, maxold)
------------------------------------------------------------------------
getcommandstatus = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local cmdhTable, err = servermanager._getcommandall( handle.cmdid,
     handle.server)
   if err then
      return nil, err
   end

   local status = servermanager.NOT_RESPONDING
   for i, cmdh in pairs(cmdhTable) do
     local state = cmdh.job_state
     if state == 'E' or
        state == 'C' or
        state == 'D' or
        state == 'A' then
        status = servermanager.FINISHED
        servermanager._checkforerror(handle)
     elseif state == 'Q' then
        status = servermanager.WAITING
     else
        status = servermanager.RUNNING
        break
     end
   end

   return status
end

------------------------------------------------------------------------
-- Retorna a maquina que esta executando o comando definido por handle.
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando. Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- host, err = getcommandexechost(handle, maxold)
------------------------------------------------------------------------
getcommandexechost = function (handle, maxold)
   if handle.host then
     return handle.host
   end
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end
-- -- EM ABERTO: Pega o host do primeiro job
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
   -- Fazemos a concatenação de todos os hosts que compõem o job.
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local exectab = {}
   local exechost
   for i, cmdh in pairs(cmds) do
     local ehost = cmdh["exec_host"]
     if ehost and not exectab[ehost] then
       exectab[ehost] = true
       if exechost then
         exechost = exechost .. "/" .. ehost
       else
         exechost = ehost
       end
     end
   end
   handle.host = exechost
   return handle.host
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto pelo
-- processo especificado. Esse valor considera o número de processadores
-- que o servidor possui. Por exemplo, caso o processo execute em apenas
-- um processador, este número estará dividido pela quantidade de
-- processadores.  Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter. 
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.  Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- load, err = getcommandcpuload(handle, maxold)
------------------------------------------------------------------------
getcommandcpuload = function (handle, maxold)
   -- Não tenho a ocupação do último minuto no PBS.
   -- Vou usar cput/walltime
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local procs, err = servermanager.getcommandallinfo(handle.cmdid,
     handle.server)
   if err then
      return nil, err
   end
  
   if not procs or #procs == 0 then
     return 0
   end
   local totcpuperc = 0
   for i, proc in pairs(procs) do
     totcpuperc = totcpuperc + proc.CPUPerc
   end
   return totcpuperc / #procs
end

------------------------------------------------------------------------
-- Retorna todas as informações sobre um determinado comando.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor que estiver executando o comando.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getcommandallinfo(handle, maxold)
------------------------------------------------------------------------
getcommandallinfo = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local procs = {}
   for i, cmdh in pairs(cmds) do
     local ehost = cmdh["exec_host"] or "unknown"
     local ram = cmdh["resources_used.mem"] or servermanager.ERROR_CODE
     local swap = cmdh["resources_used.vmem"] or servermanager.ERROR_CODE
     local cput = cmdh["resources_used.cput"] or servermanager.ERROR_CODE
     local walltime = cmdh["resources_used.walltime"] or servermanager.ERROR_CODE
     local cpuperc = 0
     if walltime ~= 0 then
       local server = cmdh["exec_host"] or handle.server
       local ncpus, err = servermanager.getnumcpus(server)
       ncpus = ncpus or 1

       cpuperc = (cput / ncpus) /walltime
     end
     local state
     local job_state = cmdh.job_state
     if job_state then
       if job_state == 'E' or
          job_state == 'C' or
          job_state == 'D' or
          job_state == 'A' then
          state = servermanager.FINISHED
          servermanager._checkforerror(handle)
       elseif job_state == 'Q' then
          state = servermanager.WAITING
       else
          state = servermanager.RUNNING
       end
     else
        state = servermanager.NOT_RESPONDING
     end
  
     local procinfo = {
        pid = tonumber(cmdh.pid),
        ppid = 0, -- XXX IMPORTANTE
        command = handle.command,
        execHost = ehost,
        state = state,
        processorId = 0, -- XXX 
        memoryRamSizeMb = ram / (1024 * 1024),
        memorySwapSizeMb = swap / (1024 * 1024),
        CPUPerc = cpuperc,
        CPUTimeSec = cput,
        wallTimeSec = walltime,
     }
     table.insert(procs, procinfo)
   end
   if #procs < 1 then
     return nil, "Não há informações sobre esse comando."
   end
   -- EM ABERTO: Alteramos o valor do campo walltime do primeiro
   -- processo!
   -- Não podemos usar o walltime dos processos. Usamos um walltime
   -- global do job, considerando a chamada da executecommand.
   procs[1].wallTimeSec = servermanager.now() - handle.start_time
   return procs
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto pelo
-- processo especificado. O retorno é uma tabela contendo o total de
-- memória RAM e de Swap, no formato:
--     { ram = xxx, swap = yyy }
---------------------------------
-- MAS QUAL A UNIDADE?!?! Bytes?!
---------------------------------
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando. 
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getcommandmemoryload(handle, maxold)
------------------------------------------------------------------------
getcommandmemoryload = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
     > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end
-- -- EM ABERTO: Como vamos fazer o somatório nesse caso?!
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
   -- Fazemos um somatório das memórias de todos os jobs.
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local totram
   local totswap
   for i, cmdh in pairs(cmds) do
     local ram = cmdh["resources_used.mem"]
     local swap = cmdh["resources_used.vmem"]
     if ram then
       totram = (totram or 0) + ram
     end
     if swap then
       totswap = (totswap or 0) + swap
     end
   end
   totram = totram or servermanager.ERROR_CODE
   totswap = totswap or servermanager.ERROR_CODE
   return { ram = totram, swap = totswap}
end

------------------------------------------------------------------------
-- Retorna os tempos de execução do processo especificado. O retorno é
-- uma tabela contendo os tempos de usuário, sistema e total (tempo de
-- parede), no formato:
--     { user = xxx, system = yyy, elapsed = zzz }
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.  Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- time, err = getcommandtimeusage(handle, maxold)
------------------------------------------------------------------------------
getcommandtimeusage = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

 ---- EM ABERTO: Como vamos fazer o somatório nesse caso?!
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
-- local cput = cmdh["resources_used.cput"] or servermanager.ERROR_CODE
-- local walltime = cmdh["resources_used.walltime"] or servermanager.ERROR_CODE
  
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local cput
   for i, cmdh in pairs(cmds) do
     if cmdh["resources_used.cput"] then
       cput = (cput or 0) + cmdh["resources_used.cput"]
     end
   end
   local walltime = servermanager.now() - handle.start_time

   local system = cput or servermanager.ERROR_CODE
   local real = walltime
   local info = {
     user = system,
     system = system,
     elapsed = real,
   }

   return info, nil
end

------------------------------------------------------------------------
-- Obtém informações sobre os jobs em execução no SGA ------------------
------------------------------------------------------------------------
getjobsinfo = function()
  local tmpname = os.tmpname()
  local command = string.format('qstat -x > %s', tmpname)

  servermanager.writeMsg("Coletando info sobre jobs com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 then
     local err = "Falha na coleta de dados de jobs (qstat)."
     servermanager.writeError(err.." Cod.:"..tostring(result))
     return
  end

  local file = io.open(tmpname, "r")
  if not file then
     local err = "Falha na leitura do arquivo temporário que guarda info sobre jobs."
     servermanager.writeError(err.." Arq.:["..tmpname.."]")
     return
  end

  local strXML = file:read("*a")
  file:close()
  os.remove(tmpname)

  return strXML
end

------------------------------------------------------------------------
-- Interrompe um processo iniciado através da função executecommand.
-- Retorna verdadeiro caso tenha sucesso. A definição de sucesso é que
-- uma chamada a getcommandstatus para o mesmo processo retornará o
-- valor FINISHED. Retorna falso caso o processo não termine.  Em caso
-- de erro, retorna nil e a mensagem de erro.
--
-- ok, err = killcommand(handle)
------------------------------------------------------------------------
killcommand = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end

   servermanager._updatecommands()

   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
   for i, cmdh in pairs(cmds) do
     local command = string.format("qdel %s.%s", cmdh.pid, handle.server)
     servermanager.writeMsg("Cancelando comando ["..command.."]")
     os.execute(command)
   end
   servermanager._checkforerror(handle)

   return 1
end

------------------------------------------------------------------------
-- Libera todos os recursos relacionados ao processo especificado. Esta
-- função precisa ser chamada após o término do processo para que
-- eventuais recursos alocados a ele possam ser liberados. Após a
-- chamada desta função, o handle do processo não poderá mais ser
-- utilizado.  Em caso de erro, retorna nil e a mensagem de erro.
--
-- ok, err = releasecommandinfo(handle)
------------------------------------------------------------------------
releasecommandinfo = function (handle)
   return 1
end

------------------------------------------------------------------------
--
-- Especificação das funções privadas
--
------------------------------------------------------------------------
_checkforerror = function (handle)
  if not handle.errorfile then
     return
  end
  -- Erro se colocamos o path absoluto ao chamar o qsub.
  local errorfile = handle.errorfile
  local str
  local fd = io.open(errorfile, "r")
  if not fd then
     servermanager.writeError( "Erro ao abrir arquivo temporário:\n"..
                              "\t["..tostring(errorfile).."]\n"..
                              "\tpara aquisição de dados de execução\n" )
     handle.errorfile = nil
     return
  end
  str = fd:read("*a")
  fd:close()
  os.remove(errorfile)
  handle.errorfile=nil
  if str and string.len(str) > 0 then
    local errormsg = string.format(
      "Possível erro na execução do comando %s:\n%s",
      handle.cmdid, str)
    servermanager.writeError(errormsg)
  end
end

_getnodeconf = function(name)
   -- XXX
   local i = 1
   while SGAD_CONF.nodes[i] and
     SGAD_CONF.nodes[i].name ~= name do
     i = i + 1
   end
   return SGAD_CONF.nodes[i]
   -- XXX
end

_getnode = function(server)
   server = server or servermanager.name
   local ninfo = servermanager._nodes[server]
   if not ninfo then
      ninfo = servermanager._add(server)
   end
   return ninfo
end
 
_updatecommands = function( )
  local cmds = {}
  local fname = os.tmpname()
  local command = string.format('qstat -f > %s', fname)

  servermanager.writeMsg("Coletando info com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 then
     local err = "Falha na coleta de dados de comandos (qstat)."
     servermanager.writeError(err.." Cod.:"..tostring(result))
     return
  end

  local fd = io.open(fname, "r")
  if not fd then
     local err = "Falha na leitura do arquivo temporário do qstat."
     servermanager.writeError(err.." Arq.:["..fname.."]")
     return
  end

  local str = fd:read("*a")
  fd:close()
  os.remove(fname)

  string.gsub(str, "Job Id: (%d+)%.(%S+)(.-)\n\n",
    function(pid, server, attrs)
 --   attrs = string.gsub(attrs, "%c", "")
      attrs = string.gsub(attrs, "  ", " ")
      local tb = {pid = pid}
      string.gsub(attrs, "%s*(%S+) = ([%S ]+)",
                  function (attr, val) tb[attr] = val end)
      local cmdid = tb.Job_Name

      -- Transforma o tempo em segundos
      if tb["resources_used.cput"] then
        tb["resources_used.cput"] =
          servermanager._str2sec(tb["resources_used.cput"])
      end
 
      if tb["resources_used.walltime"] then
        tb["resources_used.walltime"] =
          servermanager._str2sec(tb["resources_used.walltime"])
      end
 
      -- Transforma para bytes
      if tb["resources_used.mem"] then
        tb["resources_used.mem"] =
          servermanager._getvalunit(tb["resources_used.mem"])
      end
 
      if tb["resources_used.vmem"] then
        tb["resources_used.vmem"] =
          servermanager._getvalunit(tb["resources_used.vmem"])
      end
 
      if tb.exec_host then
        tb.exec_host = string.gsub(tb.exec_host, "^(%w+).+$", "%1")
      end

      if not cmds[server] then
         cmds[server] = {}
      end
 
      local cmdh = cmds[server]
      if not cmdh[cmdid] then
        cmdh[cmdid] = {}
      end
      table.insert(cmdh[cmdid], tb)
    end)

  servermanager._cmds = cmds
end

_commandsdump = function( )
   for host,cmds in pairs(servermanager._cmds) do
      for cmdid, procs in pairs(cmds) do
        print("Atributos do processo "..cmdid)
        for i,attrs in pairs(procs) do
          for attr, val in pairs(attrs) do
            print(attr, val)
          end
        end
      end
      print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
   end
end

_getcommandall = function( cmdid, host)
   if servermanager._cmds[host] then
      local cmdh = servermanager._cmds[host][cmdid]
      if type(cmdh) == "table" then
        return cmdh
      end
   end

   return nil, "Comando não encontrado."
end

_getcommand = function( cmdid, host) -- EM ABERTO: PEGO O 1o PROCESSO!
   local cmdh = servermanager._getcommandall( cmdid, host)
   if type(cmdh) == "table" and type(cmdh[1]) == "table" then
     return cmdh[1]
   end

   return nil, "Comando não encontrado."
end

_updatenode = function( node)
  local attr_table = {}

  local fname = os.tmpname()
  local domain = node.domain or SGAD_CONF.domain or ""
  local command = string.format('pbsnodes -a %s%s > %s',
                                node.name, domain, fname)

  servermanager.writeMsg("Coletando info com ["..command.."]")
  os.execute( command )

  local fd = io.open(fname, "r")
  if not fd then
    servermanager.writeError("Falha na leitura do arquivo temporário.".." ["..fname.."]")
    return nil
  end

  local str = fd:read("*a")
  fd:close()
  os.remove(fname)

  for attr, val in string.gmatch(str, "(%w+) = ([^\n]+)") do
    attr_table[attr] = val
  end
  if attr_table.status then
    for attr, val in string.gmatch(attr_table.status, "(%w+)=([^,]+)") do
      -- Não sobreescreve um valor já definido
      attr_table[attr] = attr_table[attr] or val
    end
  end
  return attr_table
end

_add = function(nodename)
   local errcode = servermanager.ERROR_CODE
   if not servermanager._nodes[nodename] then
      local new_node = {}
      local STRING = type("")
      local NUMBER = type(0)

      local node = servermanager._getnodeconf(nodename)
      if not node then
        servermanager.writeError("Host "..nodename.." not found.")
        return nil
      end

      new_node = node -- XXX
      local byteorder = node.byte_order
      local numcpus = node.num_processors
      local memory = {node.memory_ram_info_mb, node.memory_swap_info_mb}

      local t = servermanager._updatenode(node)

      -- NCPUS
      if not t.ncpus then
         numcpus = 1
         servermanager.writeError("Falha de aquisição de número de cpus!")
      else
        local _ncpus = string.gsub(t.ncpus, "(%d+)", "%1")
        _ncpus = tonumber(_ncpus)
        if type(_ncpus) ~= NUMBER or _ncpus < 1 then 
           numcpus = 1
           servermanager.writeError("Falha de aquisição de número de cpus!")
        else
           numcpus = _ncpus
        end
      end

      -- MEMORY
      if not t.totmem or not t.physmem then
         memory = { errcode, errcode}
         servermanager.writeError( "Falha de aquisição de memória!")
      else
        local _totmem = servermanager._getvalunit(t.totmem)
        local _ram = servermanager._getvalunit(t.physmem)
    
        if type(_totmem) ~= NUMBER or type(_ram) ~= NUMBER then
          memory = { errcode, errcode}
          servermanager.writeError( "Falha de aquisição de memória!")
        else
          local swap = _totmem - _ram
          memory = { _ram, swap}
        end
      end

      -- BYTE ORDER
      if type(byteorder) ~= STRING or (byteorder ~= "LITTLE_ENDIAN" and
         byteorder ~= "BIG_ENDIAN") then
         byteorder = servermanager.LITTLE_ENDIAN
         servermanager.writeError( "Falha de aquisição de byteorder!")
      else
         if byteorder == "LITTLE_ENDIAN" then
            byteorder = servermanager.LITTLE_ENDIAN
         else
            byteorder = servermanager.BIG_ENDIAN
         end
      end

      new_node.name = node.name
      new_node.num_processors = numcpus
      new_node.memory = { ram = memory[1], swap = memory[2]}
      new_node.memory_ram_info_mb = memory[1]
      new_node.memory_swap_info_mb = memory[2]
      new_node.byteorder = byteorder
      new_node.last_update = 0

      servermanager._nodes[nodename] = new_node
   end

   return servermanager._nodes[nodename]
end

------------------------------------------------------------------------
-- _update(server, maxload)
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor em questão.
--
-- Campos estáticos do nó:
-- numcpus
-- memory
--          { ram = xxx, swap = yyy }
-- byteorder
--          LITTLE_ENDIAN ou BIG_ENDIAN.
--
-- Campos dinâmicos do nó:
-- cpuload
-- memoryload
--          { ram = xxx, swap = yyy }
-- njobs
------------------------------------------------------------------------
_update = function(node, freq)
   local errcode = servermanager.ERROR_CODE
   local nodeinfo = servermanager._getnode(node)
   if freq and (servermanager.now() - nodeinfo.last_update) < freq then
      return nodeinfo
   end

   nodeinfo.last_update = servermanager.now()
   local NUMBER = type(0)
-----
   local t = servermanager._updatenode(nodeinfo)
   if not t then
      return nodeinfo
   end

   -- Load Average
   local _load = string.gsub(t.loadave or "", "(%d+)", "%1")
   _load = tonumber(_load)
   if type(_load) == NUMBER then 
      nodeinfo.cpuload = _load / nodeinfo.num_processors * 100
   end

   -- Memory Use
   local _mem = servermanager._getvalunit(t.availmem or "")
   if type(_mem) == NUMBER then
      local mload = (nodeinfo.memory.ram + nodeinfo.memory.swap) - _mem
      if mload <= nodeinfo.memory.ram then 
         nodeinfo.memoryload = {ram = mload, swap = 0}
      else
         local ramtot = nodeinfo.memory.ram
         nodeinfo.memoryload = {ram = ramtot, swap = mload - ramtot}
      end
   end

   -- Number of Jobs
   -- obtem só o número de jobs sem se preocupar em quais são
   t.jobs = t.jobs or ""
   local _, _njobs = string.gsub(t.jobs, "%d+/[^, ]+[, ]?[, ]?", "")
   nodeinfo.njobs = _njobs

   -- State
   local isonline = function(state)
     -- node-attribute values (state,ntype) 
     --   free
     --   offline
     --   down
     --   reserve
     --   job-exclusive
     --   job-sharing
     --   busy
     --   state-unknown
     --   time-shared
     --   cluster
     if not state or string.find(state,"offline") or
       string.find(state, "down") or
       string.find(state, "state-unknown") then
       return false
     end
     return true
   end
   nodeinfo.isonline = isonline(t.state)
   return nodeinfo
end

_getvalunit = function (str)
  local val = string.gsub(str, "(%d+)[KkMmGgTt]?b", "%1")
  val = tonumber(val)
  local unit = string.gsub(str, "%d+([KkMmGgTt]?)b", "%1")
  val = servermanager._memvaluetobytes(val, unit)

  return val
end

_memvaluetobytes = function (value,unit)
  unit = string.upper(unit)
  if unit == "T" then
    return value * (1024^4)
  elseif unit == "G" then
    return value * (1024^3)
  elseif unit == "M" then
    return value * (1024^2)
  elseif unit == "K" then
    return value * 1024
  else
    return value
  end
end

_str2sec = function (str) -- Retorna segundos
  -- str pode ser "SS", "MM:SS" ou "HH:MM:SS"
  local tot = 0
  string.gsub(str, "(%d+)", function (n)
    tot = tot * 60 + n
  end)

  return tot
end

writeMsg = function(str)
  local msg = "SGAD ["..os.date("%d/%m/%y - %H:%M:%S").."] - "..str
  print(msg)
  io.flush()
end
writeError = writeMsg
writeCmd = writeMsg

------------------------------------------------------------------------
servermanager = {
  -------------
  -- private --
  -------------
  NO_SERVER= "Servidor não existente", -- Colocar num arquivo de mensagens?
  _nodes   = {},
  _cmds    = {},
  _add     = _add,
  _commandsdump = _commandsdump,
  _getcommand = _getcommand,
  _getcommandall = _getcommandall,
  _getnode = _getnode,
  _getnodeconf = _getnodeconf,
  _getvalunit = _getvalunit,
  _memvaluetobytes = _memvaluetobytes,
  _str2sec = _str2sec,
  _update  = _update,
  _updatecommand = _updatecommand,
  _updatecommands = _updatecommands,
  _updatenode = _updatenode,
  _checkforerror = _checkforerror,

  -------------
  -- public --
  -------------
  now = now,
  sleep = sleep,
  writeMsg = writeMsg,
  writeError = writeError,
  writeCmd = writeCmd,

  -- Funções de controle do módulo:
  open = open,
  close = close,

  -- Funções de consulta à configuração de servidor:
  getnodes = getnodes,
  getnumcpus = getnumcpus,
  getmemory = getmemory,
  getbyteorder = getbyteorder,

  -- Funções de monitoração de servidor:
  getcpuload = getcpuload,
  getmemoryload = getmemoryload,
  getnumberofjobs = getnumberofjobs,

  -- Funções de execução, monitoração e controle de processos:
  executecommand = executecommand,
  retrievecommandhandle = retrievecommandhandle,       -- EXTRA
  getcommandpersistentdata = getcommandpersistentdata, -- EXTRA
  getcommandallinfo = getcommandallinfo,               -- EXTRA
  getcommandid = getcommandid,
  getcommandpid = getcommandpid,
  getcommandstatus = getcommandstatus,
  getcommandexechost = getcommandexechost,
  getcommandcpuload = getcommandcpuload,
  getcommandmemoryload = getcommandmemoryload,
  getcommandtimeusage = getcommandtimeusage,
  getjobsinfo = getjobsinfo,
  killcommand = killcommand,
  releasecommandinfo = releasecommandinfo,

  -- Constantes do módulo:
  RUNNING = 0,
  NOT_RESPONDING = 1,
  WAITING = 2,
  FINISHED = 3,

  LITTLE_ENDIAN = 0,
  BIG_ENDIAN = 1,

  ERROR_CODE = -1,
}

