
local pl = {}

local _offset = {x=1, y=0}
local function p(x,y)
    return tostring(x+_offset.x) .. "," .. tostring(y + _offset.y)
end

-- hook into existing formspecs
aom_inventory.register_formspec_process("crafting", "aom_inventory:main", function(flags)
    local crafting_disabled = (minetest.get_modpath("aom_gamemodes")) and not aom_gamemodes.player_has_tag(flags.player, "crafting")
    return ((crafting_disabled and "") or aom_inventory.tcraft_get_formspec(flags.player, flags.method))
end)

aom_inventory.tcraft = {}
aom_inventory.tcraft.tabs_index = {
    {"blocks", "aom_inv_tblocks.png"},
    {"shapes", "aom_inv_tshapes.png"},
    {"furniture", "aom_inv_tfurniture.png"},
    {"food", "aom_inv_tfood.png"},
    {"tools", "aom_inv_ttools.png"},
    {"mechanisms", "aom_inv_tmech.png"},
    {"goods", "aom_inv_tgoods.png"},
    {"decor", "aom_inv_tdecor.png"},
    {"misc", "aom_inv_tmisc.png"},
}

local scroll_start_y = 3 -- base offset
local recipes_per_row = 3
local recipe_scale = 1
local recipe_spacing_y = 1.4 -- extra dist per row
local recipe_spacing_x = 1.7 -- extra dist per column
local scroll_per_formspec_unit = 10 -- magic number
local scroll_per_index = (scroll_per_formspec_unit * recipe_spacing_y) / recipes_per_row

local on_globalstep = function(dtime)
    for i, player in ipairs(minetest.get_connected_players()) do
        if not pl[player] then pl[player] = {scroll = 0, stash = {}} end
        local pi = pl[player]
        if pi.refresh_grace then
            pi.refresh_grace = pi.refresh_grace - dtime
            if pi.refresh_grace < 0 then pi.refresh_grace = nil end
        end
        for k, methodstash in pairs(pi.stash) do
            pi.stash[k].time = methodstash.time - dtime
        end
    end
end
minetest.register_globalstep(on_globalstep)

local function index_to_pos(c)
    return (c / recipes_per_row) * recipe_spacing_y
end

local function index_to_recipe_uid(player, method, index)
    return ((pl[player].craftable[method] or {})[index] or {}).uid
end

local function scroll_position_to_index(scroll)
    return (scroll - scroll_start_y) / scroll_per_index + 5 * recipes_per_row
end

local function scroll_position_to_uid(player, method, scroll)
    local index = math.floor(scroll_position_to_index(scroll) / recipes_per_row) * recipes_per_row
    return index_to_recipe_uid(player, method, index)
end

-- reverses the above
local function index_to_scroll_position(i)
    local ret = i - 5 * recipes_per_row
    ret = ret * scroll_per_index
    return ret + scroll_start_y
end

local function leftpad(str, len, char)
    if string.len(str) >= len then return str end
    local t = {}
    for i = 1, len - string.len(str) do
        t[#t+1] = char
    end
    return table.concat(t, "") .. str
end

local function craft_index_to_fsu_y(k)
    return (
        math.floor((k-1) / recipes_per_row) * (recipe_spacing_y) + 0.3
    )
end
local function craft_index_to_fsu_x(k)
    return (
        ((k-1) % recipes_per_row) * (recipe_spacing_x) + 0.2
    )
end

function aom_inventory.tcraft_get_formspec(player, method, flags)
    if not method then method = "normal" end
    if not flags then flags = {} end
    if not pl[player] then pl[player] = {scroll = 0, stash = {}, craftable = {}} end
    local pi = pl[player]

    if not pi.tab then pi.tab = "blocks" end
    if not pi.stash[method] then
        pi.stash[method] = {time = 1}
    end

    if pi.stash[method].has_creative_fs then
        return pi.stash[method].last_formspec
    end

    if pi.stash[method].time > 0 and pi.stash[method].last_formspec ~= nil then
        return pi.stash[method].last_formspec
    else
        pi.stash[method].time = 1
    end

    local scroll = pi.scroll

    local pli = aom_tcraft.get_player_craftable(player)
    pi.craftable[method] = {}
    local craftable = pi.craftable[method]
    for i, def in ipairs(pli.craftable) do repeat
        if def.method ~= method and (def.method and method ~= "normal") then break end
        if (table.indexof(def.method, method) > 0) and ((not def.tags) or (table.indexof(def.tags, pi.tab) > 0)) then
            craftable[#craftable+1] = def
            -- this scrolls to the last item you were looking at if it's available
            if pi.last_viewed_uid == def.uid then
                scroll = index_to_scroll_position(i) + (pi.scroll_offset or 0)
            end
        end
    until true end

    -- so that the scroll doesn't affect the formspec refresh
    -- TODO: stop doing hacky stuff like this
    scroll = leftpad(tostring(math.floor(scroll)), 16, " ")
    local maxscroll = (#craftable) * (scroll_per_index)

    local scrollcontwidth = (craft_index_to_fsu_x(recipes_per_row)) + recipe_spacing_x + 0.3
    local scrollcontx = -0.5

    local t = {}

    -- background
    table.insert(t, "background9["..p(scrollcontx-0.1, 0.0)..";"..tostring(scrollcontwidth)..","..tostring(12)..
        ";aom_tcraft_panel.png^[multiply:"..(flags.color or "#458").."^aom_tcraft_panel_overlay.png;false;32]")

    -- global styles
    table.insert(t, "style_type[item_image_button;bgcolor=#fff;bgcolor_hovered=#fff;bgcolor_pressed=#fff]")
    table.insert(t, "style_type[item_image_button;textcolor=#8c3f5d;border=false]")

    -- scroll container
    table.insert(t, "scrollbaroptions[arrows=hide;smallstep="..tostring(math.round(scroll_per_index * recipes_per_row))..
        ";thumbsize=100;max="..tostring(maxscroll).."]")
    table.insert(t, "scrollbar["..p(scrollcontx - 0.5, 0.5)..";0.5,10.5;vertical;tcraft_scroll;"..tostring(scroll).."]")
    table.insert(t, "scroll_container["..p(scrollcontx, 0.5)..";"..tostring(scrollcontwidth+recipe_spacing_x)..",11;tcraft_scroll;vertical;0.1]")

    -- styles aom_inv_itemslot_bg
    table.insert(t, "style_type[item_image_button;bgimg=aom_inv_itemslot_bg.png^[multiply:#223^[opacity:170;bgimg_hovered=aom_inv_itemslot_bg.png^[multiply:#987]")

    -- visual for item output
    table.insert(t, "style_type[label;font_size=*1;textcolor=#fff;font=bold]")
    for k, recipe in ipairs(craftable) do
        local spl = string.split(recipe.output, " ", true, 2)
        table.insert(t, "item_image_button["..
            tostring(craft_index_to_fsu_x(k))..","..
            tostring(craft_index_to_fsu_y(k))..";"..recipe_scale..","..recipe_scale..";"..
        (spl[1])..";".."tcraftid_"..tostring(recipe.uid).."_1".."; ]")
        if spl[2] and spl[2] ~= "" and spl[2] ~= "1" then
            table.insert(t, "label["..
                tostring(0.1 + craft_index_to_fsu_x(k))..","..
                tostring(1 - 0.2 + craft_index_to_fsu_y(k))..";"..
                spl[2].."]"
            )
            -- table.insert(t, "image["..
            --     tostring(craft_index_to_fsu_x(k))..","..
            --     tostring(craft_index_to_fsu_y(k) + 1 - 0.4)..";"..(1)..","..(0.4)..";blank.png^[noalpha^[colorize:#11111150:255]")
        end
    end

    -- visual for recipe ingredients
    table.insert(t, "style_type[item_image_button;"..
        "bgimg=aom_tcraft_circle.png^[colorize:#11111100:255;"..
        "bgimg_hovered=aom_tcraft_circle.png^[colorize:#ffeeaa60:255]")
    table.insert(t, "style_type[label;font_size=*0.7;textcolor=#fea;font=normal]")
    local id = 0
    for k, recipe in ipairs(craftable) do repeat
        if recipe.items.unobtainable then break end
        local ingredients = {}
        local oy = 0
        for name, count in pairs(recipe.items) do
            -- item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]
            table.insert(ingredients, "item_image_button["..
                tostring(0.0 - 0.05 + 1.0 + craft_index_to_fsu_x(k))..","..
                tostring(oy + craft_index_to_fsu_y(k))..";0.30,0.30;"..
                name..";tcinf_"..tostring(id).. "; "..--tostring(count > 1 and count or "")..
                "]"
            )

            table.insert(ingredients, "label["..
                tostring(0.3 - 0.05 + 1.0 + craft_index_to_fsu_x(k))..","..
                tostring(oy + 0.2 + craft_index_to_fsu_y(k))..";"..
                tostring(tostring(count > 1 and count or "")).."]"
            )

            oy = oy + 0.2
            id = id + 1
        end
        table.insert(t, table.concat(ingredients))
    until true end

    -- x10 craft button
    table.insert(t, "style_type[image_button;bgimg=aom_tcraft_x10.png;bgimg_hovered=aom_tcraft_x10_hover.png]")
    table.insert(t, "style_type[image_button;border=false]")
    for k, recipe in ipairs(craftable) do
        table.insert(t, "image_button["..
        tostring(-0.1 + craft_index_to_fsu_x(k))..","..
        tostring(-0.1 + craft_index_to_fsu_y(k))..
        ";0.3,0.3;".."blank.png;".."tcraftid_"..tostring(recipe.uid).."_10".."; ]")
    end

    table.insert(t, "scroll_container_end[]")

    -- tabs
    if (method == "normal") then
        table.insert(t, "style_type[image_button;bgimg=aom_inv_tagbg.png;bgimg_hovered=aom_inv_tagbg_hover.png]")
        table.insert(t, "style_type[image_button;bgcolor=#fff;bgcolor_hovered=#fff;bgcolor_pressed=#fff]")
        local tab_pos_y = 0.3
        for i, tex in ipairs(aom_inventory.tcraft.tabs_index) do
            local tabname = tex[1]
            local is_selected = (pi.tab == tabname)
            local size = tostring((is_selected and 1) or 0.9)
            local texture = tex[2]
            if not is_selected then
                texture = texture.."^[colorize:#322947:255"
            else
                texture = "aom_inv_tagbg_sel.png^"..texture
            end
            table.insert(t, "image_button["..p(scrollcontx + scrollcontwidth - 0.2, tab_pos_y))
            table.insert(t, ";"..size..","..size..";")
            table.insert(t, texture..";tab_"..tabname.."; ;false;false;".."]")
            if is_selected then
                tab_pos_y = tab_pos_y + 1.0-0.05
            else
                tab_pos_y = tab_pos_y + 0.9-0.05
            end
        end
    end

    local fs = table.concat(t, "")
    local len = string.len(fs)
    if len ~= pi.stash[method].last_length then
        pi.refresh_grace = 0.5
    end
    pi.stash[method].last_formspec = fs
    pi.stash[method].last_length = len
    if minetest.is_creative_enabled(player:get_player_name()) and (#craftable > 0) then
        pi.stash[method].has_creative_fs = true
    end
    return fs
end

function aom_inventory.on_fields_tcraft(player, formname, fields)
    if not pl[player] then pl[player] = {scroll = 0} end
    local pi = pl[player]
    for field, val in pairs(fields) do
        local _split = string.split(field, "_", false, 3)
        if _split and _split[1] == "tab" then
            local tab = _split[2]
            if pi.tab ~= tab then
                pi.tab = tab
                pi.stash["normal"].last_length = 0
                pi.stash["normal"].time = 0
                pi.stash["normal"].has_creative_fs = false
                pi.stash["normal"].last_formspec = nil
                pi.last_viewed_uid = -1
                pi.scroll = 0
                aom_inventory.player.force_update_formspec(player)
            end
        elseif _split and _split[1] == "tcraftid" and (not pi.refresh_grace) then
            local uid = tonumber(_split[2] or "-1")
            local count = tonumber(_split[3] or "1")
            local success = aom_tcraft.try_to_craft_uid(player, uid, count)
            aom_tcraft.delay_recalc(player)

            if success then
                minetest.sound_play("aom_pickup_item", {
                    gain = 0.3 + math.random() * 0.1,
                    to_player = player:get_player_name(),
                    pitch = 0.8 + math.random() * 0.2
                })
            else
                minetest.sound_play("aom_not_allowed", {
                    gain = 0.2,
                    to_player = player:get_player_name(),
                })
            end
        end
    end
    if fields.tcraft_scroll then
        local scroll_event = minetest.explode_scrollbar_event(fields.tcraft_scroll)
        if scroll_event.type == "CHG" then
            pi.scroll = scroll_event.value
            local method = (formname == "" and "normal") or string.split(formname, ":")[2]
            if not method then
                pi.scroll_offset = 0
                pi.last_viewed_uid = -1
                return
            end
            pi.last_viewed_uid = scroll_position_to_uid(player, method, scroll_event.value)

            local sp = scroll_position_to_index(scroll_event.value) % 1
            pi.scroll_offset = math.floor(sp * scroll_per_index)
            aom_tcraft.delay_recalc(player, 0.1)
        end
    end
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
    if formname ~= "" then return end
    aom_inventory.on_fields_tcraft(player, formname, fields)
end)

