--[[----------------------------------------------------------------------------
  JsonDecode.lua

  Schlanker JSON-Decoder fuer die kompakte Ausgabe von a7infolr-cli
  (QJsonDocument::Compact). Bewusst kein vollwertiger JSON-Parser, sondern
  zugeschnitten auf das, was QJsonDocument tatsaechlich erzeugt (Objekte,
  Arrays, Strings inkl. \"/\\/\uXXXX-Escapes, Zahlen, true/false/null) -
  schlank und ohne externe Abhaengigkeit, fuer schnelles Parsen kleiner
  Antwort-Dateien (s. Performance-Vorgabe).

  Hinweis: Lightrooms Lua-Laufzeit basiert auf Lua 5.1 - daher bewusst keine
  Bit-Operatoren (>>, &, //), sondern arithmetische Aequivalente.
------------------------------------------------------------------------------]]

local JsonDecode = {}

local find, sub, byte, char = string.find, string.sub, string.byte, string.char
local floor = math.floor

local function skipWhitespace(s, i)
    local _, j = find(s, "^[ \t\r\n]*", i)
    return j + 1
end

local escapeMap = {
    ['"']  = '"',
    ['\\'] = '\\',
    ['/']  = '/',
    ['b']  = '\b',
    ['f']  = '\f',
    ['n']  = '\n',
    ['r']  = '\r',
    ['t']  = '\t',
}

-- Kodiert einen Unicode-Codepoint (aus \uXXXX) als UTF-8-Bytefolge.
-- Auf Bit-Operatoren verzichtet (Lua 5.1-Kompatibilitaet) - stattdessen
-- Division/Modulo. Surrogatpaare (> BMP) treten in a7infolr-cli-Ausgaben
-- nicht auf und werden hier nicht behandelt.
local function utf8FromCodepoint(code)
    if code < 0x80 then
        return char(code)
    elseif code < 0x800 then
        return char(
            192 + floor(code / 64),
            128 + (code % 64))
    else
        return char(
            224 + floor(code / 4096),
            128 + (floor(code / 64) % 64),
            128 + (code % 64))
    end
end

local parseValue -- forward declaration

local function parseString(s, i)
    -- i zeigt auf das oeffnende Anfuehrungszeichen
    local out = {}
    local j = i + 1
    local start = j
    while true do
        local c = byte(s, j)
        if not c then
            error("JsonDecode: unterminated string")
        elseif c == 34 then -- '"'
            out[#out + 1] = sub(s, start, j - 1)
            return table.concat(out), j + 1
        elseif c == 92 then -- '\\'
            out[#out + 1] = sub(s, start, j - 1)
            local esc = sub(s, j + 1, j + 1)
            if esc == "u" then
                local code = tonumber(sub(s, j + 2, j + 5), 16) or 0
                out[#out + 1] = utf8FromCodepoint(code)
                j = j + 6
            else
                out[#out + 1] = escapeMap[esc] or esc
                j = j + 2
            end
            start = j
        else
            j = j + 1
        end
    end
end

local function parseNumber(s, i)
    local _, j, numStr = find(s, "^(%-?%d+%.?%d*[eE]?[%+%-]?%d*)", i)
    return tonumber(numStr), j + 1
end

local function parseArray(s, i)
    local arr = {}
    i = skipWhitespace(s, i + 1)
    if byte(s, i) == 93 then -- ']'
        return arr, i + 1
    end
    while true do
        local value
        value, i = parseValue(s, i)
        arr[#arr + 1] = value
        i = skipWhitespace(s, i)
        local c = byte(s, i)
        if c == 44 then -- ','
            i = skipWhitespace(s, i + 1)
        elseif c == 93 then -- ']'
            return arr, i + 1
        else
            error("JsonDecode: expected ',' or ']'")
        end
    end
end

local function parseObject(s, i)
    local obj = {}
    i = skipWhitespace(s, i + 1)
    if byte(s, i) == 125 then -- '}'
        return obj, i + 1
    end
    while true do
        if byte(s, i) ~= 34 then
            error("JsonDecode: expected string key")
        end
        local key
        key, i = parseString(s, i)
        i = skipWhitespace(s, i)
        if byte(s, i) ~= 58 then -- ':'
            error("JsonDecode: expected ':'")
        end
        i = skipWhitespace(s, i + 1)
        local value
        value, i = parseValue(s, i)
        obj[key] = value
        i = skipWhitespace(s, i)
        local c = byte(s, i)
        if c == 44 then -- ','
            i = skipWhitespace(s, i + 1)
        elseif c == 125 then -- '}'
            return obj, i + 1
        else
            error("JsonDecode: expected ',' or '}'")
        end
    end
end

parseValue = function(s, i)
    i = skipWhitespace(s, i)
    local c = byte(s, i)
    if c == 123 then    -- '{'
        return parseObject(s, i)
    elseif c == 91 then -- '['
        return parseArray(s, i)
    elseif c == 34 then -- '"'
        return parseString(s, i)
    elseif c == 116 and sub(s, i, i + 3) == "true" then
        return true, i + 4
    elseif c == 102 and sub(s, i, i + 4) == "false" then
        return false, i + 5
    elseif c == 110 and sub(s, i, i + 3) == "null" then
        return nil, i + 4
    elseif c == 45 or (c and c >= 48 and c <= 57) then
        return parseNumber(s, i)
    else
        error(string.format("JsonDecode: unexpected character %q at position %d", sub(s, i, i), i))
    end
end

--[[----------------------------------------------------------------------------
  public table
  JsonDecode.decode(string text)

  Parst einen JSON-Text und liefert die entsprechende Lua-Struktur
  (Tabellen fuer Objekte/Arrays, Strings, Zahlen, Booleans).
------------------------------------------------------------------------------]]
function JsonDecode.decode(text)
    local value = parseValue(text, 1)
    return value
end

return JsonDecode
