--[[ ---------------------------------------------------------------------------
Created:		2/19/2011 7:32:54 AM
Last Modified:	2011-03-07T21:43:51Z
Author:			orgevo
Version:		v1.3a

SearchPanelFrame:	New, semi-standalone panel for hosting the advanced search UI
					in what I hope comes across as more intuitive to use.

AtlasLoot_AdvancedSearch is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

AtlasLoot_AdvancedSearch is distributed WITHOUT ANY WARRANTY; without even the 
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AtlasLoot_AdvancedSearch.	If not, see <http://www.gnu.org/licenses/>.

----------------------------------------------------------------------------- ]]
local ADDON_TAG					= "AtlasLootAdvancedSearch"
local AtlasLootAdvancedSearch	= LibStub("AceAddon-3.0"):GetAddon(ADDON_TAG);
local alas_addon				= AtlasLootAdvancedSearch;
local al_addon					= AtlasLoot;
local PANELFRAME_DEBUG			= true;
local ENABLE_LOGGING			= true--alas_addon.ENABLE_LOGGING and PANELFRAME_DEBUG

-- localization
local L						= LibStub("AceLocale-3.0"):GetLocale(ADDON_TAG, true)
local AL					= LibStub("AceLocale-3.0"):GetLocale("AtlasLoot");
local BabbleInventory		= AtlasLoot_GetLocaleLibBabble("LibBabble-Inventory-3.0")
local ADDON_NAME			= L["ADDON_NAME"]

local GREY = al_addon.GREY;
local RED = al_addon.RED;
local WHITE = al_addon.WHITE;
local GREEN = al_addon.GREEN;
local PURPLE = al_addon.PURPLE;
local BLUE = al_addon.BLUE;
local ORANGE = al_addon.ORANGE;

local tinsert, tconcat = table.insert, table.concat
local strlen, strmatch, strfind, strsub, strformat, strlower, strupper = string.len, string.match, string.find, string.sub, string.format, string.lower, string.upper
local pairs, next, type = pairs, next, type
local tostring = tostring
local AtlasLootAdvancedSearch_GetLootItemMetadata = AtlasLootAdvancedSearch_GetLootItemMetadata

-- print wrappers
-- t_print (testing): what I use while developing....most of these will be commented out in the repository versions
-- dev_print (development): outputs only when AtlasLootAdvancedSearch.dev_mode is true
local function r_print(str) alas_addon:Print(str); end
local function r_printf(str,...) alas_addon:Print(strformat(str,...)); end

local dbgProxy = alas_addon.DebugProxy;
local function isDevMode()			return dbgProxy and dbgProxy:isDevMode() end
local function isInternal()			return dbgProxy and dbgProxy:isInternal() end
local function t_print(str)			if dbgProxy and ENABLE_LOGGING then dbgProxy:t_print(str) end end
local function t_printf(str,...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:t_printf(str, ...) end end
local function dev_print(str)		if dbgProxy and ENABLE_LOGGING then dbgProxy:dev_print(str) end end
local function dev_printf(str, ...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:dev_printf(str,...) end end
local function d_print(str)			if dbgProxy and ENABLE_LOGGING then dbgProxy:d_print(str) end end
local function d_printf(str,...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:d_printf(str,...) end end
local function funcEnter(obj, funcName, formatStr, ...)
	if alas_addon.DebugProxy.isEnabled and ENABLE_LOGGING then
		if formatStr then alas_addon.DebugProxy:TraceEnter(obj,funcName,strformat(formatStr,...));
		else alas_addon.DebugProxy:TraceEnter(obj,funcName); end
	end
end
local function funcExit(obj, funcName, formatStr, ...)
	if alas_addon.DebugProxy.isEnabled and ENABLE_LOGGING then
		if formatStr then alas_addon.DebugProxy:TraceExit(obj,funcName,strformat(formatStr,...));
		else alas_addon.DebugProxy:TraceExit(obj,funcName); end
	end
end


-- Class declarations
-- SearchCriteriaManager handles translating between the AdvancedFilter data structures and a makeup
-- which is consumable by the UI.
ASPFrameworkRoot = newclass("ASPFrameworkRoot");
	SearchCriteriaManager	= newclass("SearchCriteriaManager", ASPFrameworkRoot);
	SearchCriteriaRow		= newclass("SearchCriteriaRow",		ASPFrameworkRoot);


local AceGUI = LibStub("AceGUI-3.0");
local SearchPanelFrame = nil;
local critManager = nil;
local filterDB = nil;
local OverrideDropDown_SetLabel = nil;


--[[
ok, so the editbox has only a single member, obj.  the relevant properties of obj are:
button,
editbox,
frame,
label

or

button
dropdown
frame
label
text
--]]


function OverrideDropDown_SetLabel(widget, text)
--r_printf("OverrideDropDown_SetLabel");
	widget:origSetLabel(text);
	if (not text) or (text == "") then
		-- both editboxes and dropdowns need their alignoffset reset.
		widget.alignoffset = 0;
		
		-- in addition, dropdown's height doesn't seem to be adjusted correctly, so we'll take care of that too.
		if widget.type == "Dropdown" then
			-- combobox (dropdown)
			widget:SetHeight(26);
--			widget.frame:SetHeight(26);
--			widget.dropdown:SetHeight(26);		
--			r_printf(" >>>>>>>>>>  Adjusted anchor for widget of type '%s': widget's alignoffset is now %s, widget frame height:%s,  widget height:%s",
--				tostring(widget.type), tostring(widget.alignoffset), tostring(widget.frame:GetHeight()), tostring(widget.dropdown:GetHeight()));
		else
			-- editbox
--			widget.editbox:SetAllPoints(widget.frame);
--			r_printf(" >>>>>>>>>>  Adjusted anchor for widget of type '%s': widget's alignoffset is now %s, widget frame height:%s,   widget height:%s",
--				tostring(widget.type), tostring(widget.alignoffset), tostring(widget.frame:GetHeight()), tostring(widget.editbox:GetHeight()));
		end
	end
end


--AceGUI:Create("Frame", "SearchPanelFrame");
function AtlasLootAdvancedSearch:ShowAdvancedSearchPanel()
	self:InitializeSearchPanel();
	SearchPanelFrame:Show();
end
function AtlasLootAdvancedSearch:HideAdvancedSearchPanel()
	SearchPanelFrame:Hide();
end

function AtlasLootAdvancedSearch:InitializeSearchPanel()
	local frame = AceGUI:Create("Frame");
	frame:SetTitle(L["ASP_TITLE_MAIN"]);--SearchPanelFrame:SetTitle(L["SEARCH_PANEL_TITLE"]);
	frame:SetStatusText(L["ASP_STATUS_MAIN"]);
	frame:SetCallback("OnClose", function(widget)
		if (widget.origSetLabel ~= nil) and (widget.SetLabel == OverrideDropDown_SetLabel) then
		r_printf("widget.OnClose (%s) - origSetLabel:%s    widget.SetLabel:%s   OverrideDropDown_SetLabel:%s",
			tostring(widget:GetName()), tostring(widget.origSetLabel), tostring(widget.SetLabel), tostring(OverrideDropDown_SetLabel));
			widget.SetLabel = widget.origSetLabel;
			widget.origSetLabel = nil;
		end
		AceGUI:Release(widget);
	end);
	
	frame:SetLayout("Flow");
	frame:SetWidth(910);

	SearchPanelFrame = frame;
	alas_addon.AdvancedSearchPanel = frame;

	if ( filterDB == nil ) then
		filterDB = AtlasLootAdvancedFilter;
	end
	if ( critManager == nil ) then
		-- create the search criteria translator
		critManager = SearchCriteriaManager:new();
		alas_addon.searchCriteriaManager = critManager;
	end

	SearchPanelFrame.Orig_OnWidthSet = SearchPanelFrame.OnWidthSet
	SearchPanelFrame.OnWidthSet = critManager.LockMinWidth;
	SearchPanelFrame.OnRelease = AtlasLootAdvancedSearch.TearDownSearchPanel;
	
	-- build the initial criteria row
	critManager:AppendSearchCriteriaRow();
end

function AtlasLootAdvancedSearch.TearDownSearchPanel(searchPanel)
	searchPanel.OnWidthSet = searchPanel.Orig_OnWidthSet;
	searchPanel.Orig_OnWidthSet = nil;
	searchPanel.OnRelease = nil;
end

--[[==============================================================================
	ASPFrameworkRoot
	==========================================================================--]]
function ASPFrameworkRoot:init()
end

--[[==============================================================================
	SearchCriteriaRow
	==========================================================================--]]
function SearchCriteriaRow:init()
	self.super:init();
	
	self.moduleControl = {};
	self.predicateControl = {};
	self.subjectControl = {};
	self.actionControl = {};
	
	-- this will later be replaced by references to the actual controls; might also wanna move the values that control the width
	-- ratios for each control in this class...hmm
	self.criteriaControls = { true, true, true, true };
end

local function HookSetLabel( widget )
	if (widget.SetLabel ~= OverrideDropDown_SetLabel) then
		widget.origSetLabel = widget.SetLabel;
	end
	widget.SetLabel = OverrideDropDown_SetLabel;
end

function SearchCriteriaRow:CreateControls( initialSourceModule )
	-- verify that the key is at least a valid input value, check for string, etc.
	
	-- then run the input value through the general purpose function we use for verifying that the input value is actually
	-- one of the available module ID keys
	
	
	-- if that checks out, then we can generate the remainder of the row, ah what the hell.....here's what I have so far
	--[[
		- need a way to generate a deterministic manged name to use for identifying any common combinations so that we can safely recycle.
	--]]
	local moduleControl;		-- controls which module is used as the data source
	local predicateControl;		-- controls what logic is applied to determine matches (is, is not, contains, does not contain, etc.)
	local subjectControl;		-- receives the user's search value
	local actionControl;		-- controls whether matches are included or excluded
	
	local nextConjunction;		-- allows the player to add more conditions
	
	-- (out of 910)
	-- from 7 to 902: 895
	-- 184 (7 - 191)
	-- 138 (197 - 335)
	-- 410 (341 - 753)
	-- 143 (759 - 902)
	-- total width of all controls is:0.983516
	local keyValueTable, keyOrderTable = {}, {};

	--@todo - set user-data on each combobox to tell them which row they're in so we only add a new row for the last one.
	
	---------------------------------------------------
	-- moduleControl, 184 / 880				=	0.20909
	moduleControl = AceGUI:Create("Dropdown");
	--moduleControl.frame:SetParent(SearchPanelFrame);
	moduleControl:SetWidth(184);
	moduleControl:SetCallback("OnValueChanged", critManager.OnModuleChanged);
	SearchPanelFrame:AddChild(moduleControl);
	HookSetLabel(moduleControl);
	
	-- fill in module names
	alas_addon:PopulateSearchPanelModuleControl(moduleControl);
	moduleControl:SetLabel();

	
	----------------------------------------------------
	-- predicateControl, 138 / 880			=	0.156818
	predicateControl = AceGUI:Create("Dropdown");
	predicateControl:SetCallback("OnValueChanged", critManager.OnPredicateChanged);
	predicateControl:SetWidth(138);
	SearchPanelFrame:AddChild(predicateControl);
	HookSetLabel(predicateControl);

	keyValueTable = {
		START	= L["OPT_PREDICATE_START"],
		ENDING	= L["OPT_PREDICATE_END"],
		MATCH	= L["OPT_PREDICATE_EXACT"],
		CONTAIN	= L["OPT_PREDICATE_FUZZY"],
	};
	keyOrderTable = { "START", "ENDING", "MATCH", "CONTAIN" }
	predicateControl:SetList(keyValueTable, keyOrderTable);
	predicateControl:SetLabel();
	

	----------------------------------------------------
	-- subjectControl, 325 / 880			=	0.468
	subjectControl = AceGUI:Create("EditBox");
	SearchPanelFrame:AddChild(subjectControl);
	HookSetLabel(subjectControl);
	subjectControl:SetWidth(320);
	subjectControl:SetLabel();


	----------------------------------------------------
	-- actionControl, 143 / 880				=	0.1625
	actionControl = AceGUI:Create("Dropdown");
	actionControl:SetCallback("OnValueChanged", critManager.OnActionChanged);
	actionControl:SetWidth(143);

	SearchPanelFrame:AddChild(actionControl);
	HookSetLabel(actionControl);

--
	keyValueTable = {
		IN = L["OPT_ACTION_INCLUDE"],
		OUT = L["OPT_ACTION_IGNORE"],
	};
	keyOrderTable = { "IN", "OUT" }
	actionControl:SetList(keyValueTable, keyOrderTable);
	actionControl:SetLabel();
	


	----------------------------------------------------
	-- nextConjunction, ~~~~	
	nextConjunction = AceGUI:Create("Dropdown");
	nextConjunction:SetCallback("OnValueChanged", critManager.OnConjunctionChanged);
	nextConjunction:SetWidth(85);
	SearchPanelFrame:AddChild(nextConjunction);
	HookSetLabel(nextConjunction);

--
	keyValueTable = {AND = L["OPT_CONJUNCT_LIMIT"], OR = L["OPT_CONJUNCT_EXPAND"], REMOVE = "---"};
	keyOrderTable = { "AND", "OR", "REMOVE" };
	nextConjunction:SetList(keyValueTable, keyOrderTable);
	nextConjunction:SetLabel();

	
	-- might wanna move the values that control the width ratios for each control in this class...hmm
	self.criteriaControls = { moduleControl, predicateControl, subjectControl, actionControl };
	self.moduleControl = self.criteriaControls[1];
	self.predicateControl = self.criteriaControls[2];
	self.subjectControl = self.criteriaControls[3];
	self.actionControl = self.criteriaControls[4];
	
	-- and initialize the drop down values
	-- for some reason need this additional call to DoLayout in order for the criteria controls to position themselves correctly from the start.
	SearchPanelFrame:DoLayout();
	self:SetModule(1);
	self:SetPredicate(1);
	self:SetAction(1);
end

function SearchCriteriaRow:DestroyControls()
	local bCleanupSuccessful = true;
	
	local controlsCopy = self.criteriaControls;
	for idx = getn(controlsCopy), 1, -1 do
		local nextControl = controlsCopy[idx];
		alas_addon:Release(nextControl);
		controlsCopy[idx] = nil;
	end
	
	return bCleanupSuccessful;
end

-- newValueID needs to correspond to the ID used as the "key" when populating the criteria controls
function SearchCriteriaRow:SetModule(newValueID, isDisplayValue )
	return self:SetCriteriaControlValue(self.criteriaControls[1], newValueID, isDisplayValue);
end
function SearchCriteriaRow:SetPredicate(newValueID, isDisplayValue)
	return self:SetCriteriaControlValue(self.criteriaControls[2], newValueID, isDisplayValue);
end
function SearchCriteriaRow:SetSubject(newValueID, isDisplayValue)
	return self:SetCriteriaControlValue(self.criteriaControls[3], newValueID, isDisplayValue);
end
function SearchCriteriaRow:SetAction(newValueID, isDisplayValue)
	return self:SetCriteriaControlValue(self.criteriaControls[4], newValueID, isDisplayValue);
end
function SearchCriteriaRow:GetModule(byValue)
	return self:GetCriteriaControlValue(self.criteriaControls[1],byValue), self.criteriaControls[1];
end
function SearchCriteriaRow:GetPredicate(byValue)
	return self:GetCriteriaControlValue(self.criteriaControls[2],byValue), self.criteriaControls[2];
end
function SearchCriteriaRow:GetSubject(byValue)
	return self:GetCriteriaControlValue(self.criteriaControls[3],byValue), self.criteriaControls[3];
end
function SearchCriteriaRow:GetAction(byValue)
	return self:GetCriteriaControlValue(self.criteriaControls[4],byValue), self.criteriaControls[4];
end
--[[=========================================================================================]]
function SearchCriteriaRow:SetCriteriaControlValue( criteriaControl, newValue, isDisplayValue )
	local bSuccessfullySet = false;
	
	if criteriaControl and type(criteriaControl) == "table" and newValue then
		if criteriaControl.type == "Dropdown" then
			--- this small block of code is to allow callers to specify a specific index if e.g. keys are unknown.
			local literalIndex = tonumber(newValue);
			if literalIndex ~= nil then
				local tblSize = 0;
				for elementID, _ in pairs(criteriaControl.list) do
					tblSize = tblSize + 1;
					newValue = elementID;
					if literalIndex == tblSize then
						break;
					end
				end
				if literalIndex <= 0 or literalIndex > tblSize then
					newValue = nil;
				end
			end
			
			if type(newValue) == "string" then
				local newListIndex = nil;
				
				if (isDisplayValue == nil) or (type(isDisplayValue) ~= "boolean") or (isDisplayValue == false) then
					newListIndex = newValue;
				
				else
					-- we'll need to search for the key belonging to this value
					for k, v in pairs(criteriaControl.list) do
						if v == newValue then
							-- found a match; the key associated with this value is the info we need.
							newListIndex = k;
							break;
						end
					end
				end
				if newListIndex and (newListIndex == "" or criteriaControl.list[newListIndex] ~= nil) then
					criteriaControl:SetValue(newListIndex);
					bSuccessfullySet = (newListIndex == criteriaControl.value);
				end
			end
		elseif criteriaControl.type == "EditBox" then
			if type(newValue) == "string" then
				criterialControl:SetText(newValue);
				bSuccessfullySet = (newValue == criteriaControl.value);
			end
		end
	end
	
	return bSuccessfullySet;
end
function SearchCriteriaRow:GetCriteriaControlValue( criteriaControl, byValue )
	local result = "";

	if criteriaControl and type(criteriaControl) == "table" then
		if criteriaControl.type == "Dropdown" then
			result = criteriaControl.value;
			if byValue and byValue ~= false then
				result = criteriaControl.list[result];
			end
		elseif criteriaControl.type == "EditBox" then
			result = criteriaControl:GetText();
		end
	end
	
	return result;
end



--[[==============================================================================
	SearchCriteriaManager
	==========================================================================--]]
function SearchCriteriaManager:init()
	self.super:init();
	
	self.criteriaRows = {};
	self.numCriteriaRows = 0;
	self.MinimumWidth = 910;
end
--@todo
--function SearchCriteriaManager:__tostring()
--	return strformat("[SearchCriteriaManager:%i children]", #self.ZoneArray);
--end
function SearchCriteriaManager:GetClassName()
	return self.class():name();
end

function SearchCriteriaManager:AppendSearchCriteriaRow( )
	local newCriteriaRow = SearchCriteriaRow:new();
	
	if newCriteriaRow then
		newCriteriaRow:CreateControls();
		
		-- add it to the table reserved for this purpose.  In doing so, increment my internal counter keeping track of how many we think we have.
		self.numCriteriaRows = self.numCriteriaRows + 1;	
		tinsert(self.criteriaRows, newCriteriaRow);
	end
	return newCriteriaRow;
end

function SearchCriteriaManager:RemoveSearchCriteriaRow( rowIndex, parentID )
	local bResult = false;
	
	if rowIndex then
		-- no, create a new class to accomidate the sub-criteria of each row and make the responsibility for
		-- managing its sub-criteria rows its own.
		if parentID then
			-- locate the row with the matching ID, and pass control to it.


		end
		
		if bResult ~= true then
			-- we'll assume then, this is an unrooted row.
			if rowIndex > 0 and rowIndex < #self.criteriaRows then
				local criteriaControlRow = self.criteriaRows[rowIndex];
				if criteriaControlRow:DestroyControls() then			
					tremove(self.criteriaRows, rowIndex);
					self.numCriteriaRows = self.numCriteriaRows - 1;
					bResult = true;
				end
			end
		end
	end
	
	return bResult;
end

function SearchCriteriaManager:LockMinWidth(newWidth)
	if ( newWidth < critManager.MinimumWidth ) then
		SearchPanelFrame:SetWidth(critManager.MinimumWidth);
	elseif SearchPanelFrame.Orig_OnWidthSet then
		SearchPanelFrame:Orig_OnWidthSet(newWidth);
	end
end

function SearchCriteriaManager.OnModuleChanged(sender, selectedKey)
end
function SearchCriteriaManager.OnPredicateChanged(sender, selectedKey)
end
function SearchCriteriaManager.OnActionChanged(sender, selectedKey)
end


function SearchCriteriaManager.OnConjunctionChanged(sender, eventNameBeingTriggered, selectedKey, additionalReason)
--r_printf("SearchCriteriaManager.OnConjunctionChanged - %s:  sender:%s   selectedKey:'%s'", tostring(eventNameBeingTriggered), tostring(sender), tostring(selectedKey));
	if sender and selectedKey then
		if selectedKey == "REMOVE" then
			local removalIndex;
			
			-- determine the index corresponding to the row containing the control that fired the event
			for idx = 1, critManager.numCriteriaRows do
				local _, conjunctionControl = critManager:GetAction(idx);
--				r_printf("checking our control (%s ..... ~~.obj:%s)   vs.  sender:%s      and sender.obj:%s", 
--					tostring(conjunctionControl), tostring(conjunctionControl.obj), tostring(sender), tostring(sender.obj));
					
				if conjunctionControl.obj == sender then
					-- found it
					removalIndex = idx;
					break;
				end
			end
			
			local bResult = critManager:RemoveSearchCriteriaRow(removalIdx);
			if bResult == true then
				-- nothing we want to know about failures, yet...
				SearchPanelFrame:DoLayout();
				r_printf("SearchCriteriaManager.OnConjunctionChanged - Requested search criteria removed successfully at row %i", removalIdx);
			else
				r_printf("SearchCriteriaManager.OnConjunctionChanged - Failed to remove requested search criteria requested.  Unable to confirm which control originally sent the message.   (Add'l info:%s)", tostring(additionalReason));
			end
			
			--  ..done
		else
			critManager:AppendSearchCriteriaRow();
		end
	end
end





function SearchCriteriaManager:SetModule(rowIndex, newValueID, byValue)
	return self.criteriaRows[rowIndex]:SetModule(newValueID, byValue);
end
function SearchCriteriaManager:SetPredicate(rowIndex, newValueID, byValue)
	return self.criteriaRows[rowIndex]:SetPredicate(self.criteriaControls[2], newValueID);
end
function SearchCriteriaManager:SetSubject(rowIndex, newValueID, byValue)
	return self.criteriaRows[rowIndex]:SetSubject(self.criteriaControls[3], newValueID);
end
function SearchCriteriaManager:SetAction(rowIndex, newValueID, byValue)
	return self.criteriaRows[rowIndex]:SetAction(self.criteriaControls[4], newValueID);
end
function SearchCriteriaManager:GetModule(rowIndex, byValue)
	return self.criteriaRows[rowIndex]:GetModule(byValue);
end
function SearchCriteriaManager:GetPredicate(rowIndex, byValue)
	return self.criteriaRows[rowIndex]:GetPredicate(byValue)
end
function SearchCriteriaManager:GetSubject(rowIndex, byValue)
	return self.criteriaRows[rowIndex]:GetSubject(byValue)
end
function SearchCriteriaManager:GetAction(rowIndex, byValue)
	return self.criteriaRows[rowIndex]:GetAction(byValue)
end
--]]