-- Copyright (C) 2010 Tyson Brown
-- 
-- This file is part QuestHelper2, an AddOn for World of Warcraft.
-- 
-- QuestHelper2 is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- 
-- QuestHelper2 is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with QuestHelper2. If not, see <http://www.gnu.org/licenses/>.

if LibDebug then LibDebug() end
local shared = select(2, ...)

-- This function tries to undo the effect of string.format, i.e.,
-- take a formatted string and try to derive the values that were used to create it.

-- This holds a temporary cache of functions for unformatting various patterns.
local cache = setmetatable({}, {__mode="v"})

-- This function filters its arguments through the functions in conv,
-- and then re-arranges them based on swap.
local temp = {}
local function wipeAndReturn(...) table.wipe(temp) return ... end
local function unscramble(swap, conv, ...)
  for i, j in pairs(swap) do
    temp[i] = conv[j]((select(j, ...)))
  end
  
  return wipeAndReturn(unpack(temp, 1, #swap))
end

-- The correct formatter to use in theory is tostring, except that when there's an error,
-- that will end up returning "nil" instead of nil.
local function format_nop(foo) return foo end

-- This function creates a function for unformatting the specified pattern.
local function createUnformatter(pattern)
  local index, swap, conv = 0, {}, {}
  
  local function unpattern(p)
    if p == "%" or p == "(" or p == ")" then
      -- Was an escaped character.
      return "%"..p
    end
    
    index = index + 1
    local b, e, i = p:find("(%d+)%$")
    if i then
      -- Pattern includes an index.
      p = p:sub(1, b-1)..p:sub(e+1)
      i = assert(tonumber(i))
    else
      i = index
    end
    
    swap[i] = index
    
    if p == "d" then
      conv[i] = tonumber
      return "(%-?%d+)"
    end
    
    if p == "f" then
      conv[i] = tonumber
      -- No support for numbers that look like 4.2e+01
      return "(%-?%d+%.?%d*)"
    end
    
    if p == "s" then
      conv[i] = format_nop
      return "(.-)"
    end
    
    -- Don't know how to deal with pattern.
    error("Don't know how to deal with '%"..p.."' in pattern '"..pattern.."'.")
  end
  
  local pat = string.format("^%s$", pattern:gsub("([%(%)])", "%%%1"):gsub("%%(.-[%(%)%%%a])", unpattern))
  
  if index == 0 then
    error("No patterns in '"..pattern.."'.")
  end
  
  for i = 1,index do
    if not swap[i] then
      error("Pattern '"..pattern.."' is missing index #"..i..".")
    end
  end
  
  return function (text)
    return unscramble(swap, conv, string.match(text, pat))
  end
end

function shared.unformat(pattern, text)
  local unformatter = cache[assert(pattern, "Missing pattern.")]
  
  if not unformatter then
    unformatter = createUnformatter(pattern)
    cache[pattern] = unformatter
  end
  
  return unformatter(text)
end
