local function trim(str, chars)
    if chars == nil then
        chars = "%s*"
    end
    return string.match(str, "^"..chars.."(.-)"..chars.."$")
end

local function split(str, delim)
    if delim == nil then
        delim = "\n"
    end
    local t = {}
    for s in string.gmatch(str, "([^"..delim.."]+)") do
        table.insert(t, trim(s))
    end
    return t
end

local function find_multiple(str, patterns, offset)
    local ws = string.len(str)
    local we = we
    local wpattern = nil
    for i, pattern in pairs(patterns) do
        s, e = string.find(str, pattern, offset)
        if s ~= nil then
            if s < ws then
                ws = s
                we = e
                wpattern = pattern
            end
        end
    end
    return ws, we, wpattern
end

local function balanced_end(str, word, offset)
    if offset == nil then
        offset = 1
    end
    local i = offset
    while true do
        local s, e, p
        if word == "then" then
            s, e, p = find_multiple(str, {"%smatch%s", "%sfunction%s", "%sthen%s", "%sdo%s", "%send%s", "%selseif%s"}, i)
        else
            s, e, p = find_multiple(str, {"%smatch%s", "%sfunction%s", "%sthen%s", "%sdo%s", "%send%s"}, i)
        end
        if p == "%send%s" then
            return e
        elseif p == "%selseif%s" then
            return e
        elseif p == nil then
            return "UNBAL"
        end
        i = balanced_end(str, string.sub(p, 3, -3), e)
        if i == "UNBAL" then
            return i
        end
    end
end

local function get_decls(str)
    -- gather data declarations from source & remove
    local datas = {}
    local i = 0
    local strout = ""
    while true do
        local n, a = string.find(str, "%sdata [%w]+", i+1)
        if n == nil then
            strout = strout .. string.sub(str, i+1)
            break
        end
        strout = strout .. string.sub(str, i+1, n)
        local e, d = string.find(str, "end", i+1)
        local cont = string.sub(str, a+1, e-1)
        local data = {}
        for i, case in ipairs(split(cont)) do
            local c = {}
            local b, p = string.find(case, "%(")
            if b == nil then
                c.name = case
                c.args = 0   
            else
                c.name = string.sub(case, 1, p-1)
                c.args = #split(case, ",")
            end
            table.insert(data, c)
        end
        i = d
        table.insert(datas, data)
    end
    return datas, strout
end

local parseexprs, replace_case, replace_match
local function parseexpr(str)
    local n, o, name, body = string.find(str, "(%w+)(%b())")
    if n == nil then
        local b = string.find(str, ",")
        if b == nil then
            return {type="var",name=str}, ""
        else
            return {type="var", name=string.sub(str, 0, b-1)}, string.sub(str,b+1)
        end
    end
    body = string.sub(body, 2, -2)
    local obj = {type="data", name=name, body=parseexprs(body)}
    local rem = string.sub(str, o+1)
    local b = string.find(rem, ",")
    if b == nil then
        return obj, ""
    else
        return obj, string.sub(rem,b+1)
    end
end

parseexprs = function(str)
    local t = {}
    while str ~= "" do
        local obj
        obj, str = parseexpr(str)
        table.insert(t, obj)
    end
    return t
end

local function getCase(datas, data)
    for i, x in ipairs(datas) do
        for i, case in ipairs(x) do
            if case.name == data then
                return i
            end
        end
    end
end

local function comparison(datas, var, pattern)
    if pattern.type == "data" then
        local out = var .. ".case == " .. getCase(datas, pattern.name)
        for i, x in ipairs(pattern.body) do
            if x.type == "data" then
                out = out .. " and " .. comparison(datas, var .. "[" .. i .. "]", x)
            end
        end
        return out
    else
        return "true"
    end
end

local function destructure(datas, var, pattern)
    if pattern.type == "var" then
        return "local " .. pattern.name .. " = " .. var
    else
        local out = ""
        for i, x in ipairs(pattern.body) do
            out = out .. "\n" .. destructure(datas, var .. "[" .. i .. "]", x)
        end
        return out
    end
end

replace_match = function(datas, str)
    while true do
        local m, b, var = string.find(str, "%smatch (%w+)%s")
        if m == nil then
            return str
        end
        local e = balanced_end(str, "match", b)
        local cont = string.sub(str, b, e-4)
        str = string.sub(str, 1, m) .. replace_case(datas, cont, var) .. string.sub(str, e, -1)
    end
end

replace_case = function(datas, out, var)
    local str = out
    local count = 0
    while true do
        local m
        local b
        local p
        m, b, p = string.find(str, "%scase ([^%s]+) do%s")
        if m == nil then
            for i=1,count do
                str = str .. "\nend"
            end
            return str
        end
        count = count + 1
        local e = balanced_end(str, "do", b)
        local cont = string.sub(str, b, e-5)
        local pattern = parseexpr(p)
        local bool = comparison(datas, var, pattern)
        local body = destructure(datas, var, pattern)
        str = string.sub(str, 1, m) .. "\nif " .. bool .. " then\n" .. body .. cont .. "\nelse\n" .. string.sub(str, e, -1)
    end
end

local function printpattern(pattern)
    if pattern.type == "data" then
        io.write(pattern.name)
        io.write("(")
        for i, v in ipairs(pattern.body) do
            printpattern(v)
            io.write(",")
        end
        io.write(")")
    else
        io.write(pattern.name)
    end
end

local function writeheaders(datas)
    local out = ""
    for i, x in ipairs(datas) do
        for i, case in ipairs(x) do
            out = out .. "\nlocal function " .. case.name .. "(...) return {case=" .. i .. ",...} end"
        end
    end
    return out
end

function preprocess(str)
    local decls, str0 = get_decls(str)
    local b = replace_match(decls, str0)
    local h = writeheaders(decls)
    return h .. b
end