
local GetTime = QuestHelper_GetTime

QuestHelper_File["collect_lzw.lua"] = "4.0.1.$svnversion$"
QuestHelper_Loadtime["collect_lzw.lua"] = GetTime()

local Merger
local Bitstream

local function cleanup(tab)
  for _, v in pairs(tab) do
    QuestHelper:ReleaseTable(v)
  end
  QuestHelper:ReleaseTable(tab)
end

local function QH_LZW_Decompress(input, tokens, outbits, inputstatic)
  local d = QuestHelper:CreateTable("lzw")
  local i
  for i = 0, tokens-1 do
    d[i] = QuestHelper:CreateTable("lzw")
    d[i][0] = string.char(i)
  end
  
  local dsize = tokens + 1  -- we use the "tokens" value as an EOF marker
  
  if inputstatic then
    local used = QuestHelper:CreateTable("lzw")
    for _, v in ipairs(inputstatic) do
      for i = #v, 2, -1 do
        local subi = v:sub(1, i)
        if not used[subi] then
          used[subi] = true
          
          d[bit.mod(dsize, tokens)][math.floor(dsize / tokens)] = subi
          dsize = dsize + 1
        else
          break
        end
      end
    end
    QuestHelper:ReleaseTable(used)
  end
  
  local bits = 1
  local nextbits = 2
  
  while nextbits < dsize do bits = bits + 1; nextbits = nextbits * 2 end
  
  local i = Bitstream.Input(input, outbits)
  local rv = {}
  
  local idlect = 0
  
  local tok = i:depend(bits)
  if tok == tokens then cleanup(d) return "" end -- Okay. There's nothing. We get it.
  
  Merger.Add(rv, d[bit.mod(tok, tokens)][math.floor(tok / tokens)])
  local w = d[bit.mod(tok, tokens)][math.floor(tok / tokens)]
  while true do
    if idlect == 25 then
      QH_Timeslice_Yield()
      idlect = 0
    else
      idlect = idlect + 1
    end
    
    dsize = dsize + 1 -- We haven't actually added the next element yet. However, we could in theory include it in the stream, so we need to adjust the number of bits properly.
    if dsize > nextbits then
      bits = bits + 1
      nextbits = nextbits * 2
    end
    
    tok = i:depend(bits)
    if tok == tokens then break end -- we're done!
    
    local entry
    if d[bit.mod(tok, tokens)][math.floor(tok / tokens)] then
      entry = d[bit.mod(tok, tokens)][math.floor(tok / tokens)]
    elseif tok == dsize - 1 then
      entry = w .. w:sub(1, 1)
    else
      QuestHelper: Assert(false, "faaaail")
    end
    Merger.Add(rv, entry)
    
    d[bit.mod(dsize - 1, tokens)][math.floor((dsize - 1) / tokens)] = w .. entry:sub(1, 1) -- Naturally, we're writing to one *less* than dsize, since we already incremented.
    
    w = entry
  end
  
  cleanup(d)
  
  return Merger.Finish(rv)
end

local function QH_LZW_Compress(input, tokens, outbits, inputstatic)
  -- shared init code
  local d = {}
  local i
  for i = 0, tokens-1 do
    d[string.char(i)] = {[""] = i}
  end
  
  local dsize = tokens + 1  -- we use the "tokens" value as an EOF marker
  
  if inputstatic then
    for _, v in ipairs(inputstatic) do
      local da = d[v:sub(1, 1)]
      for i = #v, 2, -1 do
        local b = v:sub(2, i)
        if not da[b] then
          da[b] = dsize
          dsize = dsize + 1
        else
          break
        end
      end
    end
  end
  
  local bits = 1
  local nextbits = 2
  
  while nextbits < dsize do bits = bits + 1; nextbits = nextbits * 2 end
  
  local r = Bitstream.Output(outbits)
  
  local idlect = 0
  
  local w = ""
  for ci = 1, #input do
    if idlect == 100 then
      QH_Timeslice_Yield()
      idlect = 0
    else
      idlect = idlect + 1
    end
    
    local c = input:sub(ci, ci)
    local wcp = w .. c
    if d[wcp:sub(1, 1)][wcp:sub(2)] then
      w = wcp
    else
      r:append(d[w:sub(1, 1)][w:sub(2)], bits)
      d[wcp:sub(1, 1)][wcp:sub(2)] = dsize
      dsize = dsize + 1
      if dsize > nextbits then
        bits = bits + 1
        nextbits = nextbits * 2
      end
      w = c
    end
  end
  
  if w ~= "" then
    r:append(d[w:sub(1, 1)][w:sub(2)], bits)
  
    dsize = dsize + 1   -- Our decompressor doesn't realize we're ending here, so it will have added a table entry for that last token. Sigh.
    if dsize > nextbits then
      bits = bits + 1
      nextbits = nextbits * 2
    end
  end
  
  r:append(tokens, bits)
  
  local rst = r:finish()
  QuestHelper: Assert(QH_LZW_Decompress(rst, tokens, outbits, inputstatic) == input) -- yay
  
  return rst
end

local function mdict(inputdict)
  local idc = QuestHelper:CreateTable("lzw mdict")
  for i = 1, #inputdict do if math.fmod(i, 100) == 0 then QH_Timeslice_Yield() end idc[inputdict:sub(i, i)] = strchar(i - 1) end
  return idc
end

local function dictize(idcl, stt)
  local im = QuestHelper:CreateTable("lzw dictize")
  for i = 1, #stt do
    local subl = idcl[stt:sub(i, i)]
    QuestHelper: Assert(subl)
    Merger.Add(im, subl)
  end
  local result = Merger.Finish(im)
  QuestHelper:ReleaseTable(im)
  return result
end

local function QH_LZW_Prepare(inputdict, inputstatic)
  QuestHelper: Assert(inputdict and inputstatic)
  
  local idc = mdict(inputdict)
  
  local inpstat = {}
  local ct = 0
  for _, v in ipairs(inputstatic) do
    ct = ct + 1
    if ct == 10 then QH_Timeslice_Yield() ct = 0 end
    table.insert(inpstat, dictize(idc, v))
  end
  
  return idc, #inputdict, inpstat
end

local function QH_LZW_Compress_Dicts_Prepared(input, idprepped, idpreppedsize, outputdict, isprepped)
  input = dictize(idprepped, input)
  
  local bits, dsize = 1, 2
  if not outputdict then bits = 8 else while dsize < #outputdict do bits = bits + 1 ; dsize = dsize * 2 end end
  QuestHelper: Assert(not outputdict or #outputdict == dsize)
  
  local comp = QH_LZW_Compress(input, idpreppedsize, bits, isprepped)
  
  if outputdict then
    local origcomp = comp
    local im = {}
    for i = 1, #origcomp do Merger.Add(im, outputdict:sub(strbyte(origcomp:sub(i, i)) + 1)) end
    comp = Merger.Finish(im)
  end
  
  return comp
end

local function QH_LZW_Compress_Dicts(input, inputdict, outputdict, inputstatic)
  local inproc = input
  local inpstat = inputstatic
  if inputdict then
    local idc = mdict(inputdict)
    
    inproc = dictize(idc, input)
    
    if inputstatic then
      inpstat = {}
      for _, v in ipairs(inputstatic) do
        table.insert(inpstat, dictize(idc, v))
      end
    end
    
    QuestHelper:ReleaseTable(idc)
  end
  
  local bits, dsize = 1, 2
  if not outputdict then bits = 8 else while dsize < #outputdict do bits = bits + 1 ; dsize = dsize * 2 end end
  QuestHelper: Assert(not outputdict or #outputdict == dsize)
  
  local comp = QH_LZW_Compress(inproc, inputdict and #inputdict or 256, bits, inpstat)
  
  if outputdict then
    local origcomp = comp
    local im = {}
    for i = 1, #origcomp do Merger.Add(im, outputdict:sub(strbyte(origcomp:sub(i, i)) + 1)) end
    comp = Merger.Finish(im)
  end
  
  return comp
end

local function QH_LZW_Decompress_Dicts_Prepared(compressed, inputdict, outputdict, ispreppred) -- this is kind of backwards - we assume that "outputdict" is the dictionary that "compressed" is encoded in
  QuestHelper: Assert(not outputdict)
  QuestHelper: Assert(inputdict)
  
  local decomp = QH_LZW_Decompress(compressed, #inputdict, 8, ispreppred)
  
  local ov = {}
  for i = 1, #decomp do
    Merger.Add(ov, inputdict:sub(decomp:byte(i) + 1, decomp:byte(i) + 1))
  end
  return Merger.Finish(ov)
end

local function QH_LZW_Decompress_Dicts(compressed, inputdict, outputdict, inputstatic) -- this is kind of backwards - we assume that "outputdict" is the dictionary that "compressed" is encoded in
  QuestHelper: Assert(not outputdict)
  QuestHelper: Assert(inputdict)
  
  local inpstat = inputstatic
  if inputdict and inputstatic then
    local idc = mdict(inputdict)
  
    inpstat = {}
    for _, v in ipairs(inputstatic) do
      table.insert(inpstat, dictize(idc, v))
    end
    
    QuestHelper:ReleaseTable(idc)
  end
  
  local decomp = QH_LZW_Decompress(compressed, #inputdict, 8, inpstat)
  
  local ov = {}
  for i = 1, #decomp do
    Merger.Add(ov, inputdict:sub(decomp:byte(i) + 1, decomp:byte(i) + 1))
  end
  return Merger.Finish(ov)
end

QH_LZW_Prepare_Arghhacky = QH_LZW_Prepare -- need to rig up a better mechanism for this really
QH_LZW_Decompress_Dicts_Arghhacky = QH_LZW_Decompress_Dicts -- need to rig up a better mechanism for this really
QH_LZW_Decompress_Dicts_Prepared_Arghhacky = QH_LZW_Decompress_Dicts_Prepared -- need to rig up a better mechanism for this really

function QH_Collect_LZW_Init(_, API)
  Merger = API.Utility_Merger
  QuestHelper: Assert(Merger)
  
  Bitstream = API.Utility_Bitstream
  QuestHelper: Assert(Bitstream)
  
  API.Utility_LZW = {Compress = QH_LZW_Compress, Decompress = QH_LZW_Decompress, Compress_Dicts = QH_LZW_Compress_Dicts, Decompress_Dicts = QH_LZW_Decompress_Dicts, Prepare = QH_LZW_Prepare, Compress_Dicts_Prepared = QH_LZW_Compress_Dicts_Prepared, Decompress_Dicts_Prepared = QH_LZW_Decompress_Dicts_Prepared}
end

-- old debug code :)
  
--[[  
print("hello")

QH_LZW_Compress("TOBEORNOTTOBEORTOBEORNOT", 256, 8)
]]

--[[
QuestHelper:TextOut("lulz")

local inq = "ABABABABA"
local alpha = 253
local bits = 7

str = QH_LZW_Compress(inq, alpha, bits)
tvr = ""
for i = 1, #str do
  tvr = tvr .. string.format("%d ", strbyte(str, i))
end
QuestHelper:TextOut(tvr)

ret = QH_LZW_Decompress(str, alpha, bits)
QuestHelper:TextOut(ret)

QuestHelper: Assert(inq == ret)
]]
