-- ------------------------------------------------------------------------------ --
--                            TradeSkillMaster_Shopping                           --
--            http://www.curse.com/addons/wow/tradeskillmaster_shopping           --
--                                                                                --
--             A TradeSkillMaster Addon (http://tradeskillmaster.com)             --
--    All Rights Reserved* - Detailed license information included with addon.    --
-- ------------------------------------------------------------------------------ --

local TSM = select(2, ...)
local Search = TSM:NewModule("Search", "AceEvent-3.0", "AceHook-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Shopping") -- loads the localization table
local private = {}

-- ------------------------------------------------ --
--					GUI Creation functions					 --
-- ------------------------------------------------ --

function Search:Show(frame)
	TSM.Util:SetParent(frame)
	private.searchBar = private.searchBar or private:CreateSearchBar(frame)
	private.searchBar:Show()
	private.searchBar.editBox:SetFocus()
	private.searchBar.editBox:SetText("")
	private.searchBar:Enable()
	private.searchBar.normal:Click()
	TSM.Sidebar:Show(frame)
	if TSM.db.global.sidebarBtn > #private.searchBar.buttons then
		TSM.db.global.sidebarBtn = 0
	end
	if TSM.db.global.sidebarBtn > 0 then
		if not private.searchBar.buttons[TSM.db.global.sidebarBtn].isSelected then
			private.searchBar.buttons[TSM.db.global.sidebarBtn]:Click()
		end
	else
		TSM.Sidebar:Hide()
	end
end

function Search:Hide()
	if not private.searchBar then return end
	private.searchBar:Hide()
	TSM.Util:HideSearchFrame()
	TSMAPI.AuctionControl:HideControlButtons()
	TSMAPI.AuctionScan:StopScan()
	TSM.Sidebar:Hide()
end

function private:CreateSearchBar(parent)
	local function StartSearch(searchQuery)
		if private.mode == "normal" then
			Search:StartFilterSearch(searchQuery)
		elseif private.mode == "destroy" then
			private.searchBar.editBox:SetText(searchQuery)
			local filters = Search:GetFilters(searchQuery)
			if filters and #filters == 1 then
				for itemString, name in pairs(TSM.db.global.destroyingTargetItems) do
					if strlower(name) == strlower(filters.currentFilter) then
						return TSM.Destroying:StartDestroyingSearch(itemString, filters[1])
					end
				end
				TSM:Printf(L["Invalid target item for destroy search: '%s'"], filters.currentFilter)
			else
				TSM:Printf(L["Invalid destroy search: '%s'"], searchQuery)
			end
		end
	end

	local function HandleModifiedItemClick(link)
		local putIntoChat = Search.hooks.HandleModifiedItemClick(link)
		if not putIntoChat and private.searchBar:IsVisible() and not private.searchBar.isDisabled and TSMAPI:AHTabIsVisible("Shopping") then
			local name = TSMAPI:GetSafeItemInfo(link)
			if name then
				StartSearch(name.."/exact")
				return true
			end
		end
		return putIntoChat
	end
	local function InsertLink(link)
		local putIntoChat = Search.hooks.ChatEdit_InsertLink(link)
		if not putIntoChat then
			if private.searchBar:IsVisible() and not private.searchBar.isDisabled and TSMAPI:AHTabIsVisible("Shopping") then
				local name = TSMAPI:GetSafeItemInfo(link)
				if name then
					StartSearch(name.."/exact")
					return true
				end
			end
		end
		return putIntoChat
	end
	Search:RawHook("ChatEdit_InsertLink", InsertLink, true)
	Search:RawHook("HandleModifiedItemClick", HandleModifiedItemClick, true)

	local function OnChar(self)
		local text = self:GetText()
		if private.mode == "normal" then
			for i=1, #TSM.db.global.previousSearches do
				local prevSearch = strlower(TSM.db.global.previousSearches[i])
				if strsub(prevSearch, 1, #text) == strlower(text) then
					self:SetText(prevSearch)
					self:HighlightText(#text, -1)
					break
				end
			end
		elseif private.mode == "destroy" then
			for _, name in pairs(TSM.db.global.destroyingTargetItems) do
				name = strlower(name)
				if strsub(name, 1, #text) == strlower(text) then
					self:SetText(name)
					self:HighlightText(#text, -1)
					break
				end
			end
		end
	end
	
	local function OnEditFocusGained(self)
		self:HighlightText()
	end
	
	local function OnEditFocusLost(self)
		self:HighlightText()
	end
	
	local function OnUpdate(self)
		if self:IsEnabled() and not TSMAPI:AHTabIsVisible("Shopping") then
			self:ClearFocus()
		end
	end
	
	local function OnEnter(self)
		GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
		GameTooltip:SetMinimumWidth(400)
		GameTooltip:AddLine(L["Enter what you want to search for in this box. You can also use the following options for more complicated searches."].."\n", 1, 1, 1, 1)
		GameTooltip:AddLine(format("|cffffff00"..L["Multiple Search Terms:|r You can search for multiple things at once by simply separated them with a ';'. For example '%selementium ore; obsidium ore|r' will search for both elementium and obsidium ore."].."\n", TSMAPI.Design:GetInlineColor("link2")), 1, 1, 1, 1)
		GameTooltip:AddLine(format("|cffffff00"..L["Inline Filters:|r You can easily add common search filters to your search such as rarity, level, and item type. For example '%sarmor/leather/epic/85/i350/i377|r' will search for all leather armor of epic quality that requires level 85 and has an ilvl between 350 and 377 inclusive. Also, '%sinferno ruby/exact|r' will display only raw inferno rubys (none of the cuts)."].."\n", TSMAPI.Design:GetInlineColor("link2"), TSMAPI.Design:GetInlineColor("link2")), 1, 1, 1, 1)
		GameTooltip:Show()
	end

	
	local searchBarFrame = CreateFrame("Frame", nil, parent)
	searchBarFrame:SetAllPoints()
	searchBarFrame:Hide()

	local eb = TSMAPI.GUI:CreateInputBox(searchBarFrame)
	eb:SetPoint("TOPLEFT", 85, -5)
	eb:SetHeight(22)
	eb:SetWidth(600)
	eb:SetScript("OnShow", eb.SetFocus)
	eb:SetScript("OnEnterPressed", function() StartSearch(private.searchBar.editBox:GetText()) end)
	eb:SetScript("OnChar", OnChar)
	eb:SetScript("OnEditFocusGained", OnEditFocusGained)
	eb:SetScript("OnEditFocusLost", OnEditFocusLost)
	eb:SetScript("OnEnter", OnEnter)
	eb:SetScript("OnLeave", function() GameTooltip:Hide() end)
	eb:SetScript("OnUpdate", OnUpdate)
	searchBarFrame.editBox = eb
	
	local btn = TSMAPI.GUI:CreateButton(searchBarFrame, 20)
	btn:SetPoint("TOPLEFT", eb, "TOPRIGHT", 4, 0)
	btn:SetPoint("BOTTOMLEFT", eb, "BOTTOMRIGHT", 4, 0)
	btn:SetWidth(85)
	btn:SetText(SEARCH)
	btn:SetScript("OnClick", function() StartSearch(private.searchBar.editBox:GetText()) end)
	searchBarFrame.button = btn
	
	local btn = TSMAPI.GUI:CreateButton(searchBarFrame, 16)
	btn:SetPoint("TOPLEFT", searchBarFrame.button, "TOPRIGHT", 4, 0)
	btn:SetPoint("BOTTOMLEFT", searchBarFrame.button, "BOTTOMRIGHT", 4, 0)
	btn:SetPoint("TOPRIGHT", -5, -5)
	btn:SetText(L["Stop"])
	btn:Disable()
	btn:SetScript("OnClick", function() TSM.Util:StopScan() searchBarFrame:Enable() end)
	searchBarFrame.stop = btn
	
	local function OnModeChange(self)
		searchBarFrame.normal:UnlockHighlight()
		searchBarFrame.destroy:UnlockHighlight()
		self:LockHighlight()
		if self.mode ~= private.mode then
			private.mode = self.mode
			private:UpdateMode()
		end
	end
	
	local btn = TSMAPI.GUI:CreateButton(searchBarFrame, 14)
	btn:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", 0, -8)
	btn:SetHeight(16)
	btn:SetWidth(110)
	btn:SetText(L["Normal Mode"])
	btn:SetScript("OnClick", OnModeChange)
	btn.mode = "normal"
	btn.tooltip = L["When in normal mode, you may run simple and filtered searches of the auction house."]
	searchBarFrame.normal = btn
	
	local btn = TSMAPI.GUI:CreateButton(searchBarFrame, 14)
	btn:SetPoint("TOPLEFT", searchBarFrame.normal, "BOTTOMLEFT", 0, -4)
	btn:SetPoint("TOPRIGHT", searchBarFrame.normal, "BOTTOMRIGHT", 0, -4)
	btn:SetHeight(16)
	btn:SetText(L["Destroy Mode"])
	btn:SetScript("OnClick", OnModeChange)
	btn.mode = "destroy"
	btn.tooltip = L["When in destroy mode, you simply enter a target item (ink/pigment, enchanting mat, gem, etc) into the search box to search for everything you can destroy to get that item."]
	searchBarFrame.destroy = btn
	
	local line = TSMAPI.GUI:CreateHorizontalLine(searchBarFrame, 0)
	line:ClearAllPoints()
	line:SetHeight(4)
	line:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", 120, -7)
	line:SetPoint("TOPRIGHT", 0, -34)
	
	local pagesLabel = TSMAPI.GUI:CreateLabel(searchBarFrame)
	pagesLabel:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", 125, -15)
	pagesLabel:SetHeight(20)
	pagesLabel:SetJustifyH("CENTER")
	pagesLabel:SetJustifyV("CENTER")
	pagesLabel:SetText(L["Sidebar Pages:"])
	pagesLabel:SetWidth(pagesLabel:GetWidth() + 5)
	
	local buttons = {}
	local function OnClick(self)
		self.isSelected = not self.isSelected
		for _, btn in ipairs(buttons) do
			btn:UnlockHighlight()
			if btn ~= self then
				btn.isSelected = false
			end
		end
		if self.isSelected then
			TSM.db.global.sidebarBtn = self.index
			self:LockHighlight()
			TSM.Sidebar:Show()
		else
			TSM.db.global.sidebarBtn = 0
			self:UnlockHighlight()
			TSM.Sidebar:Hide()
		end
		TSM.Sidebar:ButtonClick(self.label)
	end
	local pages, callbacks = TSM.Sidebar:GetPages()
	for i, label in ipairs(pages) do
		local btn = TSMAPI.GUI:CreateButton(searchBarFrame, 16)
		btn:SetPoint("TOPLEFT", buttons[i-1] or pagesLabel, "TOPRIGHT", 5, 0)
		btn:SetHeight(20)
		btn:SetText(label)
		btn:SetWidth(ceil(btn:GetTextWidth()+10))
		btn:SetScript("OnClick", OnClick)
		btn.label = label
		btn.index = i
		buttons[i] = btn
	end
	searchBarFrame.buttons = buttons
	
	local line = TSMAPI.GUI:CreateHorizontalLine(searchBarFrame, 0)
	line:ClearAllPoints()
	line:SetHeight(4)
	line:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", 120, -40)
	line:SetPoint("TOPRIGHT", 0, -67)
	
	local line = TSMAPI.GUI:CreateVerticalLine(searchBarFrame, 0)
	line:ClearAllPoints()
	line:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", 120, -7)
	line:SetHeight(33)
	line:SetWidth(4)
	
	searchBarFrame.Disable = function(self)
		self.isDisabled = true
		self.editBox:ClearFocus()
		self.editBox:HighlightText(0, 0)
		self.editBox:Disable()
		self.button:Disable()
		self.stop:Enable()
	end
	searchBarFrame.Enable = function(self)
		self.isDisabled = nil
		self.editBox:Enable()
		self.button:Enable()
		self.stop:Disable()
	end
	
	return searchBarFrame
end

function private:UpdateMode()
	private.searchBar.editBox:SetText("")
	if private.mode == "nomal" then
		TSM.Util:ShowSearchFrame(nil, L["% Market Value"], true)
	elseif private.mode == "destroy" then
		TSM.Util:ShowSearchFrame(true, L["% Target Value"], true)
	end
	TSM.Util:StopScan()
	private.searchBar:Enable()
end

local function ScanCallback(event, ...)
	if TSM.searchCallback then
		TSM.searchCallback(event)
	end
	if event == "filter" then
		local filter = ...
		return filter.maxPrice
	elseif event == "process" then
		local itemString, auctionItem = ...
		if auctionItem.query.maxPrice then
			auctionItem:FilterRecords(function(record)
					return (record:GetItemBuyout() or 0) > auctionItem.query.maxPrice
				end)
		end
		if TSM.isCrafting then
			local func = TSMAPI:ParseCustomPrice("matprice")
			local price = func and func(itemString) or nil
			auctionItem:SetMarketValue(price)
		else
			auctionItem:SetMarketValue(TSM:GetMaxPrice(TSM.db.global.marketValueSource, itemString))
		end
		return auctionItem
	elseif event == "done" then
		private.searchBar:Enable()
		return
	end
end

function Search:StartFilterSearch(filter, callback, isCrafting)
	TSM.isCrafting = isCrafting
	TSM.searchCallback = callback
	if strfind(filter, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?%-?([0-9]*)$") or strfind(filter, "battlepet:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)$") then
		filter = TSMAPI:GetSafeItemInfo(filter) or filter
	end
	if TSM.isCrafting then
		TSM.Util:ShowSearchFrame(nil, L["% Mat Price"])
	else
		TSM.Util:ShowSearchFrame(nil, L["% Market Value"])
	end
	private.searchBar.editBox:SetText(filter)
	private.searchBar:Disable()
	if filter ~= "" then
		for i=#TSM.db.global.previousSearches, 1, -1 do
			if strlower(TSM.db.global.previousSearches[i]) == strlower(filter) then
				tremove(TSM.db.global.previousSearches, i)
			end
		end
		tinsert(TSM.db.global.previousSearches, 1, filter)
		while #TSM.db.global.previousSearches > 100 do
			tremove(TSM.db.global.previousSearches)
		end
		TSM.Sidebar:UpdateCurrentFrame()
	end
	local isItemList, itemList = true, {}
	for _, searchTerm in ipairs({(";"):split(filter)}) do
		if tonumber(searchTerm) then
			tinsert(itemList, TSMAPI:GetItemString(searchTerm))
		else
			isItemList = nil
		end
	end
	if isItemList then
		TSM.Util:StartItemScan(itemList, ScanCallback)
	else
		TSM.Util:StartFilterScan(Search:GetFilters(filter), ScanCallback)
	end
end

function Search:SetSearchBarDisabled(disabled)
	if disabled then
		private.searchBar:Disable()
	else
		private.searchBar:Enable()
	end
end

function Search:SetMode(mode)
	if mode == "normal" then
		private.searchBar.normal:Click()
	elseif mode == "destroy" then
		private.searchBar.destroy:Click()
	end
end

function Search:SetSearchText(text)
	private.searchBar.editBox:SetText(text)
end


-- ------------------------------------------------ --
--				Search Filter Util functions				 --
-- ------------------------------------------------ --

local function GetMaxQuantity(str)
	if #str > 1 and strsub(str, 1, 1) == "x" then
		return tonumber(strsub(str, 2))
	end
end

local function GetItemLevel(str)
	if #str > 1 and strsub(str, 1, 1) == "i" then
		return tonumber(strsub(str, 2))
	end
end

local function GetItemClass(str)
	for i, class in ipairs({GetAuctionItemClasses()}) do
		if strlower(str) == strlower(class) then
			return i
		end
	end
end

local function GetItemSubClass(str, class)
	if not class then return end

	for i, subClass in ipairs({GetAuctionItemSubClasses(class)}) do
		if strlower(str) == strlower(subClass) then
			return i
		end
	end
end

local function GetItemRarity(str)
	for i=0, 4 do
		local text =  _G["ITEM_QUALITY"..i.."_DESC"]
		if strlower(str) == strlower(text) then
			return i
		end
	end
end

local function GetSearchFilterOptions(searchTerm)
	local parts = {("/"):split(searchTerm)}
	local queryString, class, subClass, minLevel, maxLevel, minILevel, maxILevel, rarity, usableOnly, exactOnly, evenOnly, maxQuantity, maxPrice
	
	if #parts == 1 then
		return true, parts[1]
	elseif #parts == 0 then
		return false, L["Invalid Filter"]
	end
	
	for i, str in ipairs(parts) do
		str = str:trim()
		
		if tonumber(str) then
			if not minLevel then
				minLevel = tonumber(str)
			elseif not maxLevel then
				maxLevel = tonumber(str)
			else
				return false, L["Invalid Min Level"]
			end
		elseif GetMaxQuantity(str) then
			if not maxQuantity then
				maxQuantity = GetMaxQuantity(str)
			else
				return false, L["Invalid Max Quantity"]
			end
		elseif GetItemLevel(str) then
			if not minILevel then
				minILevel = GetItemLevel(str)
			elseif not maxILevel then
				maxILevel = GetItemLevel(str)
			else
				return false, L["Invalid Item Level"]
			end
		elseif not class and GetItemClass(str) then
			if not class then
				class = GetItemClass(str)
			else
				return false, L["Invalid Item Type"]
			end
		elseif GetItemSubClass(str, class) then
			if not subClass then
				subClass = GetItemSubClass(str, class)
			else
				return false, L["Invalid Item SubType"]
			end
		elseif GetItemRarity(str) then
			if not rarity then
				rarity = GetItemRarity(str)
			else
				return false, L["Invalid Item Rarity"]
			end
		elseif strlower(str) == "usable" then
			if not usableOnly then
				usableOnly = 1
			else
				return false, L["Invalid Usable Only Filter"]
			end
		elseif strlower(str) == "exact" then
			if not exactOnly then
				exactOnly = 1
			else
				return false, L["Invalid Exact Only Filter"]
			end
		elseif strlower(str) == "even" then
			if not evenOnly then
				evenOnly = 1
			else
				return false, L["Invalid Even Only Filter"]
			end
		elseif TSMAPI:UnformatTextMoney(str) then
			maxPrice = TSMAPI:UnformatTextMoney(str)
		elseif i == 1 then
			if strfind(str, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?%-?([0-9]*)$") or strfind(str, "battlepet:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)$") then
				queryString = TSMAPI:GetSafeItemInfo(str)
			else
				queryString = str
			end
		else
			return false, L["Unknown Filter"]
		end
	end
	
	if maxLevel and minLevel and maxLevel < minLevel then
		local oldMaxLevel = maxLevel
		maxLevel = minLevel
		minLevel = oldMaxLevel
	end
	
	if maxILevel and minILevel and maxILevel < minILevel then
		local oldMaxILevel = maxILevel
		maxILevel = minILevel
		minILevel = oldMaxILevel
	end
	
	return true, queryString or "", class or 0, subClass or 0, minLevel or 0, maxLevel or 0, minILevel or 0, maxILevel or 0, rarity or 0, usableOnly or 0, exactOnly or nil, evenOnly or nil, maxQuantity or 0, maxPrice
end

-- gets all the filters for a given search term (possibly semicolon-deliminated list of search terms)
function Search:GetFilters(searchQuery)
	local filters = {}
	local searchTerms = {(";"):split(searchQuery)}
	filters.num = 0
	
	for i=1, #searchTerms do
		local searchTerm = searchTerms[i]:trim()
		if tonumber(searchTerm) then
			local filter = TSMAPI:GetAuctionQueryInfo(TSMAPI:GetItemString(searchTerm))
			if filter then
				tinsert(filters, filter)
				filters.num = filters.num + 1
				if filters.currentFilter then
					filters.currentFilter = filters.currentFilter.."; "..searchTerm
				else
					filters.currentFilter = searchTerm
				end
				if filters.currentSearchTerm then
					filters.currentSearchTerm = filters.currentSearchTerm .. "; "..searchTerm
				else
					filters.currentSearchTerm = searchTerm
				end
			end
		else
			local isValid, queryString, class, subClass, minLevel, maxLevel, minILevel, maxILevel, rarity, usableOnly, exactOnly, evenOnly, maxQuantity, maxPrice = GetSearchFilterOptions(searchTerm)
			
			if not isValid then
				TSM:Print(L["Skipped the following search term because it's invalid."])
				TSM:Print("\""..searchTerm.."\": "..queryString)
			elseif strlenutf8(queryString) > 63 then
				TSM:Print(L["Skipped the following search term because it's too long. Blizzard does not allow search terms over 63 characters."])
				TSM:Print("\""..searchTerm.."\"")
				isValid = nil
			end
		
			if isValid then
				filters.num = filters.num + 1
				if filters.currentFilter then
					filters.currentFilter = filters.currentFilter.."; "..queryString
				else
					filters.currentFilter = queryString
				end
				if filters.currentSearchTerm then
					filters.currentSearchTerm = filters.currentSearchTerm .. "; "..searchTerm
				else
					filters.currentSearchTerm = searchTerm
				end
				tinsert(filters, {name=queryString, usable=usableOnly, minLevel=minLevel, maxLevel=maxLevel, quality=rarity, class=class, subClass=subClass, minILevel=minILevel, maxILevel=maxILevel, exactOnly=exactOnly, evenOnly=evenOnly, maxQuantity=maxQuantity, maxPrice=maxPrice})
			end
		end
	end
	
	return filters
end