smart_inventory.skins_mod = minetest.get_modpath("skinsdb")
smart_inventory.armor_mod = minetest.get_modpath("3d_armor")
smart_inventory.clothing_mod = minetest.get_modpath("clothing")

if not smart_inventory.skins_mod and
		not smart_inventory.armor_mod and
		not smart_inventory.clothing_mod then
	return
end

local filter = smart_inventory.filter
local cache = smart_inventory.cache
local ui_tools = smart_inventory.ui_tools
local txt = smart_inventory.txt
local creative = minetest.setting_getbool("creative_mode")

local function update_grid(state, listname)
-- Update the users inventory grid
	local list = {}
	state.param["player_"..listname.."_list"] = list
	local name = state.location.rootState.location.player
	local player = minetest.get_player_by_name(name)
	local invlist_tab = {}
	if listname == "main" then
		local inventory = player:get_inventory()
		local invlist = inventory and inventory:get_list("main")
		table.insert(invlist_tab, invlist)
	else
		if smart_inventory.armor_mod then
			local inventory = minetest.get_inventory({type="detached", name=name.."_armor"})
			local invlist = inventory and inventory:get_list("armor")
			if invlist then
				table.insert(invlist_tab, invlist)
			end
		end
		if smart_inventory.clothing_mod then
			local clothing_meta = player:get_attribute("clothing:inventory")
			state.param.player_clothing_data = clothing_meta and minetest.deserialize(clothing_meta) or {}
			local invlist = {}
			for i=1,6 do
				table.insert(invlist, ItemStack(state.param.player_clothing_data[i]))
			end
			table.insert(invlist_tab, invlist)
		end
	end

	local list_dedup = {}
	for _, invlist in ipairs(invlist_tab) do
		for stack_index, stack in ipairs(invlist) do
			local itemdef = stack:get_definition()
			local is_armor = false
			if itemdef then
				cache.add_item(itemdef) -- analyze groups in case of hidden armor
				if cache.citems[itemdef.name].cgroups["armor"] or cache.citems[itemdef.name].cgroups["clothing"] then
					local entry = {}
					for k, v in pairs(cache.citems[itemdef.name].ui_item) do
						entry[k] = v
					end
					entry.stack_index = stack_index
					local wear = stack:get_wear()
					if wear > 0 then
						entry.text = tostring(math.floor((1 - wear / 65535) * 100 + 0.5)).." %"
					end
					table.insert(list, entry)
					list_dedup[itemdef.name] = itemdef
				end
			end
		end
	end

	-- add all usable in creative available armor to the main list
	if listname == "main" and creative == true then
		if smart_inventory.armor_mod then
			for _, itemdef in pairs(cache.cgroups["armor"].items) do
				if not list_dedup[itemdef.name] and not itemdef.groups.not_in_creative_inventory then
					list_dedup[itemdef.name] = itemdef
					table.insert(list, cache.citems[itemdef.name].ui_item)
				end
			end
		end
		if smart_inventory.clothing_mod then
			for _, itemdef in pairs(cache.cgroups["clothing"].items) do
				if not list_dedup[itemdef.name] and not itemdef.groups.not_in_creative_inventory then
					list_dedup[itemdef.name] = itemdef
					table.insert(list, cache.citems[itemdef.name].ui_item)
				end
			end
		end
	end

	local grid = state:get(listname.."_grid")
	grid:setList(list)
end

local function update_selected_item(state, listentry)
	local name = state.location.rootState.location.player
	state.param.armor_selected_item = listentry or state.param.armor_selected_item
	listentry = state.param.armor_selected_item
	if not listentry then
		return
	end
	local i_list = state:get("i_list")
	i_list:clearItems()
	for _, groupdef in ipairs(ui_tools.get_tight_groups(cache.citems[listentry.itemdef.name].cgroups)) do
		i_list:addItem(groupdef.group_desc)
	end
	state:get("item_name"):setText(listentry.itemdef.description)
	state:get("item_image"):setImage(listentry.item)
end

local function update_page(state)
	local name = state.location.rootState.location.player
	local player_obj = minetest.get_player_by_name(name)
	local skin_obj = smart_inventory.skins_mod and skins.get_player_skin(player_obj)

	-- Update grid lines
	if smart_inventory.armor_mod or smart_inventory.clothing_mod then
		update_grid(state, "main")
		update_grid(state, "overlay")
	end

	-- Update preview area and armor informations list
	if smart_inventory.armor_mod then
		state:get("preview"):setImage(armor.textures[name].preview)
		state.location.parentState:get("player_button"):setImage(armor.textures[name].preview)
		local a_list = state:get("a_list")
		a_list:clearItems()
		for k, v in pairs(armor.def[name]) do
			local grouptext
			if k == "groups" then
				for gn, gv in pairs(v) do
					if txt and txt["armor:"..gn] then
						grouptext = txt["armor:"..gn]
					else
						grouptext = "armor:"..gn
					end
					if grouptext and gv ~= 0 then
						a_list:addItem(grouptext..": "..gv)
					end
				end
			else
				local is_physics = false
				for _, group in ipairs(armor.physics) do
					if group == k then
						is_physics = true
						break
					end
				end
				if is_physics then
					if txt and txt["physics:"..k] then
						grouptext = txt["physics:"..k]
					else
						grouptext = "physics:"..k
					end
					if grouptext and v ~= 1 then
						a_list:addItem(grouptext..": "..v)
					end
				else
					if txt and txt["armor:"..k] then
						grouptext = txt["armor:"..k]
					else
						grouptext = "armor:"..k
					end
					if grouptext and v ~= 0 then
						if k == "state" then
							a_list:addItem(grouptext..": "..tostring(math.floor((1 - v / armor.def[name].count / 65535) * 100 + 0.5)).." %")
						else
							a_list:addItem(grouptext..": "..v)
						end
					end
				end
			end
		end
		update_selected_item(state)
	elseif skin_obj then
		local skin_preview = skin_obj:get_preview()
		state.location.parentState:get("player_button"):setImage(skin_preview)
		state:get("preview"):setImage(skin_preview)
	elseif smart_inventory.clothing_mod then
		update_selected_item(state)
		state.location.parentState:get("player_button"):setImage('inventory_plus_clothing.png')
		state:get("preview"):setImage('blank.png') --TODO: build up clothing preview
	end

	-- Update skins list and skins info area
	if skin_obj then
		local m_name = skin_obj:get_meta_string("name")
		local m_author = skin_obj:get_meta_string("author")
		local m_license = skin_obj:get_meta_string("license")
		if m_name then
			state:get("skinname"):setText("Skin name: "..(skin_obj:get_meta_string("name")))
		else
			state:get("skinname"):setText("")
		end
		if m_author then
			state:get("skinauthor"):setText("Author: "..(skin_obj:get_meta_string("author")))
		else
			state:get("skinauthor"):setText("")
		end
		if m_license then
			state:get("skinlicense"):setText("License: "..(skin_obj:get_meta_string("license")))
		else
			state:get("skinlicense"):setText("")
		end

		-- set the skins list
		state.param.skins_list = skins.get_skinlist_for_player(name)
		local cur_skin = skins.get_player_skin(player_obj)
		local skins_grid_data = {}
		local grid_skins = state:get("skins_grid")
		for idx, skin in ipairs(state.param.skins_list) do
			table.insert(skins_grid_data, {
					image = skin:get_preview(),
					tooltip = skin:get_meta_string("name"),
					is_button = true,
					size = { w = 0.87, h = 1.30 }
			})
			if not state.param.skins_initial_page_adjusted and skin:get_key() == cur_skin:get_key() then
				grid_skins:setFirstVisible(idx - 19) --8x5 (grid size) / 2 -1
				state.param.skins_initial_page_adjusted = true
			end
		end
		grid_skins:setList(skins_grid_data)
	end
end

local function move_item_to_armor(state, item)
	local name = state.location.rootState.location.player
	local player = minetest.get_player_by_name(name)
	local inventory = player:get_inventory()
	local armor_inv = minetest.get_inventory({type="detached", name=name.."_armor"})

	-- get item to be moved to armor inventory
	local itemstack, itemname, itemdef
	if creative == true then
		itemstack = ItemStack(item.item)
		itemname = item.item
	else
		itemstack = inventory:get_stack("main", item.stack_index)
		itemname = itemstack:get_name()
	end
	itemdef = minetest.registered_items[itemname]
	local new_groups = {}
	for _, element in ipairs(armor.elements) do
		if itemdef.groups["armor_"..element] then
			new_groups["armor_"..element] = true
		end
	end

	-- remove all items with the same group
	local removed_items = {}
	for stack_index, stack in ipairs(armor_inv:get_list("armor")) do
		local old_def = stack:get_definition()
		if old_def then
			for groupname, groupdef in pairs(old_def.groups) do
				if new_groups[groupname] then
					table.insert(removed_items, stack)
					armor_inv:set_stack("armor", stack_index, {}) --take old
					minetest.detached_inventories[name.."_armor"].on_take(armor_inv, "armor", stack_index, stack, player)
					if armor_inv:set_stack("armor", stack_index, itemstack) then --put new
						minetest.detached_inventories[name.."_armor"].on_put(armor_inv, "armor", stack_index, itemstack, player)
						itemstack = ItemStack("")
					end
				end
			end
		end
		if stack:is_empty() and not itemstack:is_empty() then
			if armor_inv:set_stack("armor", stack_index, itemstack) then
				minetest.detached_inventories[name.."_armor"].on_put(armor_inv, "armor", stack_index, itemstack, player)
				itemstack = ItemStack("")
			end
		end
	end

	-- handle put backs in non-creative to not lost items
	if creative == false then
		inventory:set_stack("main", item.stack_index, itemstack)
		for _, stack in ipairs(removed_items) do
			stack = inventory:add_item("main", stack)
			if not stack:is_empty() then
				armor_inv:add_item("armor", stack)
			end
		end
	end
end

local function move_item_to_clothing(state, item)
	local name = state.location.rootState.location.player
	local player = minetest.get_player_by_name(name)
	local inventory = player:get_inventory()

	local clothes = state.param.player_clothing_data
	local clothes_ordered = {}

	for i=1, 6 do
		if clothes[i] then
			table.insert(clothes_ordered, clothes[i])
		end
	end

	if #clothes_ordered < 6 then
		table.insert(clothes_ordered, item.item)
		player:set_attribute("clothing:inventory", minetest.serialize(clothes_ordered))
		clothing:set_player_clothing(player)
		state.param.player_clothing_data = clothes_ordered
		-- handle put backs in non-creative to not lost items
		if creative == false then
			local itemstack = inventory:get_stack("main", item.stack_index)
			itemstack:take_item()
			inventory:set_stack("main", item.stack_index, itemstack)
		end
	end
end

local function move_item_to_inv(state, item)
	local name = state.location.rootState.location.player
	local player = minetest.get_player_by_name(name)
	local inventory = player:get_inventory()

	if cache.cgroups["armor"] and cache.cgroups["armor"].items[item.item] then
		local armor_inv = minetest.get_inventory({type="detached", name=name.."_armor"})
		local itemstack = armor_inv:get_stack("armor", item.stack_index)
		if creative == true then
			-- trash armor item in creative
			itemstack = ItemStack("")
		else
			itemstack = inventory:add_item("main", itemstack)
		end
		armor_inv:set_stack("armor", item.stack_index, itemstack)
		minetest.detached_inventories[name.."_armor"].on_take(armor_inv, "armor", item.stack_index, itemstack, player)

	elseif cache.cgroups["clothing"] and cache.cgroups["clothing"].items[item.item] then
		local clothes = state.param.player_clothing_data

		if creative ~= true and clothes[item.stack_index] then
			local itemstack = inventory:add_item("main", ItemStack(clothes[item.stack_index]))
			if itemstack:is_empty() then
				clothes[item.stack_index] = nil
			end
		else
			clothes[item.stack_index] = nil
		end
		player:set_attribute("clothing:inventory", minetest.serialize(clothes))
		clothing:set_player_clothing(player)
	end

end

local function player_callback(state)
	local name = state.location.rootState.location.player
	state:background(0, 1.2, 6, 6.6, "it_bg", "smart_inventory_background_border.png")
	state:item_image(0.8, 1.5,2,2,"item_image","")
	state:label(2.5,1.2,"item_name", "")
	state:listbox(0.8,3.3,5.1,4,"i_list", nil, true)

	state:background(6.2, 1.2, 6, 6.6, "pl_bg", "smart_inventory_background_border.png")
	state:image(6.7,1.7,2,4,"preview","")
	state:listbox(8.6,1.7,3.5,3,"a_list", nil, true)
	state:label(6.7,5.5,"skinname","")
	state:label(6.7,6.0,"skinauthor", "")
	state:label(6.7,6.5, "skinlicense", "")

	state:background(0, 0, 20, 1, "top_bg", "halo.png")
	state:background(0, 8, 20, 2, "bottom_bg", "halo.png")
	if smart_inventory.armor_mod or smart_inventory.clothing_mod then
		local grid_overlay = smart_inventory.smartfs_elements.buttons_grid(state, 0, 0, 20, 1, "overlay_grid")

		grid_overlay:onClick(function(self, state, index, player)
			if state.param.player_overlay_list[index] then
				update_selected_item(state, state.param.player_overlay_list[index])
				move_item_to_inv(state, state.param.player_overlay_list[index])
				update_page(state)
			end
		end)

		local grid_main = smart_inventory.smartfs_elements.buttons_grid(state, 0, 8, 20, 2, "main_grid")
		grid_main:onClick(function(self, state, index, player)
			update_selected_item(state, state.param.player_main_list[index])
			local item = state.param.player_main_list[index]
			if cache.citems[item.item].cgroups["armor"] then
				move_item_to_armor(state, state.param.player_main_list[index])
			elseif cache.citems[item.item].cgroups["clothing"]  then
				move_item_to_clothing(state, state.param.player_main_list[index])
			end
			update_page(state)
		end)
	end

	if smart_inventory.skins_mod then
		local player_obj = minetest.get_player_by_name(name)
		-- Skins Grid
		local grid_skins = smart_inventory.smartfs_elements.buttons_grid(state, 12.9, 1.5, 7 , 7, "skins_grid", 0.80, 1.20)
		state:background(12.4, 1.2, 7.5 , 6.6, "bg_skins", "smart_inventory_background_border.png")
		grid_skins:onClick(function(self, state, index, player)
			local cur_skin = state.param.skins_list[index]
			if state.location.rootState.location.type ~= "inventory" and cur_skin._key:sub(1,17) == "character_creator" then
				state.location.rootState.obsolete = true  -- other screen appears, obsolete the inventory session
			end
			skins.set_player_skin(player_obj, cur_skin)
			if smart_inventory.armor_mod then
				armor.textures[name].skin = cur_skin:get_texture()
				armor:set_player_armor(player_obj)
			end
			update_page(state)
		end)
	end

	-- not visible update plugin for updates from outsite (API)
	state:element("code", { name = "update_hook" }):onSubmit(function(self, state)
		update_page(state)
		state.location.rootState:show()
	end)

	update_page(state)
end

smart_inventory.register_page({
	name = "player",
	icon = "player.png",
	tooltip = "Customize yourself",
	smartfs_callback = player_callback,
	sequence = 20,
	on_button_click = update_page
})

-- register callback in 3d_armor for updates
if smart_inventory.armor_mod and armor.register_on_update then

	local function submit_update_hook(player)
		local name = player:get_player_name()
		local state = smart_inventory.get_page_state("player", name)
		if state then
			state:get("update_hook"):submit()
		end
	end

	armor:register_on_update(submit_update_hook)

	-- There is no callback in 3d_armor for wear change in on_hpchange implementation
	minetest.register_on_player_hpchange(function(player, hp_change)
		minetest.after(0, submit_update_hook, player)
	end)
end
