#!/usr/bin/lua 

json = require("json")

local stateVar=
{
  currentState="INIT",
  state={
  ["INIT"]={},
  ["ON_WAN"]={
     fail_to=0
     },
  ["TO_BACKUP"]={
     start_to=0,
     start_retry=0
     },
  ["ON_BACKUP"]={
     link_pers=0
     },
  ["TO_WAN"]={
     }
  }
};
local defaultcfg =
{
  wan_profile="wan",
  backup_profile="wwan",
  link_test_ip='8.8.8.8',
}
local cfg = {}


function execute(cmd, raw)
  local f = assert(io.popen(cmd, 'r'))
  local s = f:read('*a') or nil
  local status,reason,code = f:close()
  if raw then return s,status,reason,code end
  s = string.gsub(s, '^%s+', '')
  s = string.gsub(s, '%s+$', '')
  s = string.gsub(s, '[\n\r]+', ' ')
  return s,status,reason,code
end



function load_cfg(path)
  print("Looking for configuration in "..path)
  local f = io.open(path,"r")
  if f then
     local cfgjson = json.decode(f:read("*a"))
     if cfgjson then
        cfg = cfgjson
     else
       print("Failed to load config using deafults")
       cfg = defaultcfg
     end 
      f:close()
  else
     print("Config file '"..path.."' does not exist")
     cfg = defaultcfg
  end
 
  
  f = io.open(path,"w")
  
  local cfgjson = json.encode(cfg)
  cfgjson = cfgjson:gsub('[{,]','%1\n')
  cfgjson = cfgjson:gsub('}','\n%1')
  
  f:write(cfgjson)
  
  f:close()
  
  verify_cfg()
  
end


function verify_cfg()
   local out,stat = execute(" [ -e /etc/netctl/"..cfg.wan_profile.." ] ")
   if stat then
     print("Using WAN Profile '"..cfg.wan_profile.."'...") 
   else
     print("WAN Profile "..cfg.wan_profile.." does not exist")
     os.exit(-1)  
   end
   out,stat = execute(" [ -e /etc/netctl/"..cfg.backup_profile.." ] ")
   if stat then
     print("Using Backup Profile '"..cfg.backup_profile.."'...") 
   else
     print("Backup Profile '"..cfg.backup_profile.."' does not exist")
     os.exit(-1)  
   end
   
   f = io.open("/etc/netctl/"..cfg.wan_profile,"r")
   local wan_profile = f:read("*a")
   local wan_type = wan_profile:match("[^#]IP=(.-)\n") or "bullshit"
   if wan_type == "static" then
     print("WAN IP config is static...")
     local gateway = wan_profile:match("[^#]Gateway='(.-)'\n") or "bullshit"
     if gateway ~= "bullshit" then
        print("Using WAN gateway '"..gateway.."'")
        cfg.wan_gateway = gateway
     else 
        print("The WAN profile does not have a gateway configuration")
        os.exit(-1)
     end
   else
     print("The WAN profile is for a '"..wan_type.."' IP configuration")
     print("This script only works with 'static' type configurations")
     os.exit(-1)
   end
   print("Using link test IP '"..cfg.link_test_ip.."'...")
   f:close()
      
end

function check_wan_state()
   local out,state = execute([[netctl status ]]..cfg.wan_profile)
   print("WAN State:",state)
   if state then
      return true
   end
   return false
end

function check_backup_state()
   local out,state = execute([[netctl status ]]..cfg.backup_profile)
   print("Backup State:",state)
   if state then
      return true
   end
   return false
end

function restart_backup()
   local out,state = execute([[netctl restart ]]..cfg.backup_profile)
   print("Restart backup State:",state)
   if state then
      return true
   end
   return false
end

function check_link()
    local out,state = execute([[ping -c 3 ]]..cfg.link_test_ip)
    if state then
      return true
   end
   return false
end

function set_backup_routes()
   execute([[ip route del default via ]]..cfg.wan_gateway)
   execute([[ip route add ]]..cfg.link_test_ip..[[ via ]]..cfg.wan_gateway)
end

function reset_routes()
    execute([[ip route del ]]..cfg.link_test_ip)
    execute([[ip route add default via ]]..cfg.wan_gateway)
end

stateVar.state.INIT.run = function ()
  print("Running INIT")
  stateVar.currentState = "ON_WAN"
  
  if(check_backup_state()) then
     stateVar.currentState = "ON_BACKUP"
     print("Looks like we are on the backup")
  else if(check_wan_state()) then
     stateVar.currentState = "ON_WAN"
     print("Looks like we are on the WAN")
  else
     print("Nothing is good. Maybe need to restart the WAN")
  end
  end
end

stateVar.state.ON_WAN.run = function ()
  --print("Running ON_WAN")
  stateVar.currentState = "ON_WAN"
  local state = stateVar.state.ON_WAN
  if check_link() then
     if state.fail_to > 0 then
       print("WAN Link Seems OK")
     end
     state.fail_to = 0
  else
     if state.fail_to >= 3 then
       print("WAN Definitely Broken")
       state.fail_to = 0
       stateVar.currentState = "TO_BACKUP"
     else
       state.fail_to = state.fail_to + 1
       print("WAN Seems Broken")
       
     end
     end
end

stateVar.state.ON_BACKUP.run = function ()
  --print("Running ON_BACKUP")
  local state = stateVar.state.ON_BACKUP
  stateVar.currentState = "ON_BACKUP"
  if check_link() then
     if state.link_pers >= 5 then
       print("WAN is back...")
       state.link_pers = 0
       stateVar.currentState = "TO_WAN"
     else
       print("WAN might be back...")
       state.link_pers = state.link_pers +1
     end
  else
     --print("WAN still down")
     state.link_pers = 0
  end
end

stateVar.state.TO_BACKUP.run = function ()
   print("Running TO_BACKUP")
  local state = stateVar.state.TO_BACKUP
   if check_backup_state() then
      print("Backup is up")
      stateVar.currentState = "ON_BACKUP"
      state.start_retry = 0
      state.start_to = 0
      set_backup_routes()
   else
     if state.start_retry < 3 then
       print("Trying to start the backup link")
       
       restart_backup()
       state.start_retry =  state.start_retry + 1
     else
       state.start_to = 0 
       state.start_retry = 0
       stateVar.currentState = "TO_WAN"
       print("Backup link is broken")
     end
   end    
end

stateVar.state.TO_WAN.run = function ()
   print("Running TO_WAN")
   local state = stateVar.state.TO_WAN
   os.execute([[netctl stop ]]..cfg.backup_profile)
   reset_routes()
   stateVar.currentState = "ON_WAN"
end   




function sleep(n)
  execute("sleep " .. tonumber(n))
end


--This is the main state machine loop
local cfg_file = arg[1] or "/etc/wanbackup.conf"

load_cfg(cfg_file)

while true do
 local prev_state = stateVar.currentState
 stateVar.state[stateVar.currentState].run()
 if prev_state ~= stateVar.currentState then
   print("State Change: ["..prev_state.."]--->["..stateVar.currentState.."]")
 else
   sleep(10)
 end
end