-- See LICENSE for terms

-- Search through tables for values and display them in an examine dialog

local ChoGGi_Funcs = ChoGGi_Funcs
local pairs, type = pairs, type
local T = T
local Sleep = Sleep
local RetName = ChoGGi_Funcs.Common.RetName
local FindThreadFunc = ChoGGi_Funcs.Common.FindThreadFunc
local RetParamsParents = ChoGGi_Funcs.Common.RetParamsParents
local Translate = ChoGGi_Funcs.Common.Translate

local GetParentOfKind = ChoGGi_Funcs.Common.GetParentOfKind
local function GetRootDialog(dlg)
	return dlg.parent_dialog or GetParentOfKind(dlg, "ChoGGi_DlgFindValue")
end
DefineClass.ChoGGi_DlgFindValue = {
	__parents = {"ChoGGi_XWindow"},
	obj = false,
	obj_name = false,
	dialog_width = 700.0,
	dialog_height = 110.0,

	found_objs = false,
	dupe_objs = {},

	-- check this key when searching
	search_in_progress = false,
	-- for aborting
	search_thread = false,
}

function ChoGGi_DlgFindValue:Init(parent, context)
	local g_Classes = g_Classes

	self.obj = context.obj
	self.obj_name = RetName(self.obj)
	self.title = T(10123--[[Search]]) .. ": " .. self.obj_name
	self.title_image = "CommonAssets/UI/Menu/EV_OpenFirst.tga"
	self.title_image_single = true

	-- By the Power of Grayskull!
	self:AddElements(parent, context)

	self.idTextArea = g_Classes.ChoGGi_XDialogSection:new({
		Id = "idTextArea",
		Dock = "top",
	}, self.idDialog)

	self.idEdit = g_Classes.ChoGGi_XTextInput:new({
		Id = "idEdit",
		Dock = "left",
		MinWidth = 550,
		RolloverText = T{302535920001303--[["Search for text within <color ChoGGi_green><str></color>."]],
			str = self.obj_name,
		},
		Hint = Translate(302535920001306--[[Enter text to find]]),
		OnKbdKeyDown = self.Input_OnKbdKeyDown,
	}, self.idTextArea)

	self.idLimit = g_Classes.ChoGGi_XTextInput:new({
		Id = "idLimit",
		Dock = "right",
		MinWidth = 50,
		RolloverText = T(302535920001304--[[Set how many levels within this table we search. <color ChoGGi_red>Warning</color>: O(n).]]),
		OnKbdKeyDown = self.Input_OnKbdKeyDown,
	}, self.idTextArea)
	self.idLimit:SetText("1")

	self.idButtonContainer = g_Classes.ChoGGi_XDialogSection:new({
		Id = "idButtonContainer",
		Dock = "bottom",
		Margins = box(0, 0, 0, 4),
	}, self.idDialog)

	self.idSearch = g_Classes.ChoGGi_XButton:new({
		Id = "idSearch",
		Dock = "left",
		Text = T(302535920001696--[[Search]]),
		Background = g_Classes.ChoGGi_XButton.bg_green,
		RolloverText = T{302535920001303--[["Search for text within <color ChoGGi_green><str></color>."]],
			str = self.obj_name,
		},
		Margins = box(10, 0, 0, 0),
		OnPress = self.FindText,
	}, self.idButtonContainer)

	self.idCaseSen = g_Classes.ChoGGi_XCheckButton:new({
		Id = "idCaseSen",
		Dock = "left",
		Margins = box(15, 0, 0, 0),
		Text = T(302535920000501--[[Case-sensitive]]),
		RolloverText = T(302535920000502--[[Treat uppercase and lowercase as distinct.]]),
	}, self.idButtonContainer)

	self.idThreads = g_Classes.ChoGGi_XCheckButton:new({
		Id = "idThreads",
		Dock = "left",
		Margins = box(4, 0, 0, 0),
		Text = T(302535920001360--[[Threads]]),
		RolloverText = T(302535920001361--[[Will also search thread func names for value (case is ignored for this).]]),
	}, self.idButtonContainer)

	self.idCancel = g_Classes.ChoGGi_XButton:new({
		Id = "idCancel",
		Dock = "right",
		MinWidth = 80,
		Text = T(3687--[[Cancel]]),
		Background = g_Classes.ChoGGi_XButton.bg_red,
		RolloverText = T(302535920001750--[[Abort search and close dialog.]]),
		Margins = box(0, 0, 10, 0),
		OnPress = self.CloseSearch,
	}, self.idButtonContainer)

	-- new table for each find dialog, so examine only opens once
	self.found_objs = {}

	self.idEdit:SetFocus()

	self:PostInit(context.parent)

	local hint = self.idMoveControl.RolloverText
	hint = T(302535920001305--[[Find text within object, Use Threads checkmark for ""ThreadsRegister"" and other objs.]])
		.. "\n\n" .. hint
	self.idMoveControl.RolloverText = hint
end

--~ function ChoGGi_DlgFindValue:RetStringCase(value, case)
--~ 	local obj_type = type(value)
--~ 	if obj_type == "string" then
--~ 		return case and value or value:lower(), obj_type
--~ 	else
--~ 		return case and tostring(value) or tostring(value):lower(), obj_type
--~ 	end
--~ end

local result_count

function ChoGGi_DlgFindValue:CloseSearch()
	self = GetRootDialog(self)

	DeleteThread(self.search_thread)

	self.idCloseX.OnPress()
end

function ChoGGi_DlgFindValue:FindText()
	self = GetRootDialog(self)

	if self.search_in_progress then

		local function CallBackFunc(answer)
			if not answer then
				DeleteThread(self.search_thread)
				self.search_in_progress = false
			end
		end

		ChoGGi_Funcs.Common.QuestionBox(
			T(302535920001749--[[Search in progress]]) .. ": " .. self.idEdit:GetText(),
			CallBackFunc,
			T(10123--[[Search]]),
			T(6294--[[OK]]),
			T(718--[[Abort]]) .. " " .. T(10123--[[Search]])
		)
		return
	end


	local str = self.idEdit:GetText()
	-- No sense in finding nothing
	if str == "" then
		return
	end

	-- If not case all strings are made lower
	local case = self.idCaseSen:GetCheck()
	if not case then
		str = str:lower()
	end

	-- Always start off empty
	table.clear(self.found_objs)
	table.clear(self.dupe_objs)

	-- Stop new searches
	self.search_in_progress = true
	-- Just in case?
	DeleteThread(self.search_thread)

	self.search_thread = CreateRealTimeThread(function()

		-- build our list of objs
		result_count = 0
		self:RetObjects(
			self.obj,
			self.obj,
			str,
			case,
			self.idThreads:GetCheck(),
			tonumber(self.idLimit:GetText()) or 1
		)

		-- and fire off a new dialog
		local dlg = OpenExamineReturn(self.found_objs, nil, T(302535920000854--[[Results Found]]))

		self.search_in_progress = false

		-- should do this nicer, but whatever
		Sleep(10)
		dlg:SetPos(self:GetPos() + point(0, self.idDialog.box:sizey()))
	end)
end

function ChoGGi_DlgFindValue:RetObjects(obj, parent, str, case, threads, limit, level)
	if not level then
		level = 0
	end

	if level > limit then
		return
	end

	-- should've commented this...

	if type(obj) ~= "table" then
		return
	end

	-- somewhat useful in results (usually off by + - 1)
	result_count = result_count + 1

	local location_str1 = "L" .. level .. " P: " .. RetName(obj) .. "; "
	local location_str2 = ", " .. RetName(parent)

	-- Don't want to lockup ui for long searches even if it takes longer
	Sleep(1)

	local count = 0
	for key, value in pairs(obj) do

		local key_name, value_name = RetName(key), RetName(value)
		local key_str, key_type = case and key_name or key_name:lower(), type(key)
		local value_str, value_type = case and value_name or value_name:lower(), type(value)

		local key_location = location_str1 .. key_name .. location_str2 .. " " .. result_count

		-- :find(str, 1, true) (1, true means don't use lua patterns, just plain text)
		if not self.dupe_objs[obj] and not self.found_objs[key_location]
			and (key_str:find(str, 1, true) or value_str:find(str, 1, true))
		then
			count = count + 1
			if threads then
				self.found_objs[key_location] = key
				self.dupe_objs[key] = key
			else
				self.found_objs[key_location] = obj
				self.dupe_objs[obj] = obj
			end

		elseif threads then
			local value_location = location_str1 .. value_name .. location_str2
			if key_type == "thread" and not self.dupe_objs[key]
				and not self.found_objs[key_location] and FindThreadFunc(key, str)
			then
				self.found_objs[key_location] = key
				self.dupe_objs[key] = key

			elseif value_type == "thread" and not self.dupe_objs[value]
				and not self.found_objs[value_location] and FindThreadFunc(value, str)
			then
				self.found_objs[value_location] = value
				self.dupe_objs[value] = value

			end -- threads
		end

		-- keep on searching
		if key_type == "table" then
			self:RetObjects(key, obj, str, case, threads, limit, level + 1)
		end
		if value_type == "table" then
			self:RetObjects(value, obj, str, case, threads, limit, level + 1)
		end

	end

end

function ChoGGi_DlgFindValue:Input_OnKbdKeyDown(vk)
	self = GetRootDialog(self)
	if vk == const.vkEnter then
		self:FindText()
		return "break"
	elseif vk == const.vkEsc then
		self.idCloseX:Press()
		return "break"
	end

	return g_Classes.ChoGGi_XTextInput.OnKbdKeyDown(self.idEdit, vk)
end

-- Use to open a dialog
function ChoGGi_Funcs.Common.OpenInFindValueDlg(obj, parent, ...)
	if not obj then
		return
	end

	local params, parent_type
	params, parent, parent_type = RetParamsParents(parent, params, ...)

	if not IsKindOf(parent, "XWindow") then
		parent = nil
	end

	params.obj = obj
	params.parent = parent

	return ChoGGi_DlgFindValue:new({}, terminal.desktop, params)
end

