-- Crafting Mod - semi-realistic crafting in minetest
-- Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library 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
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


local function get_item_description(name)
	if name:sub(1, 6) == "group:" then
		local group = name:sub(7, #name):gsub("%_", " ")
		return "Any " .. group
	else
		local def = minetest.registered_items[name] or {}
		return def.description or name
	end
end

function crafting.make_result_selector(player, type, level, size, context)
	local page = context.crafting_page or 1
  
	local full_recipes = crafting.get_all_for_player(player, type, level)
	local recipes
	if context.crafting_query then
		recipes = {}

		for i = 1, #full_recipes do
			local output = full_recipes[i].recipe.output
			local desc   = get_item_description(output):lower()
			if string.find(output, context.crafting_query, 1, true) or
					string.find(desc, context.crafting_query, 1, true) then
				recipes[#recipes + 1] = full_recipes[i]
			end
		end
	else
		recipes = full_recipes
	end


	local num_per_page = size.x * size.y
	local max_pages = math.floor(0.999 + #recipes / num_per_page)
	local pagedown = "button[-1.4,0.5;0.8,0.8;prev;<]"
	local pageup = "button[-0.8,0.5;0.8,0.8;next;>]"
  if (max_pages == 0) then -- if max_pages is 0 then make it all 0!!! :D
     page = 0
     max_pages = 0
  elseif max_pages == 1 then
     pagedown = ""
     pageup = ""
  elseif page > max_pages or page < 1 then
     page = ((page - 1) % max_pages) + 1
  end
  context.crafting_page = page -- update context.crafting_page to any modifications made by above code

	local start_i  = (page - 1) * num_per_page + 1

	local formspec = {}

	formspec[#formspec + 1] = "container["
	formspec[#formspec + 1] = tostring(size.x)
	formspec[#formspec + 1] = ","
	formspec[#formspec + 1] = tostring(size.y)
	formspec[#formspec + 1] = "]"

	-- Crafted by label
	local creator = context.creator
	if creator and creator ~= '' then
		local creator_offset_x =  (3*(30-string.len(creator))/30/2) - 8
		local craftedby_offset_x = -7.05 -- 3*(30-string.len('crafted by'))/30/2


		formspec[#formspec + 1] = "label["..craftedby_offset_x..",0.45;Crafted by:]"
		formspec[#formspec + 1] = "label["..creator_offset_x..",0.75;"..creator.."]"
	end

	formspec[#formspec + 1] = "style_type[item_image_button;border=false]"
	formspec[#formspec + 1] = "field_close_on_enter[query;false]"
	formspec[#formspec + 1] = "field[-4.75,0.81;3,0.8;query;;"
	formspec[#formspec + 1] = context.crafting_query
	formspec[#formspec + 1] = "]button[-2.2,0.5;0.8,0.8;search;?]"
	formspec[#formspec + 1] = pagedown
	formspec[#formspec + 1] = pageup

	formspec[#formspec + 1] = "container_end[]"


	formspec[#formspec + 1] = "label[0,-0.25;"
	formspec[#formspec + 1] = minetest.formspec_escape("Page: " ..
			page .. "/" .. max_pages ..
			" | Unlocked: " .. #full_recipes .. " / " .. #crafting.recipes[type])
	formspec[#formspec + 1] = "]"

	local x = 0
	local y = 0
	local y_offset = 0.2
	for i = start_i, math.min(#recipes, start_i * num_per_page)  do
	   local result = recipes[i]
	   if result then
		local recipe = result.recipe

		local itemname = ItemStack(recipe.output):get_name()
		local item_description = get_item_description(itemname)

		formspec[#formspec + 1] = "item_image_button["
		formspec[#formspec + 1] = x
		formspec[#formspec + 1] = ","
		formspec[#formspec + 1] = y + y_offset
		formspec[#formspec + 1] = ";1,1;"
		formspec[#formspec + 1] = recipe.output
		formspec[#formspec + 1] = ";result_"
		formspec[#formspec + 1] = tostring(recipe.id)
		formspec[#formspec + 1] = ";]"

		formspec[#formspec + 1] = "tooltip[result_"
		formspec[#formspec + 1] = tostring(recipe.id)
		formspec[#formspec + 1] = ";"
		formspec[#formspec + 1] = minetest.formspec_escape(item_description .. "\n")
		for j, item in pairs(result.items) do
			local color = item.have >= item.need and "#6f6" or "#f66"
			local itemtab = {
				"\n",
				minetest.get_color_escape_sequence(color),
				get_item_description(item.name), ": ",
				item.have, "/", item.need
			}
			formspec[#formspec + 1] = minetest.formspec_escape(table.concat(itemtab, ""))
		end
		formspec[#formspec + 1] = minetest.get_color_escape_sequence("#ffffff")
		formspec[#formspec + 1] = "]"

		formspec[#formspec + 1] = "image["
		formspec[#formspec + 1] = x
		formspec[#formspec + 1] = ","
		formspec[#formspec + 1] = y + y_offset
		if result.craftable then
			formspec[#formspec + 1] = ";1,1;crafting_slot_craftable.png]"
		else
			formspec[#formspec + 1] = ";1,1;crafting_slot_uncraftable.png]"
		end

		x = x + 1
		if x == size.x then
			x = 0
			y = y + 1
		end
		if y == size.y then
			break
		end
	   end
	end

	while y < size.y do
		while x < size.x do
			formspec[#formspec + 1] = "image["
			formspec[#formspec + 1] = tostring(x)
			formspec[#formspec + 1] = ","
			formspec[#formspec + 1] = tostring(y + y_offset)
			formspec[#formspec + 1] = ";1,1;crafting_slot_empty.png]"

			x = x + 1
		end
		x = 0
		y = y + 1
	end

	return table.concat(formspec, "")
end

local function sanitize(badstring)
   local disallowed = { "\\", "{", "}",
			--lua magic characters
			"%(", "%)", "%[", "%]", "%.", "%$",
			"%^", "%%", "%+", "%-", "%*", "%?"  }
   badstring:trim():lower()
   for i in ipairs(disallowed) do
      badstring = badstring:gsub(disallowed[i],"")
   end
   return badstring
end

function crafting.result_select_on_receive_results(player, type, level, context, fields)
	if fields.prev then
		context.crafting_page = (context.crafting_page or 1) - 1
		return true
	elseif fields.next then
		context.crafting_page = (context.crafting_page or 1) + 1
		return true
	elseif fields.search or fields.key_enter_field == "query" then
		context.crafting_query = sanitize(fields.query)
		context.crafting_page  = 1
		if context.crafting_query == "" then
			context.crafting_query = nil
		end
		return true
	end

	for key, value in pairs(fields) do
		if key:sub(1, 7) == "result_" then
			local num = string.match(key, "result_([0-9]+)")
			if num then
				local inv    = player:get_inventory()
				local recipe = crafting.get_recipe(tonumber(num))
				local name   = player:get_player_name()
				if not crafting.can_craft(name, type, level, recipe) then
					minetest.log("error", "[crafting] Player clicked a button they shouldn't have been able to")
					return true
				elseif crafting.perform_craft(name, inv, "main", "main", recipe) then
					return true -- crafted
				else
					minetest.chat_send_player(name, "Missing required items!")
					return false
				end
			end
		end
	end
end

if minetest.global_exists("sfinv") then
	sfinv.override_page("sfinv:crafting", {
		get = function(self, player, context)
			local formspec = crafting.make_result_selector(player, "inv", 1, { x = 8, y = 3 }, context)
			formspec = formspec .. "list[detached:creative_trash;main;0,3.4;1,1;]" ..
					"image[0.05,3.5;0.8,0.8;creative_trash_icon.png]"
			return sfinv.make_formspec(player, context, formspec, true)
		end,
		on_player_receive_fields = function(self, player, context, fields)
			if crafting.result_select_on_receive_results(player, "inv", 1, context, fields) then
				sfinv.set_player_inventory_formspec(player)
			end
			return true
		end
	})
end

local node_fs_context = {}
local node_serial = 0

function crafting.make_on_rightclick(type, level, inv_size)
	node_serial = node_serial + 1
	local formname = "crafting:node_" .. node_serial

	local function show(player, context)
		local formspec = crafting.make_result_selector(player, type, level, inv_size, context)
		formspec = "size[" .. inv_size.x  .. "," .. (inv_size.y + 5.6) ..
				"]list[current_player;main;0," .. (inv_size.y + 1.7) ..";8,1;]" ..
				"list[current_player;main;0," .. (inv_size.y + 2.85) ..";8,3;8]" .. formspec
		minetest.show_formspec(player:get_player_name(), formname, formspec)
	end

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

		local context = node_fs_context[player:get_player_name()]
		if not context then
			return false
		end

		if crafting.result_select_on_receive_results(player, type, level, context, fields) then
			show(player, context)
		end
		return true
	end)

	return function(pos, node, player)
		local meta = pos and minetest.get_meta(pos)
		local name = player:get_player_name()
		local context = node_fs_context[name] or {}
		if context.type ~= type then context = {} end
		node_fs_context[name] = context
		context.pos   = vector.new(pos)
		context.type  = type
		context.level = level
		context.creator = (meta and meta:get_string('creator')) or name

		show(player, context)
	end
end
