

local ANY_NON_BLANK = "~"
local ENFORCE_ID_SEARCH_IN_LIST = false


local ACHINFO_NAME = 2

local MULTI_SEARCH = {
	2, -- Name
	8, -- Description
	11, -- Reward
	"crit"
}


local GetAchievementInfo = GetAchievementInfo


local function isInTable(tab, item)
	for i,v in ipairs(tab) do
		if (v == item) then  return true;  end
	end
	return false
end

local function getList(excludeHidden, achType)
	if (excludeHidden) then
		if (achType == "p") then
			return TjAchieve.GetStandardPersonalAchIDs()
		elseif (achType == "g") then
			return TjAchieve.GetStandardGuildAchIDs()
		elseif (achType == "o") then
			return {}
		else
			return TjAchieve.GetStandardAchIDs()
		end
	else
		if (achType == "p") then
			return TjAchieve.GetPersonalAchIDs()
		elseif (achType == "g") then
			return TjAchieve.GetGuildAchIDs()
		elseif (achType == "o") then
			return TjAchieve.GetOtherAchIDs()
		else
			return TjAchieve.GetAchIDs()
		end
	end
end
--Overachiever.AchSearchGetList = getList

function Overachiever.AchSearch(argnum, query, excludeHidden, achType, strictCase, limit)
	local list = getList(excludeHidden, achType)
	return Overachiever.AchSearchFromList(list, argnum, query, strictCase, limit)
end

function Overachiever.AchSearchFromList(list, argnum, query, strictCase, limit)
	--if (limit and type(limit) ~= "number") then  error("Invalid call to Overachiever.AchSearchFromList(). Limit must be a number if not nil/false.");  end
	local qtype = type(query)
	if (not strictCase and qtype == "string") then  query = strlower(query);  end
	local results, numFound = {}, 0
    for i,id in ipairs(list) do
		local ret = select(argnum, GetAchievementInfo(id))
		if (qtype == type(ret)) then
			local found = false
			if (query == ret) then
				found = true
			elseif (qtype == "string") then
				if (not strictCase) then  ret = strlower(ret);  end
				if ((query == ANY_NON_BLANK and ret ~= "") or strfind(ret, query, 1, true)) then
					found = true
				end
			end
			if (found) then
				numFound = numFound + 1
				results[numFound] = id
				if (limit and numFound >= limit) then  break;  end
			end
		end
	end
	return results
end

function Overachiever.AchSearchNameOrID(query, excludeHidden, achType, strictCase, limit)
	local list = getList(excludeHidden, achType)
	return Overachiever.AchSearchNameOrIDFromList(list, query, strictCase, limit)
end

function Overachiever.AchSearchNameOrIDFromList(list, query, strictCase, limit)
	if (not query or query == "")  then return {};  end
	local id = tonumber(query)
	if (not id and query:sub(1, 1) == "#") then  id = tonumber(query:sub(2));  end  -- Get ID from strings like "#123"
	if (id) then
		if (not GetAchievementInfo(id) or (ENFORCE_ID_SEARCH_IN_LIST and not isInTable(list, id))) then
			id = nil
		end
	end
	if (id and limit == 1) then  return { id };  end

	local results = Overachiever.AchSearchFromList(list, ACHINFO_NAME, query, strictCase, limit)
	if (id) then  tinsert(results, 1, id);  end
	return results
end


function Overachiever.GetFirstVisibleAchFromList(list)
	for i,id in ipairs(list) do
		if (TjAchieve.IsAchievementVisible(id)) then  return id, i;  end
	end
	return nil, nil
end


function Overachiever.OpenSearchToNameOrID(name)
	--if (not AchievementFrame and CanShowAchievementUI()) then
	--	AchievementFrame_LoadUI()
	--end
	Overachiever.OpenTab("Overachiever_SearchFrame")
	if (Overachiever.OpenSearchTab) then
		Overachiever.OpenSearchTab(name)
		return true
	end
	return false
end



-- THROTTLED
--------------

local function handleSubthread(func, ...)
	-- Make sure you don't pass any nil arguments to this. They don't play nicely with unpack. (Instead, use something like false when possible.)
	local WAITING = {}
	local results = WAITING
	local args = { ... }
	args[#args + 1] = function(output)
		results = output
	end
	func(unpack(args))
	local task = TjThreads.GetLatestTask()
	coroutine.yield(TjThreads.QUICKYIELD)
	while (results == WAITING) do
		if (not TjThreads.IsTaskActive(task)) then
			error("Subthread has terminated without yielding expected results. Check previous error in log.")
		end
		coroutine.yield(TjThreads.QUICKYIELD)
	end
	return results
end


local function createMultiSearchFunc(list, query, strictCase, listener)
	return function()
		local results_all, results_lookup = {}, {}
		local numFound = 0
		for i,argnum in ipairs(MULTI_SEARCH) do
			local results
			if (argnum == ACHINFO_NAME) then
				results = Overachiever.AchSearchNameOrIDFromList(list, query, strictCase)
			elseif (argnum == "crit") then
				coroutine.yield()
				local q = query
				if (query == ANY_NON_BLANK) then  q = TjAchieve.ANY_NON_BLANK;  end
				results = handleSubthread(TjAchieve.StartSearchCriteriaByID, q, list)
			else
				results = Overachiever.AchSearchFromList(list, argnum, query, strictCase)
			end
			for i,id in ipairs(results) do
				if (not results_lookup[id]) then
					numFound = numFound + 1
					results_all[numFound] = id
					results_lookup[id] = true
				end
			end
		end
		listener(results_all)
	end
end

function Overachiever.StartAchSearchMulti(query, excludeHidden, achType, strictCase, listenerFunc)
	-- Search names, descriptions, and rewards
	local list = getList(excludeHidden, achType)
	Overachiever.StartAchSearchMultiFromList(list, query, strictCase, listenerFunc)
end

function Overachiever.StartAchSearchMultiFromList(list, query, strictCase, listenerFunc)
	local func = createMultiSearchFunc(list, query or "", strictCase, listenerFunc)
	TjThreads.AddTask(func)
end


local function createFullSearchFunc(list, strictCase, nameOrID, desc, criteria, reward, any, listener)
	return function()
		if (nameOrID ~= "") then
			-- Check name first since it may be a numeric ID (which eliminates all other achievements)
			list = Overachiever.AchSearchNameOrIDFromList(list, nameOrID, strictCase)
			coroutine.yield()
		end
		if (reward ~= "") then
			-- Rewards next since there are relatively few of these so it may narrow the list quickly
			list = Overachiever.AchSearchFromList(list, 11, reward, strictCase)
			coroutine.yield()
		end
		if (desc ~= "") then
			list = Overachiever.AchSearchFromList(list, 8, desc, strictCase)
			coroutine.yield()
		end
		if (#list > 0 and criteria ~= "") then
			if (criteria == ANY_NON_BLANK) then  criteria = TjAchieve.ANY_NON_BLANK;  end
			list = handleSubthread(TjAchieve.StartSearchCriteriaByID, criteria, list)
		end
		if (#list > 0 and any ~= "") then
			list = handleSubthread(Overachiever.StartAchSearchMultiFromList, list, any, strictCase or false)
		end
		listener(list)
	end
end

function Overachiever.StartAchSearch(excludeHidden, achType, strictCase, nameOrID, desc, criteria, reward, any, listenerFunc)
	local list = getList(excludeHidden, achType)
	local func = createFullSearchFunc(list, strictCase, nameOrID or "", desc or "", criteria or "", reward or "", any or "", listenerFunc)
	TjThreads.AddTask(func)
end
-- /run Overachiever.StartAchSearch(nil, nil, nil, "food", "food", "", "", "")



--[[
function Overachiever.Debug_CriteriaSearch(query, excludeHidden, achType)
	local list = getList(excludeHidden, achType)
	local results = TjAchieve.SearchCriteriaByIDNow(query, list)
	if (results) then
		print("Found:")
		for i,id in ipairs(results) do
			print(i,id)
		end
	else
		print("Nothing found.")
	end
end
--]]
