--[[----------------------------------------------------------------------------
Class to parse Java Properties config files

Author: Frikkie Jansen van Rensburg
Email: frikkie@kses.net

Date: March 2017
----------------------------------------------------------------------------]]--

local propParser = {}

local ct = {}
local mt = {}

-- helpers
local function populateTable(isComment, fvalue, fkey)
  if isComment then
    mt[#mt+1] = fvalue
  else
    -- clean up key and value
    fvalue = string.gsub(fvalue,"^%s*","")
    fkey = string.gsub(fkey,"^%s*","")

    fkey = string.gsub(fkey, "\\=","=")
    fkey = string.gsub(fkey, "\\:",":")
    fkey = string.gsub(fkey, "\\ "," ")

    fvalue = string.gsub(fvalue,"\\=","=")
    fvalue = string.gsub(fvalue,"\\:",":")

    ct[fkey] = fvalue
    mt[#mt+1] = fkey
  end
end
-- Module functions
function propParser.parse(file)
  local key = ""
  local value = ""
  local runOn = false
  ct = {}
  mt = {}

  for line in io.lines(file) do
    if runOn then
      line = string.gsub(line,"^%s*","")
      value = value .. string.sub(line,1,#line)
      if string.sub(line,#line,#line) == "\\" then
        value = string.sub(value,1,#value-1)
        runOn = true
      else
        runOn = false
        populateTable(false, value, key)
        value = ""
        key = ""
      end
    else
      key = ""
      value = ""
      -- first, check for comments
      if (string.find(line , "!") == 1) or (string.find(line , "#") == 1) then
        populateTable(true, line)
      else

        if string.sub(line, #line, #line) == "\\" then
          line = string.sub(line, 1, #line-1)
          runOn = true
        else
          runOn = false
        end
        for i = 1, #line do
          -- look for the pesky separator, can be one of the special chars, a newline
          -- or simply a space
          c = string.sub(line, i, i)
          if (c == "=") or (c == ":") then
            if string.sub(line,i-1,i-1) ~= "\\" then
              -- unescaped, so now you can split the string
              if runOn then
                key = string.sub(line, 1, i-1)
                value = string.sub(line, i+1, #line)
                break
              else
                key = string.sub(line, 1, i-1)
                value = string.sub(line, i+1, #line)
                populateTable(false,value,key)
                break
              end
            end
          elseif c == " " then
            if string.sub(line,i-1,i-1) ~= "\\" then
              -- unescaped, so now you can split the string

              -- look ahead to the next : or =, weird logic but necessary to properly
              -- seperate the key and the value
              local pos = 0
              for k = i,#line do
                  local c2 = string.sub(line, k, k)
                  if c2 == "=" or c2 == ":" then
                    pos = k
                    break
                  elseif c2 ~= " " then
                    pos = i
                    break
                  end
              end

              if runOn then
                key = string.sub(line, 1, i-1)
                value = string.sub(line, pos+1, #line)
                break
              else
                key = string.sub(line, 1, i-1)
                value = string.sub(line, pos+1, #line)
                populateTable(false,value,key)
                break
              end
            end
          end-- if c == "="
        end -- for characters in the line
      end
    end -- if run on
  end -- for lines
  setmetatable(ct,mt)
  return ct
end

function propParser.write(file, t)
  --print(string.format("Writing Java Properties file %s\n",file))

  local s = ""
  local temp = ""

  local f,e = io.open(file,"w")
  if f == nil then
    print(string.format("Error: %s",e))
    return nil
  end

  local meta = {}
  meta = getmetatable(t)

  if meta == nil or meta == {} then
    if t ~= {} and t ~= nil then
      for k,v in pairs(t) do
        temp = ""
        if string.find(k,"=") ~= nil then
          temp = string.gsub(k, "=", "\\=")
        else
          temp = k
        end

        if string.find(temp,":") ~= nil then
          temp = string.gsub(temp, ":", "\\:")
        end

        if string.find(temp," ") ~= nil then
          temp = string.gsub(temp, " ", "\\ ")
        end
        s = s .. temp .. "=" .. v .. "\n"
      end
    end
  else
    -- no meta table, just write the contents of the table
    for k,v in ipairs(meta) do
      if string.sub(v,1,1) == "#" then
        -- comment
        s = s .. v .. "\n"
      else
        -- this is for repeating keys in the meta, should not happen but log4j
        -- has some "interesting" config setups
        if t[v] ~= nil then
          -- we first need to escape the special chars
          if string.find(v,"=") ~= nil then
            temp = string.gsub(v, "=", "\\=")
          else
            temp = v
          end

          if string.find(temp,":") ~= nil then
            temp = string.gsub(temp, ":", "\\:")
          end

          if string.find(temp," ") ~= nil then
            temp = string.gsub(temp, " ", "\\ ")
          end

          s = s .. temp .. "=" .. t[v] .. "\n"
          t[v] = nil
        end
      end
    end -- for ipairs in meta

    if t ~= {} and t ~= nil then
      for k,v in pairs(t) do
        temp = ""
        if string.find(k,"=") ~= nil then
          temp = string.gsub(k, "=", "\\=")
        else
          temp = k
        end

        if string.find(temp,":") ~= nil then
          temp = string.gsub(temp, ":", "\\:")
        end

        if string.find(temp," ") ~= nil then
          temp = string.gsub(temp, " ", "\\ ")
        end
        s = s .. temp .. "=" .. v .. "\n"
      end
    end
  end

  --print(s)
  f:write(s)
  f:close()
  return true
end

return propParser
