#!/usr/bin/lua

package.path=package.path..";/usr/share/lua/kses/?.lua"
package.cpath=package.cpath..";/usr/share/lua/kses/?.so"



local lkmsg = require("lkmsg")
local rsm = require("rsmdict")
local sock = require("socket")
local ftp = require("socket.ftp")
local url = require("socket.url")
local lfs = require("lfs")
local ltn12 = require("ltn12")

local log_stdout = false
local dev_processed = false
local cap_processed = false
local ftp_config = false


config_file="/etc/rsm/fldd.conf"
cfg={}

function log_msg(msg)
   os.execute("logger -t tvsd \""..msg.."\"")
   if log_stdout then 
     print("tvsd: "..msg)
   end
end

--Fucntion to print tables and show replies etc
function print_table(t,p)

    local prefix = p or ""
    for k,v in pairs(t) do
      local ks = k
      if type(k) == 'number' then
        ks = "["..k.."]"
      end
      if type(v) == 'table' then
         print(prefix..ks.." --> {}")
         print_table(v,prefix.."  ")
      else if type(v) == 'function' then
            print(prefix..ks.." --> func()")
          else 
            print(prefix..ks.." --> "..v.." ("..type(v)..")")
            end
      end 
    end
end

local function ftp_files(files,srcdir)
  local stat = true
  --[[
    First make sure the remote directory exists
  ]]
  if not cfg.site.nmsaddr or not cfg.site.siteid then return false end
  
  local ftp_url="ftp://"..cfg["fldd.ftp.user"]..":"..cfg["fldd.ftp.passwd"].."@"..cfg.site.nmsaddr.."/"..cfg.site.siteid
  cmd = url.parse(ftp_url)
  cmd.command = "mkd"
  cmd.argument = cfg.site.siteid
  cmd.source = ltn12.source.string("")
   local fstat,err = ftp.put(cmd)
  if not fstat then log_msg("FTP Error: "..err) end
  
  for k,f in pairs(files) do
    
    local fio = assert(io.open(srcdir.."/"..f,"r"))
    if fio then
        log_msg("FTP Sending "..f)
        fstat,err = ftp.put(ftp_url.."/"..f,fio:read("*a"))
        if fstat then 
           stat = stat and true 
        else 
           stat = stat and false
           log_msg("FTP Error: "..err) 
        end
    else
       log_msg("Could not open "..f)    
    end
  end
  return stat
end


local function send_delivery()
   local cfg_dir = getconfig("fldd.tank.delivery.dir","/etc/rsm/fldd/tankdeliveries")
   local tx_files = {}
   for file in lfs.dir(cfg_dir) do
      if file:match(".+%.xml") then
         table.insert(tx_files,file)
      end   
   end
   ftp_delivery =  not ftp_files(tx_files,cfg_dir)
end



local function send_config()
   local cfg_dir = getconfig("fldd.tank.config.dir","/etc/rsm/fldd/tankconfigs")
   local tx_files = {}
   for file in lfs.dir(cfg_dir) do
      if file:match(".+%.xml") then
         table.insert(tx_files,file)
      end   
   end
   ftp_config =  not ftp_files(tx_files,cfg_dir)
end



function load_config()
  local lines =  assert(io.open(config_file)):lines()
  for line in lines do
     -- Check for comments
    if not line:match("^#(.+)$") then
      local key,value = line:match("^([%w_%.]+)%s-=%s-(.+)$")
       if key then
          if tonumber(value) then value = tonumber(value) end
          if value == "true" then value = true end
          if value == "false" then value = false end
          if key and value ~= nil then
                cfg[key]=value
          end
       end  
    end
  end
end

function getconfig(key,def)
   if key then
      return cfg[key]
   end
   return nil
end

function print_delivery(del)
  log_msg("Delivery Report for 0x"..string.format("%X",del.device).." Tank "..del.tanknum)
  log_msg("Start Time: "..os.date('%Y-%m-%d %H:%M:%S',del.StartTime).." End Time: "..os.date('%Y-%m-%d %H:%M:%S',del.EndTime))
  log_msg("Start Volume: "..del.StartVolume.." End Volume: "..del.EndVolume)
end


function generate_and_send_delivery(del)

    local del_dir = getconfig("fldd.tank.delivery.dir","/etc/rsm/fldd/tankdeliveries")
    local currdelfile =  io.open(del_dir.."/delivery-0x"..string.format("%X",del.device).."-"..del.tanknum..".xml","r")
    local currfile=""
    if currdelfile then
      currfile = currdelfile:read("*a")
      currdelfile:close() 
    end 
    local delfile = io.open(del_dir.."/delivery-0x"..string.format("%X",del.device).."-"..del.tanknum..".xml","w") 
 local   file = 
[[<Delivery id="0x]]..string.format("%X",del.device)..[[">
    <Tank id="]]..del.tanknum..[[">
       <Start_Date>]]..os.date("%y%m%d%H%M",del.StartTime)..[[</Start_Date>
       <End_Date>]]..os.date("%y%m%d%H%M",del.EndTime)..[[</End_Date>
       <Start_Volume>]]..del.StartVolume..[[</Start_Volume>
       <End_Volume>]]..del.EndVolume..[[</End_Volume>
       <Start_TCVolume>]]..del.StartTCVolume..[[</Start_TCVolume>
       <End_TCVolume>]]..del.EndTCVolume..[[</End_TCVolume>
       <Start_Water>]]..del.StartWater..[[</Start_Water>
       <End_Water>]]..del.EndWater..[[</End_Water>
       <Start_Temp>]]..del.StartTemp..[[</Start_Temp>
       <End_Temp>]]..del.EndTemp..[[</End_Temp>
       <Start_Height>]]..del.StartTemp..[[</Start_Height>
       <End_Height>]]..del.EndTemp..[[</End_Height>
    </Tank>
</Delivery>           
]]
    delfile:write(file)
    delfile:close()
    if file ~= currfile then
      ftp_delivery = true
    end   
end

function generate_tank_cfg()
 local cfg_dir = getconfig("fldd.tank.config.dir","/etc/rsm/fldd/tankconfigs")
 log_msg("Generating Config Files")
 for k,dev in pairs(cfg.devices) do
     local currcfgfile =  io.open(cfg_dir.."/0x"..string.format("%X",dev.serial)..".xml","r")
    local currcfg=""
    if currcfgfile then
      currcfg = currcfgfile:read("*a")
      currcfgfile:close() 
    end 
   local cfgfile= io.open(cfg_dir.."/0x"..string.format("%X",dev.serial)..".xml","w")
 
 
 
 local   file = 
[[<Dev id="0x]]..string.format("%X",dev.serial)..[[">
]]
    for j,tank in pairs(dev.tanks) do
file = file..[[
    <Tank id="]]..j..[[">
       <Capacity>]]..tank.Full_Volume..[[</Capacity>
       <Grade>]]..tank.Product_Label..[[</Grade>
       <Analog_Ports>
          <Volume>]]..tank.Volume..[[</Volume>
          <TC_Volume>]]..tank.TC_Volume..[[</TC_Volume>
          <Ullage>]]..tank.Ullage..[[</Ullage>
          <Height>]]..tank.Height..[[</Height>
          <Temperature>]]..tank.Temperature..[[</Temperature>
          <Water_Volume>]]..tank.Water_Volume..[[</Water_Volume>
       </Analog_Ports>
       <DInput_Ports>
          <High_Water_Alarm>]]..tank.High_Water_Alarm..[[</High_Water_Alarm>
          <Overfill_Alarm>]]..tank.OverFill_Alarm..[[</Overfill_Alarm>
          <Low_Product_Alarm>]]..tank.Low_Product_Alarm..[[</Low_Product_Alarm>
          <Invalid_Fuel_Level_Alarm>]]..tank.Invalid_Fuel_Level_Alarm..[[</Invalid_Fuel_Level_Alarm>
          <Probe_Out_Alarm>]]..tank.Probe_Out_Alarm..[[</Probe_Out_Alarm>
          <Delivery_Needed_Warning>]]..tank.Delivery_Needed_Alarm..[[</Delivery_Needed_Warning>
          <Maximum_Product_Alarm>]]..tank.Maximum_Product_Alarm..[[</Maximum_Product_Alarm>
          <Delivery_in_progress>]]..tank.Delivery_In_Progress_Alarm..[[</Delivery_in_progress>
       </DInput_Ports>
    </Tank>
]]
    end
file = file..[[
</Dev>           
]]
    cfgfile:write(file)
    cfgfile:close()

  if file ~= currcfg then
    log_msg("Config File updated")
    log_msg("currfile:")
    if currcfg then log_msg(currcfg) else log_msg("nil") end
    log_msg("newfile:")
    log_msg(file)
    ftp_config = true
  end
  
  end    
end


function process_adhoc(adhoc)
  if not adhoc then return end
  if adhoc.label and adhoc.label == "FLDD Delivery Report" then
     local delivery = {}
     delivery["device"] = adhoc.serial
     delivery["tanknum"] = adhoc.port
     local values= adhoc.value
     for k, v in string.gmatch(values, "(%w+)=([%w%.]+)") do
        if tonumber(v) then v = tonumber(v) end
        delivery[k] = v
     end
     log_msg("Delivery Event:")
     print_delivery(delivery)
     generate_and_send_delivery(delivery)
  end  
end

function insert_cap(cap)
  if cfg.devices[cap.serial] then 
     if not cfg.devices[cap.serial].tanks then cfg.devices[cap.serial].tanks = {} end
     --Need to parse the label into the parts they FLDD-tn-label
     local fldd,tank,label = cap.label:match("(FLDD)-(%d)-(.+)")
     if fldd and tank and label then
       label=label:gsub("%s","_")
       tank = tonumber(tank) 
       if not cfg.devices[cap.serial].tanks[tank] then cfg.devices[cap.serial].tanks[tank] = {} end
       cfg.devices[cap.serial].tanks[tank][label]=cap.value
     end 
     cap_processed = true
  end
end

function insert_dev(dev)
   if not cfg.devices then cfg.devices = {} end
   cfg["devices"][dev.serial] = dev
end


function populate_site(tags)
  site={}
  for k,tag in ipairs(tags) do
        if tag.tagid == RSM.RSM_TagID.TAG_SITE_NMS_ADDRESS then
           site["nmsaddr"]= tag.tag_value:match("([%w%.]+):*%d*")
           --check whether the siteid has changed or have been added for the first time
           if not cfg.site or cfg.site.nmsaddr ~= site.nmsaddr then
             ftp_delivery = true
             ftp_config = true
           end
        elseif tag.tagid == RSM.RSM_TagID.TAG_SITE_ID then
           site["siteid"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_SITE_LABEL then
           site["label"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_SITE_STATE then
           site["opstate"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_SITE_VERSION then
           site["version"]= tag.tag_value          
        else
           --log_msg("Unknown Site tag "..tag.tagid)
        end
  end
  return site
end

function populate_device(tags)
  device={}
  for k,tag in ipairs(tags) do
        if tag.tagid == RSM.RSM_TagID.TAG_DEV_STATE then
           device["opstate"]= tag.tag_value
        elseif tag.tagid == RSM.RSM_TagID.TAG_DEV_STATUS then
           device["status"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_DEV_LABEL then
           device["label"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_DEV_ID then
           device["serial"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_DEV_VERSION then
           device["version"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_DEV_TYPE then
           device["type"]= tag.tag_value          
        else
           --log_msg("Unknown Device tag "..tag.tagid)
        end
  end
  return device
end

function populate_cap(tags)
  local cap={}
  for k,tag in ipairs(tags) do
        if tag.tagid == RSM.RSM_TagID.TAG_DEV_ID then
           cap["serial"]= tag.tag_value
        elseif tag.tagid == RSM.RSM_TagID.TAG_CAP_ID then
           cap["id"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_CAP_LABEL then
           cap["label"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_CAP_VALUE then
           cap["value"]= tag.tag_value          
        else
           --log_msg("Unknown Capability tag "..tag.tagid)
        end
  end
  return cap
end

function populate_adhoc(tags)
  local adhoc={}
  for k,tag in ipairs(tags) do
        if tag.tagid == RSM.RSM_TagID.TAG_DEV_ID then
           adhoc["serial"]= tag.tag_value
        elseif tag.tagid == RSM.RSM_TagID.TAG_PORT_PNUM then
           adhoc["port"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_ADHOC_LABEL then
           adhoc["label"]= tag.tag_value          
        elseif tag.tagid == RSM.RSM_TagID.TAG_ADHOC_VALUE then
           adhoc["value"]= tag.tag_value          
        else
        
        end
  end
  return adhoc
end

function handle_object(obj)
   --log_msg("Received Object "..obj.objid)
   if obj.objid == RSM.RSM_TagID.TAG_SITE_CFG then
      local site = populate_site(obj.tags)
      --log_msg("Received Site Object")
      cfg["site"]=site
   elseif obj.objid == RSM.RSM_TagID.TAG_DEV_CFG then
      local device = populate_device(obj.tags)
      --log_msg("Received Device Object")
      if device.type == RSM.TAG_DEV_TYPE_Types.TAG_DEV_TYPE_VEEDER_ROOT then
         insert_dev(device)
         dev_processed = true
      end
   elseif obj.objid == RSM.RSM_TagID.TAG_CAP_DEF then
      local cap = populate_cap(obj.tags)
      --log_msg("Received Capability Object")
      insert_cap(cap)
   elseif obj.objid == RSM.RSM_TagID.TAG_ADHOC_EVT then
      adhoc = populate_adhoc(obj.tags)
      --log_msg("Received Adhoc Event")
      process_adhoc(adhoc)
   end  
   
end


function handle_rx(msg)
   
   if not msg then return end
   
   if msg.type == RSM.RSM_MsgID.MSG_REQ or msg.type == RSM.RSM_MsgID.MSG_EVT then
      for k,obj in ipairs(msg.objs) do
        handle_object(obj)
      end
   end   
   if dev_processed and cap_processed then
      dev_processed = false
      cap_processed = false
   end
end


if arg[1] ~= nil then
config_file = arg[1]
end

--[[
Main Function Part
]]
log_msg("TVS Logger daemon is starting up")
log_msg("Using config file "..config_file)
load_config()
log_msg("Need to connect to "..getconfig("rsmd.host","localhost")..":"..getconfig("rsmd.north.port",60000))
lkmsg:new()
lkmsg:connect(getconfig("rsmd.host","localhost"),getconfig("rsmd.north.port",60000))

local ctr=0;
local last_update = os.time()
local last_config_check = 0;
local tank_cfg = false
local last_site_check = 0;

while true do
  
  if not cfg.nmsaddr or not cfg.siteid then
     if last_site_check + 30 < os.time() then
       local msg = lkmsg:msg(RSM.RSM_MsgID.MSG_REQ)
       local obj = lkmsg:obj(RSM.RSM_TagID.TAG_SITE_CFG)
       msg:add(obj)
       lkmsg:send(msg)
       last_site_check = os.time()
     end    
  else
    if last_site_check + 50 < os.gettime() then
       local msg = lkmsg:msg(RSM.RSM_MsgID.MSG_REQ)
       local obj = lkmsg:obj(RSM.RSM_TagID.TAG_SITE_CFG)
       msg:add(obj)
       lkmsg:send(msg)
       last_site_check = os.time()
    end
  end
  
  if not cfg.devices and last_config_check + 10 < os.time() then
     --log_msg("Sending Get Devices")
     local msg = lkmsg:msg(RSM.RSM_MsgID.MSG_REQ)
     obj = lkmsg:obj(RSM.RSM_TagID.TAG_DEV_CFG)
     obj:addTag(RSM.RSM_TagID.TAG_DEV_TYPE,RSM.TAG_DEV_TYPE_Types.TAG_DEV_TYPE_VEEDER_ROOT)
     msg:add(obj)
     lkmsg:send(msg)
     last_config_check = os.time()
  elseif cfg.devices then 
     local tx_msg = false
     local msg = lkmsg:msg(RSM.RSM_MsgID.MSG_REQ)
     for k,dev in pairs(cfg.devices) do
        if not dev.tanks then
           obj = lkmsg:obj(RSM.RSM_TagID.TAG_CAP_DEF)
           obj:addTag(RSM.RSM_TagID.TAG_DEV_ID,dev.serial)
           msg:add(obj)
           tx_msg = true
        else
           --log_msg("Device "..k.." does not need refreshment")
        end
     end 
     if tx_msg == true then
         --log_msg("Sending Get Capabilities")
         lkmsg:send(msg)
     elseif tank_cfg == false then
        generate_tank_cfg()
        tank_cfg = true
     end 
  end
  
  
  if last_update + 60 < os.time() then
     --log_msg("Should really update the config now")
     cfg.devices = nil
     last_update = os.time()
     tank_cfg = false
  --else
     --print((last_update+60)..">"..os.time())
  end 

  if ftp_delivery == true then
     log_msg("Should Really be sending the config now")
     send_delivery() 
  end
  if ftp_config == true then
     log_msg("Should Really be sending the config now")
     send_config() 
  end
  local rxmsg = lkmsg:receive(RSM.RSM_MetaTags)
  handle_rx(rxmsg)
  ctr = ctr+1
end


