
--
--  Overachiever - Tabs: Search.lua
--    by Tuhljin
--
--  If you don't wish to use the search tab, feel free to delete this file or rename it (e.g. to Search_unused.lua).
--  The addon's other features will work regardless.
--

local L = OVERACHIEVER_STRINGS
local GetAchievementInfo = Overachiever.GetAchievementInfo
local GetAchievementCriteriaInfo = Overachiever.GetAchievementCriteriaInfo

local categories_sel = Overachiever.UI_GetValidCategories(1)
local EditBoxes = {}


-- Set this to "true" (without quotes) to make Search tab edit boxes lose their input focus when a search begins.
local OPTION_LoseFocusOnSearch = false


local function copytab(from, to)
  for k,v in pairs(from) do
    if(type(v) == "table") then
      to[k] = {}
      copytab(v, to[k]);
    else
      to[k] = v;
    end
  end
end

local function tabcontains(tab, element)
  for k,v in pairs(tab) do
    if (v == element) then  return true;  end
  end
  return false
end

local function AchSearch(isCustomList, searchList, ...)
  if (not searchList) then
    return Overachiever.SearchForAchievement(nil, categories_sel, ...)
  end
  return Overachiever.SearchForAchievement(isCustomList, searchList, ...)
end

local function findCriteria(id, pattern)
  local critString, foundCrit
  --print("findCriteria " .. id .. " " .. pattern)
  for i=1,GetAchievementNumCriteria(id) do
    critString = GetAchievementCriteriaInfo(id, i)
    foundCrit = strfind(strlower(critString), pattern, 1, true)
    if (foundCrit) then  return true;  end
  end
end

local found

local function AchSearch_Criteria(list, pattern, results)
  pattern = strlower(pattern)
  found = found and wipe(found) or {}
  local anyFound
  if (list) then
    for i,id in ipairs(list) do
      if (findCriteria(id, pattern)) then
        found[#found+1] = id
        anyFound = true
      end
    end
  else
    local id
    for _,cat in ipairs(categories_sel) do
      for i=1,GetCategoryNumAchievements(cat) do
        id = GetAchievementInfo(cat, i)
        if (id and findCriteria(id, pattern)) then
          found[#found+1] = id
          anyFound = true
        end
      end
    end
  end
  if (anyFound) then
    if (results ~= found) then
      results = results and wipe(results) or {}
      copytab(found, results)
    end
    return results
  end
end

local multifound

local function AchSearch_multiple(list, pattern, results, ...)
  found = found or {}
  multifound = multifound and wipe(multifound) or {}
  local anyFound, argnum, foundB
  for i=1, select("#", ...) do
    argnum = select(i, ...)
    foundB = AchSearch(true, list, argnum, pattern, nil, true, found)
    if (foundB) then  -- Theoretically faster than checking if #(found) < 1.
      for _,id in ipairs(found) do
        multifound[id] = true  -- With this method, we won't get duplicate IDs.
      end
      anyFound = true
    end
  end
  foundB = AchSearch_Criteria(list, pattern, found)
  if (foundB) then
    for _,id in ipairs(found) do
      multifound[id] = true
    end
    anyFound = true
  end
  if (anyFound) then
    results = results and wipe(results) or {}
    for id in pairs(multifound) do  -- pairs, not ipairs
      results[#results+1] = id
    end
    return results
  end
end


local VARS
local frame, panel, sortdrop
local EditAny, EditName, EditDesc, EditCriteria, EditReward
local SubmitBtn, ResetBtn, ResultsLabel
local FullListCheckbox
local typedrop

local function SortDrop_OnSelect(self, value)
  VARS.SearchSort = value
  frame.sort = value
  frame:ForceUpdate(true)
end

local function TypeDrop_OnSelect(self, value)
  VARS.SearchType = value
  categories_sel = Overachiever.UI_GetValidCategories(value)
  frame.guildView_default = value == 2 and true or nil
end

local function FullList_OnClick(self)
  if (self:GetChecked()) then
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
    VARS.SearchFullList = true
  else
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF);
    VARS.SearchFullList = false
  end
end

local function OnLoad(v)
  VARS = v
  sortdrop:SetSelectedValue(VARS.SearchSort or 0)
  typedrop:SetSelectedValue(VARS.SearchType or 1)
  if (VARS.SearchFullList) then  FullListCheckbox:SetChecked(true);  end
end

frame, panel = Overachiever.BuildNewTab("Overachiever_SearchFrame", L.SEARCH_TAB,
                 "Interface\\AddOns\\Overachiever_Tabs\\SearchWatermark", L.SEARCH_HELP, OnLoad)

sortdrop = TjDropDownMenu.CreateDropDown("Overachiever_SearchFrameSortDrop", panel, {
  {
    text = L.TAB_SORT_NAME,
    value = 0
  },
  {
    text = L.TAB_SORT_COMPLETE,
    value = 1
  },
  {
    text = L.TAB_SORT_POINTS,
    value = 2
  },
  {
    text = L.TAB_SORT_ID,
    value = 3
  };
})
sortdrop:SetLabel(L.TAB_SORT, true)
sortdrop:SetPoint("TOPLEFT", panel, "TOPLEFT", -16, -22)
sortdrop:OnSelect(SortDrop_OnSelect)

local function beginSearch(self)
  PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
  
  if (OPTION_LoseFocusOnSearch) then
    for i,editbox in ipairs(EditBoxes) do
      _G[editbox]:SetAutoFocus(false)
      _G[editbox]:ClearFocus()
    end
  end

  local name, desc, criteria, reward, any = EditName:GetText(), EditDesc:GetText(), EditCriteria:GetText(), EditReward:GetText(), EditAny:GetText()
  if (name == "" and desc == "" and criteria == "" and reward == "" and any == "") then  -- all fields are blank
    ResultsLabel:Hide()
    return;
  end
  local list = VARS.SearchFullList and Overachiever.GetAllAchievements(categories_sel) or nil
  local results = frame.AchList
  if (list ~= 0 and name ~= "") then  -- Check name first since it may be a numeric ID (which eliminates all other achievements) and it helps make the "OR" logic here simpler (name OR id) since we won't have to consider results-so-far
    list = AchSearch(true, list, 2, name, nil, true, results) or 0
    local id = tonumber(name)
	if (not id and name:sub(1, 1) == "#") then  id = tonumber(name:sub(2));  end  -- Get ID from strings like "#123"
    if (id) then
      if (GetAchievementInfo(id)) then
        if (list == 0) then
          list = { id }
        elseif (not tabcontains(list, id)) then
          list[#list+1] = id
        end
        if (list ~= results) then
          wipe(results)
          copytab(list, results)
        end
      end
    end
  end
  if (list ~= 0 and reward ~= "") then  -- Rewards next since there are few of these so it may narrow the list quickly
    list = AchSearch(true, list, 11, reward, nil, true, results) or 0
  end
  if (list ~= 0 and desc ~= "") then
    list = AchSearch(true, list, 8, desc, nil, true, results) or 0
  end
  if (list ~= 0 and criteria ~= "") then
    list = AchSearch_Criteria(list, criteria, results) or 0
  end
  if (list ~= 0 and any ~= "") then
    list = AchSearch_multiple(list, any, results, 11, 2, 8) or 0
  end
  if (list == 0 or not list) then  wipe(results);  end
  Overachiever_SearchFrameContainerScrollBar:SetValue(0)
  frame:ForceUpdate(true)
  ResultsLabel:Show()
end

function frame.SetNumListed(num)
  ResultsLabel:SetText(L.SEARCH_RESULTS:format(num))
end


local function tabPressed(self)
  self:SetAutoFocus(false)
  EditBox_HandleTabbing(self, EditBoxes)
end

local function escapeEditBox(self)
  self:SetAutoFocus(false)
end

local function focusEditBox(self, clearAll)
  for i,editbox in ipairs(EditBoxes) do
    _G[editbox]:SetAutoFocus(false)
  end
  self:SetAutoFocus(true)
end

local function createEditBox(name, labeltext, obj, x, y)
  name = "Overachiever_SearchFrame"..name.."Edit"
  local editbox = CreateFrame("EditBox", name, panel, "InputBoxTemplate")
  editbox:SetAutoFocus(false)
  editbox:SetWidth(170); editbox:SetHeight(16)
  editbox:SetPoint("TOPLEFT", obj, "BOTTOMLEFT", x or 0, y or -23)
  local label = editbox:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
  label:SetPoint("BOTTOMLEFT", editbox, "TOPLEFT", -6, 4)
  label:SetText(labeltext)
  editbox:SetScript("OnTabPressed", tabPressed)
  editbox:SetScript("OnEnterPressed", beginSearch)
  editbox:HookScript("OnEscapePressed", escapeEditBox)
  editbox:HookScript("OnEditFocusGained", focusEditBox)
  EditBoxes[#EditBoxes+1] = name  -- We use the name rather than a frame because names are used by EditBox_HandleTabbing.
  return editbox
end

EditName = createEditBox("Name", L.SEARCH_NAME, sortdrop, 22, -19)
EditDesc = createEditBox("Desc", L.SEARCH_DESC, EditName)
EditCriteria = createEditBox("Criteria", L.SEARCH_CRITERIA, EditDesc)
EditReward = createEditBox("Reward", L.SEARCH_REWARD, EditCriteria)
EditAny = createEditBox("Any", L.SEARCH_ANY, EditReward)
createEditBox = nil


typedrop = TjDropDownMenu.CreateDropDown("Overachiever_SearchFrameTypeDrop", panel, {
  {
    text = L.SEARCH_TYPE_ALL,
    value = 0
  },
  {
    text = L.SEARCH_TYPE_INDIV,
    value = 1
  },
  {
    text = L.SEARCH_TYPE_GUILD,
    value = 2
  };
})
typedrop:SetLabel(L.SEARCH_TYPE, true)
typedrop:SetPoint("LEFT", sortdrop, "LEFT")
typedrop:SetPoint("TOP", EditAny, "BOTTOM", 0, -21)
typedrop:OnSelect(TypeDrop_OnSelect)


FullListCheckbox = CreateFrame("CheckButton", "Overachiever_SearchFrameFullListCheckbox", panel, "InterfaceOptionsCheckButtonTemplate")
--FullListCheckbox:SetPoint("TOPLEFT", EditAny, "BOTTOMLEFT", -8, -12)
FullListCheckbox:SetPoint("LEFT", EditAny, "LEFT", -8, 0)
FullListCheckbox:SetPoint("TOP", typedrop, "BOTTOM", 0, -4)
Overachiever_SearchFrameFullListCheckboxText:SetText(L.SEARCH_FULLLIST)
Overachiever_SearchFrameFullListCheckboxText:SetJustifyH("LEFT")
FullListCheckbox:SetHitRectInsets(0, -1 * min(Overachiever_SearchFrameFullListCheckboxText:GetWidth() + 8, 155), 0, 0)
FullListCheckbox:SetScript("OnClick", FullList_OnClick)
FullListCheckbox:SetScript("OnEnter", function(self)
  GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
  GameTooltip:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
  GameTooltip:SetText(L.SEARCH_FULLLIST_TIP, nil, nil, nil, nil, 1)
end)
FullListCheckbox:SetScript("OnLeave", GameTooltip_Hide)

local function resetEditBoxes()
  PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
  for i,editbox in ipairs(EditBoxes) do
    _G[editbox]:SetText("")
  end
end

SubmitBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
SubmitBtn:SetWidth(75); SubmitBtn:SetHeight(21)
SubmitBtn:SetPoint("TOPLEFT", FullListCheckbox, "BOTTOMLEFT", 2, -8)
SubmitBtn:SetText(L.SEARCH_SUBMIT)
SubmitBtn:SetScript("OnClick", beginSearch)

ResetBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
ResetBtn:SetWidth(75); ResetBtn:SetHeight(21)
ResetBtn:SetPoint("LEFT", SubmitBtn, "RIGHT", 4, 0)
ResetBtn:SetText(L.SEARCH_RESET)
ResetBtn:SetScript("OnClick", resetEditBoxes)

ResultsLabel = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
ResultsLabel:SetPoint("TOPLEFT", SubmitBtn, "BOTTOMLEFT", 0, -8)
ResultsLabel:Hide()


--[[
function Overachiever.Debug_GetIDRange()
  local gap, i, id, firstid, lastid = 0, 0
  repeat
    i = i + 1
    firstid = GetAchievementInfo(i)
    assert(i < 1000)
  until (firstid)
  repeat
    i = i + 1
    id = GetAchievementInfo(i)
    if (id) then  gap, lastid = 0, id;  else  gap = gap + 1;  end
  until (gap > 1000)
  print("ID range: {", firstid, ",", lastid, "}")
end
-- Last check: WoW 3.2: { 6 , 4316 }
--]]
