----------------------------------------------------------------------------------
--
-- WantAds.lua
--
-- Author: Ian Pieragostini
--
-- WantAds is a WoW UI modification which allows players to place ads while they are online.
-- These ads are visible to all players on the server within the player's faction.
-- 
---------------------------------------------------------------------------------

----------------------------------------------------------------------------------
--
-- Constants
-- 
---------------------------------------------------------------------------------
local WANTADS_VERSION = "08b";
local WANTADS_PROTOCOL_VERSION = "10";
local WANTADS_MSG_PREFIX = "<WANTADS"..WANTADS_PROTOCOL_VERSION..">";
local WANTADS_MSG_ADD = "1";
local WANTADS_MSG_REMOVE = "2";
local WANTADS_MSG_REMOVE_ALL = "3";
local WANTADS_MSG_REQUEST_ADS = "4";
local WANTADS_BAR_CODE = "<BAR>";
local WANTADS_NUM_GLOBAL_AD_BUTTONS = 17;
local WANTADS_NUM_MY_AD_BUTTONS = 4;
WANTADS_ADBUTTONSIZEY = 16;

----------------------------------------------------------------------------------
--
-- Global variables / file scope variables
-- 
---------------------------------------------------------------------------------
local g_WantAdsInitialized;						--- Is WantAds initialized?
local g_PublishMyAds;							--- The player wants to advertise their ads

local g_GlobalAds = {};  						--- Currently know about advertisements
WantAds_MyAds = {};								--- Ads which this player has placed
WantAds_NextMyAdsId = 0;						--- Next Id that should be used for MyAds

local g_GlobalAdSelected;						--- Index of the ad currently selected
local g_MyAdSelected;							--- Index of my ad currently selected
local g_HyperLinkIndex;							--- Index of hyperlink currently viewed

----------------------------------------------------------------------------------
--
-- Print a debug string to the chat frame
--	msg - message to print
-- 
---------------------------------------------------------------------------------
local WANTADS_DEBUG = true;						-- Is debug printf enabled?
local function DEBUG_MSG(msg)
	if (WANTADS_DEBUG)
	then
		ChatFrame1:AddMessage(msg, 1.0, 1.0, 0.5);
	end
end

----------------------------------------------------------------------------------
--
-- Remove an add by owner and id
-- 
---------------------------------------------------------------------------------
local function RemoveByOwnerAndId(adTable, owner, id)
	local size = table.getn(adTable);
	local i = 1;
	while (i <= size) do
		local ad = adTable[i];
		if ((ad.m_Owner == owner) and (ad.m_Id == id)) then
			table.remove(adTable, i);
			size = size - 1;
		else
			i = i + 1;
		end
	end
end

----------------------------------------------------------------------------------
--
-- Remove all ads by owner
-- 
---------------------------------------------------------------------------------
local function RemoveByOwner(adTable, owner)
	local size = table.getn(adTable);
	local i = 1;
	while (i <= size) do
		local ad = adTable[i];
		if (ad.m_Owner == owner) then
			table.remove(adTable, i);
			size = size - 1;
		else
			i = i + 1;
		end
	end
end

----------------------------------------------------------------------------------
--
-- Predicate function which can be used to compare two ads for sorting
-- 
---------------------------------------------------------------------------------
function AdPredicate(lhs, rhs)
	--
	-- nil references are always less than
	--
	if (lhs == nil) then
		if (rhs == nil) then
			return false;
		else
			return true;
		end
	elseif (rhs == nil) then
		return false;	
	end
	--
	-- Sort by text first
	--
	if (lhs.m_Text < rhs.m_Text) then
		return true;
	elseif (rhs.m_Text < lhs.m_Text) then
		return false;
	end
	--
	-- Sort by owner next
	--
	if (lhs.m_Owner < rhs.m_Owner) then
		return true;
	elseif (rhs.m_Owner < lhs.m_Owner) then
		return false;
	end
	--
	-- Sort by Id
	--
	if (lhs.m_Id < rhs.m_Id) then
		return true;
	elseif (rhs.m_Id < lhs.m_Id) then
		return false;
	end
	--
	-- These ads are identical
	--
	return false;	
end

---------------------------------------------------------------------------------
--
-- Send a message to the player indicated.  If player is nil, the message is broadcast to the Cosmos channel.
-- 
---------------------------------------------------------------------------------
function WantAds_SendMessage(who, msg)
	if (not who) then
		Cosmos_SendMessage(msg);
	else
		SendChatMessage(msg, "WHISPER", this.language, who);
	end
end

---------------------------------------------------------------------------------
--
-- Send a single ad to the player given.  If player is nil, the message is broadcast.
-- 
---------------------------------------------------------------------------------
local function SendAd(who, ad)
	if (ad.m_Enabled) then
		--
		-- Replace | with a code to stop the server from stripping out hyperlinks
		--
		local text = string.gsub(ad.m_Text, "|", WANTADS_BAR_CODE);
		WantAds_SendMessage(who, WANTADS_MSG_PREFIX..WANTADS_MSG_ADD..";"..ad.m_Id..";"..text);
	end
end

----------------------------------------------------------------------------------
--
-- Send all ads in the table to the user (nil indicates broadcast)
-- 
---------------------------------------------------------------------------------
local function SendAllAds(adTable, who)
	local size = table.getn(adTable);
	for i = 1, size, 1 do
		local ad = adTable[i];
		SendAd(who, ad);
	end
end

---------------------------------------------------------------------------------
--
-- This callback is registered with Cosmos and is called when someone whispers this player or sends a message to the Cosmos channel
-- 
---------------------------------------------------------------------------------
function WantAds_OnMessage(type, info, text, author, arg3, arg4)
	--
	-- Do nothing if we haven't been initialized
	--
	if (not g_WantAdsInitialized) then
		return 1;
	end
	--
	-- Validate parameters
	--
	if ((not text) or (not author)) then
		return 1;
	end
	--
	-- Parse the message
	--
	local iStart, iEnd, msg, params;
	iStart, iEnd, params = string.find(text, WANTADS_MSG_PREFIX.."(.*)");
	if (not iStart) then
		--
		-- This is not a want ads message, let someone else handle it
		--
		return 1;
	end
	iStart, iEnd, msg, params = string.find(params, "(%w+);(.*)");
	if (not iStart) then
		--
		-- Message format is bad
		--
		DEBUG_MSG("WantAds_OnMessage: Bad message format");
		return 0;
	end

	--DEBUG_MSG("WantAds_OnMessage: "..msg..", "..params);
 
	--
	-- Process all the messages we know about
	--
	if (msg == WANTADS_MSG_ADD) then
		--
		-- Someone is placing an add
		--
		local iStart, iEnd, id, rawtext = string.find(params, "(%d+);(.*)");
		--
		-- Replace the code for | with a |
		--
		local adtext = string.gsub(rawtext, WANTADS_BAR_CODE, "|");
		if (iStart) then
			-- Scan for dupes
			for k,v in g_GlobalAds do 
				if ( v.m_Owner == author and v.m_Id == id and v.m_Text == adtext ) then
					-- DONT ADD IT AGAIN
					return 0;
				end
			end
			table.insert(g_GlobalAds, {m_Owner = author, m_Id = id, m_Text = adtext});
			WantAds_UpdateGlobalAdButtons();
		else
			DEBUG_MSG("WantAds_OnMessage: Bad add message: "..params);
		end
	elseif (msg == WANTADS_MSG_REMOVE) then
		--
		-- A previously placed add is being removed
		--
		local iStart, iEnd, id = string.find(params, "(%d+)");
		if (iStart) then
			RemoveByOwnerAndId(g_GlobalAds, author, id);
			WantAds_UpdateGlobalAdButtons();
		else
			DEBUG_MSG("WantAds_OnMessage: Bad remove message: "..params);
		end
	elseif (msg == WANTADS_MSG_REQUEST_ADS) then
		--
		-- Someone is requesting our ads, probably just arrived in the channel
		--
		if (g_PublishMyAds) then
			SendAllAds(WantAds_MyAds, author);
		end
	elseif (msg == WANTADS_MSG_REMOVE_ALL) then
		--
		-- Remove all the ads from the author
		--
		RemoveByOwner(g_GlobalAds, author);
		WantAds_UpdateGlobalAdButtons();
	else
		--
		-- This message was unknown
		--
		DEBUG_MSG("WantAds_OnMessage: Unknown message: "..msg..", "..params);
	end

	--
	-- If we got this far, we want to hide the message
	--
	return 0;
end

---------------------------------------------------------------------------------
--
-- Register with Cosmos UI
-- 
---------------------------------------------------------------------------------
function WantAds_RegisterCosmos()

	--
	-- Add a button to the Cosmos page to activate WantAds
	--
	if ( Cosmos_RegisterButton ) then 
		Cosmos_RegisterButton(WANTADS_BUTTON_TEXT, WANTADS_BUTTON_SUBTEXT, WANTADS_BUTTON_TIP, "Interface\\AddOns\\WantAds\\WantAds_Icon", WantAds_Toggle);
	end
	
	--
	-- Watch the whisper and Cosmos channel for WantAd messages
	--
	if ( Cosmos_RegisterChatWatch ) then 
		Cosmos_RegisterChatWatch("WANTADS_WHISPER", {"WHISPER"}, function (type, info, arg1, arg2, arg3, arg4) if (WantAds_OnMessage(type, info, arg1, arg2, arg3, arg4) == 0) then return 0; else return 1; end end );
		Cosmos_RegisterChatWatch("WANTADS_CHANNEL", {CHANNEL_COSMOS}, function (type, info, arg1, arg2, arg3, arg4) if (WantAds_OnMessage(type, info, arg1, arg2, arg3, arg4) == 0) then return 0; else return 1; end end );
	end
	
	-- Register a /slash command to toggle the UI
	if (Cosmos_RegisterChatCommand) then
		Cosmos_RegisterChatCommand("WANT_ADS", {"/wantads"}, WantAds_Toggle, WANTADS_HELP);
	end

end


----------------------------------------------------------------------------------
--
-- Called by WoW when this add-on is loaded.  Register for events we are interested in.
-- 
---------------------------------------------------------------------------------
function WantAds_OnLoad()
	-- By default, player does not advertise
	g_PublishMyAds = false;

	-- Save the player's ads from session to session	
	RegisterForSave("WantAds_MyAds");
	RegisterForSave("WantAds_NextMyAdsId");
	
	-- Register with Cosmos
	WantAds_RegisterCosmos();
	
	-- Register for the channel leave event so we can spot exiting players
	this:RegisterEvent("CHAT_MSG_CHANNEL_LEAVE");

	-- Watch the user clicking on items in containers so we can add a hyperlink
	HookFunction("ContainerFrameItemButton_OnClick", "WantAds_WatchItemClicks", "before");

	-- Start uninitialized
	g_WantAdsInitialized = false;

	-- Initial selection values
	g_GlobalAdSelected = 0;
	g_MyAdSelected = 0;
	g_HyperLinkIndex = 1;
end

----------------------------------------------------------------------------------
--
-- Called when the main window is shown
-- 
---------------------------------------------------------------------------------
function WantAds_OnShow()
	-- Intialize on the first show event
	if (g_WantAdsInitialized == false) then
		g_WantAdsInitialized = true;
		-- Ask everyone to send me their ads
		WantAds_SendMessage(nil, WANTADS_MSG_PREFIX..WANTADS_MSG_REQUEST_ADS..";");
	end
	WantAds_UpdateGlobalAdButtons();
	WantAds_UpdateMyAdsFrame();
end

----------------------------------------------------------------------------------
--
-- Called by WoW on events for which we registered.
-- 
---------------------------------------------------------------------------------
function WantAds_OnEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
	-- Return if we havent' been initialized
	if (not g_WantAdsInitialized) then
		return;
	end
	
	-- If a player is seen leaving the Cosmos channel, remove all their ads
	if ((event == "CHAT_MSG_CHANNEL_LEAVE") and (arg9 == "Cosmos")) then
		RemoveByOwner(g_GlobalAds, arg2);
	end
	
end

----------------------------------------------------------------------------------
--
-- Toggle hidden status of want ads
-- 
---------------------------------------------------------------------------------
function WantAds_Toggle() 
	if ( WantAds:IsVisible() ) then 
		WantAds:Hide();
	else
		WantAds:Show();
	end
end

----------------------------------------------------------------------------------
--
-- Update the buttons for a given table
-- 
---------------------------------------------------------------------------------
function WantAds_UpdateAdButtonsForTable(adTable, numButtons, offset, selected, prefix) 
	local size = table.getn(adTable);
	local i = 1;
	while (i <= numButtons) do
		local button = getglobal(prefix..i);
		local iAd = i + offset;
		button.m_AdIndex = iAd;
		if (iAd <= size) then
			button:Show();
			local ad = adTable[iAd];
			local textField = prefix..i.."Text";
			local ownerField = prefix..i.."Owner";
			getglobal(textField):SetText(ad.m_Text);
			getglobal(ownerField):SetText(ad.m_Owner);
			if (selected == iAd) then
				button:LockHighlight();
			else
				button:UnlockHighlight();
			end
		else
			button:Hide();
		end
		i = i + 1;
	end
end

----------------------------------------------------------------------------------
--
-- Update global ad buttons in the UI
-- 
---------------------------------------------------------------------------------
function WantAds_UpdateGlobalAdButtons()
	local size = table.getn(g_GlobalAds);
	if (size) then
		table.sort(g_GlobalAds, AdPredicate);
	end
	local offset = FauxScrollFrame_GetOffset(WantAdsGlobalAdScrollFrame);	
	WantAds_UpdateAdButtonsForTable(g_GlobalAds, WANTADS_NUM_GLOBAL_AD_BUTTONS, offset, g_GlobalAdSelected, "WantAdsGlobalAdButton");
	FauxScrollFrame_Update(WantAdsGlobalAdScrollFrame, size, WANTADS_NUM_GLOBAL_AD_BUTTONS, WANTADS_ADBUTTONSIZEY);

	if ( size ~= 0 ) then 
		WantAdsMinimapButton:Show();
	else 
		WantAdsMinimapButton:Hide();
	end
end


----------------------------------------------------------------------------------
--
-- Update the item ref
-- 
---------------------------------------------------------------------------------
function WantAds_UpdateItemRef(index, text)
	local i = 1;
	for itemRef in string.gfind(text, "|H([%w:]+)|h") do
		if (i == index) then
			SetItemRef(itemRef);
			return true;
		end
		i = i + 1;
	end
	return false;
end

----------------------------------------------------------------------------------
--
-- Called when a global ad is clicked
-- 
---------------------------------------------------------------------------------
function WantAds_GlobalAdButton_OnClick()
	local id = this:GetID();
	local offset = FauxScrollFrame_GetOffset(WantAdsGlobalAdScrollFrame);
	local newSelectedId = id + offset;
	if (newSelectedId ~= g_GlobalAdSelected) then
		g_GlobalAdSelected = id + offset;
		WantAds_UpdateGlobalAdButtons();
		g_HyperLinkIndex = 1;
	else
		g_HyperLinkIndex = g_HyperLinkIndex + 1;
	end
	local found = WantAds_UpdateItemRef(g_HyperLinkIndex, g_GlobalAds[g_GlobalAdSelected].m_Text);
	if (found == false) then
		g_HyperLinkIndex = 1;
		WantAds_UpdateItemRef(g_HyperLinkIndex, g_GlobalAds[g_GlobalAdSelected].m_Text);
	end
end

----------------------------------------------------------------------------------
--
-- Called when the invite button is clicked
-- 
---------------------------------------------------------------------------------
function WantAds_Invite_OnClick()
	--
	-- Make sure we have an ad selected
	--
	if (g_GlobalAdSelected > 0) then
		--
		-- Make sure there is something in the table
		--
		local size = table.getn(g_GlobalAds);
		if (g_GlobalAdSelected <= size) then
			--
			-- Invite the owner of the currently selected ad
			--
			local ad = g_GlobalAds[g_GlobalAdSelected];
			InviteByName(ad.m_Owner);
		end
	end
end

----------------------------------------------------------------------------------
--
-- Send a whisper command to the owner of the selected ad
-- 
---------------------------------------------------------------------------------
function WantAds_Whisper_OnClick()
	--
	-- Make sure we have an ad selected
	--
	if (g_GlobalAdSelected > 0) then
		--
		-- Make sure there is something in the table
		--
		local size = table.getn(g_GlobalAds);
		if (g_GlobalAdSelected <= size) then
			--
			-- Send a whisper command to the chat window
			--
			local ad = g_GlobalAds[g_GlobalAdSelected];
			if ( not ChatFrameEditBox:IsVisible() ) then
				ChatFrame_OpenChat("/w "..ad.m_Owner.." ");
			else
				ChatFrameEditBox:SetText("/w "..ad.m_Owner.." ");
			end
			ChatEdit_ParseText(ChatFrame1.editBox, 0);
		end
	end
end

----------------------------------------------------------------------------------
--
-- Get /who information about the owner of the currently selected ad
-- 
---------------------------------------------------------------------------------
function WantAds_Who_OnClick()
	--
	-- Make sure we have an ad selected
	--
	if (g_GlobalAdSelected > 0) then
		--
		-- Make sure there is something in the table
		--
		local size = table.getn(g_GlobalAds);
		if (g_GlobalAdSelected <= size) then
			--
			-- Send a who command to the chat window
			--
			local text = ChatFrameEditBox:GetText();
			local ad = g_GlobalAds[g_GlobalAdSelected];
			ChatFrameEditBox:SetText("/who "..ad.m_Owner);
			ChatEdit_SendText(ChatFrameEditBox);
			ChatFrameEditBox:SetText(text);
		end
	end
end


----------------------------------------------------------------------------------
--
-- Called when a my add is clicked
-- 
---------------------------------------------------------------------------------
function WantAds_MyAdButton_OnClick()
	local id = this:GetID();
	local offset = FauxScrollFrame_GetOffset(WantAdsMyAdScrollFrame);
	g_MyAdSelected = id + offset;
	WantAds_UpdateMyAdsFrame();
end


----------------------------------------------------------------------------------
--
-- Called when MyAds check button is clicked
-- 
---------------------------------------------------------------------------------
function WantAds_MyAdsCheckButton_OnClick()
	local ad = WantAds_MyAds[this.m_AdIndex];
	if ( this:GetChecked() ) then
		WantAds_MyAds[this.m_AdIndex].m_Enabled = true;	
		PlaySound("igMainMenuOptionCheckBoxOn");
		--
		-- If ads are being published, send a message to everyone adding this ad
		--
		if (g_PublishMyAds) then
			SendAd(nil, ad);
		end	
	else
		WantAds_MyAds[this.m_AdIndex].m_Enabled = false;
		PlaySound("igMainMenuOptionCheckBoxOff");
		--
		-- If my ads are currently published, send a message to everyone to remove this ad
		--		
		if (g_PublishMyAds) then
			WantAds_SendMessage(nil, WANTADS_MSG_PREFIX..WANTADS_MSG_REMOVE..";"..ad.m_Id..";");
		end
	end
end

----------------------------------------------------------------------------------
--
-- Hooked to container item clicks so we can add hyperlinks
-- 
---------------------------------------------------------------------------------
function WantAds_WatchItemClicks(button, ignoreShift)
	if (WantAds:IsVisible()) then
		if ( button == "LeftButton" ) then
			if ( IsShiftKeyDown() and not ignoreShift ) then
				local link = GetContainerItemLink(this:GetParent():GetID(), this:GetID());
				WantAdsEditBox:Insert(link);
			end
		end
	end
end


----------------------------------------------------------------------------------
--
-- Add a new entry to MyAds
-- 
---------------------------------------------------------------------------------
function WantAds_AddButton_OnClick()
	--
	-- Add a new ad if there is text in the edit box
	--
	local text = WantAdsEditBox:GetText();
	if (text ~= "") then
		--
		-- Add to the my ads table
		--
		local ad = {m_Id = WantAds_NextMyAdsId, m_Text = text, m_Enabled = true};
		table.insert(WantAds_MyAds, ad);
		--
		-- If ads are being published, send a message to everyone adding this ad
		--
		if (g_PublishMyAds) then
			SendAd(nil, ad);
		end
		--
		-- Incrememnt the id and wrap around
		--
		WantAds_NextMyAdsId = WantAds_NextMyAdsId + 1;
		if (WantAds_NextMyAdsId >= 65535) then WantAds_NextMyAdsId = 0; end;
		--
		-- Reset edit box and update buttons in UI
		--
		WantAdsEditBox:SetText("");
		WantAds_UpdateMyAdsFrame();
	end
end

----------------------------------------------------------------------------------
--
-- Remove the currently selected MyAds
-- 
---------------------------------------------------------------------------------
function WantAds_RemoveButton_OnClick()
	--
	-- Remove the currently selected ad only if one is selected
	--
	if (g_MyAdSelected > 0) then
		--
		-- Make sure there is something in the table
		--
		local size = table.getn(WantAds_MyAds);
		if (g_MyAdSelected <= size) then
			--
			-- If my ads are currently published, send a message to everyone to remove this ad
			--		
			if (g_PublishMyAds) then
				local ad = WantAds_MyAds[g_MyAdSelected];
				WantAds_SendMessage(nil, WANTADS_MSG_PREFIX..WANTADS_MSG_REMOVE..";"..ad.m_Id..";");
			end
			--
			-- Remove the add
			--
			table.remove(WantAds_MyAds, g_MyAdSelected);
			--
			-- Adjust the selected index if the last ad in the table was deleted
			--
			if (g_MyAdSelected == size) then
				g_MyAdSelected = g_MyAdSelected - 1;
			end
			--
			-- Make sure changes are reflected in the UI
			--
			WantAds_UpdateMyAdsFrame();
		end
	end
end

----------------------------------------------------------------------------------
--
-- Toggle advertise on and off
-- 
---------------------------------------------------------------------------------
function WantAds_SetPublishMyAds(state)
	if (state) then
		-- Broadcast all my ads
		SendAllAds(WantAds_MyAds, nil);
		WantAdsMinimapButton:Show();
	else
		-- Broadcast a message to remove all my ads
		WantAds_SendMessage(nil, WANTADS_MSG_PREFIX..WANTADS_MSG_REMOVE_ALL..";");
		WantAdsMinimapButton:Hide();
	end
	g_PublishMyAds = state;
end

----------------------------------------------------------------------------------
--
-- Update the contents of the MyAds frame
-- 
---------------------------------------------------------------------------------
function WantAds_UpdateMyAdsFrame()
	--
	-- Sort my ads
	--
	local size = table.getn(WantAds_MyAds);
	if (size) then
		table.sort(WantAds_MyAds, AdPredicate);
	end
	--
	-- Determine where the scroll bar is
	--
	local offset = FauxScrollFrame_GetOffset(WantAdsMyAdScrollFrame);
	--
	-- Walk through all the rows in the MyAds frame
	--
	local size = table.getn(WantAds_MyAds);
	local i = 1;
	while (i <= WANTADS_NUM_MY_AD_BUTTONS) do
		--
		-- Get the index to the ad displayed in this row
		--
		local iAd = i + offset;
		--
		-- Get the button on this row and set the index
		--
		local button = getglobal("WantAdsMyAdButton"..i);
		button.m_AdIndex = iAd;
		local checkButton = getglobal("WantAdsMyAdsCheckButton"..i);
		checkButton.m_AdIndex = iAd;
		--
		-- Is there a valid ad on this row?
		--
		if (iAd <= size) then
			local ad = WantAds_MyAds[iAd];
			--
			-- Check the check box if appropriate
			--
			if (ad.m_Enabled) then
				checkButton:SetChecked(1);
			else
				checkButton:SetChecked(0);
			end
			checkButton:Show();
			--
			-- Update the button text
			--
			button:Show();
			local textField = "WantAdsMyAdButton"..i.."Text";
			getglobal(textField):SetText(ad.m_Text);
			--
			-- If this is the selected ad, highlight it
			--
			if (g_MyAdSelected == iAd) then
				button:LockHighlight();
				checkButton:LockHighlight();
			else
				button:UnlockHighlight();
				checkButton:LockHighlight();
			end
		else
			--
			-- Hide the button
			--
			button:Hide();
			checkButton:Hide();
		end
		--
		-- Next row
		--
		i = i + 1;
	end
	--
	-- Update the scroll bar
	--
	FauxScrollFrame_Update(WantAdsMyAdScrollFrame, size, WANTADS_NUM_MY_AD_BUTTONS, WANTADS_ADBUTTONSIZEY);
end
