/*
	Stargate for GarrysMod10
	Copyright (C) 2007  aVoN

	This program 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.

	This program is distributed in the hope that it will be useful,
	but 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

local PANEL = {};

PANEL.Sounds = {
	Info = Sound("buttons/button9.wav"),
	Click = Sound("npc/turret_floor/click1.wav"),
	Warning = Sound("buttons/button2.wav"),
}
PANEL.Data = {
	Addresses = {}, -- Last Dialled Addresses
	Address = "", -- Last Address in the multichoice
	LastSearchText = "", -- The last search text a person entered
}

--################# Init @aVoN
function PANEL:Init()
	self:SetSize(400,100);
	self.VGUI = {
		AddressListView=vgui.Create("DListView",self),
		AddressLabel=vgui.Create("DLabel",self),
		AddressMultiChoice=vgui.Create("DMultiChoice",self),
		DialTypeCheckbox=vgui.Create("DCheckBox",self),
		DialImageButton=vgui.Create("DImageButton",self),
		AbortDialImageButton=vgui.Create("DImageButton",self),
		SearchLabel=vgui.Create("DLabel",self),
		SearchTextEntry=vgui.Create("DTextEntry",self),
		SearchImageButton=vgui.Create("DImageButton",self),
		RefreshImageButton=vgui.Create("DImageButton",self),
	}
	self.Entity = NULL;
	self:SetCookieName("StarGate.SAddressSelect"); -- Just for the checkbox to save the value (it always annoyed me, it losses it after a restart)
	--####### Apply Sizes: Search Fields
	-- The Address List
	self.VGUI.AddressListView:SetPos(0,25);
	self.VGUI.AddressListView:AddColumn("Address"):SetFixedWidth(60);
	self.VGUI.AddressListView:AddColumn("Name");
	self.VGUI.AddressListView:SortByColumn(1,true);
	self.VGUI.AddressListView.OnRowSelected = function(ListView,Row)
		local selected = ListView:GetSelectedLine();
		local address = ListView:GetLine(selected):GetColumnText(1):upper();
		self.VGUI.AddressMultiChoice.TextEntry:SetText(address);
		self.Data.Address = address;
		-- Avoids confusing soundspam on double click
		if(selected ~= self.LastSelected) then
			self.LastSelected = selected;
			surface.PlaySound(self.Sounds.Click);
		end
	end
	self.VGUI.AddressListView.DoDoubleClick = function(ListView,id,List)
		self:DialGate(List:GetColumnText(1));
	end
	
	-- The Search Label
	self.VGUI.SearchLabel:SetPos(0,0);
	self.VGUI.SearchLabel:SetSize(40,self.VGUI.SearchLabel:GetTall());
	self.VGUI.SearchLabel:SetText("Search:");
	
	-- The Search Field
	self.VGUI.SearchTextEntry:SetPos(40,0);
	self.VGUI.SearchTextEntry:SetSize(140,self.VGUI.SearchTextEntry:GetTall());
	self.VGUI.SearchTextEntry:SetTooltip("Enter Parts of the Address or Name to find a Stargate");
	self.VGUI.SearchTextEntry:SetText(self.Data.LastSearchText);
	-- Starts the Search delayed
	self.VGUI.SearchTextEntry.OnTextChanged = function(TextEntry)
		-- Do the search, but delayed. It's handled in the Think
		self.Data.LastSearchText = TextEntry:GetValue();
		self.Data.LastSearchTyped = CurTime();
	end
	
	-- The Search Button - It may seem to be obsolete (due to the autosearch), but an address can just be changed
	self.VGUI.SearchImageButton:SetPos(182,2);
	self.VGUI.SearchImageButton:SetSize(16,16);
	self.VGUI.SearchImageButton:SetImage("gui/silkicons/application_form_magnify");
	self.VGUI.SearchImageButton:SetTooltip("Search Stargate");
	self.VGUI.SearchImageButton.DoClick = function(ImageButton)
		-- Immediately do a search (Maybe a stargate has recently changed it's addresse or name?)
		self:RefreshList();
	end
	
	-- The Refresh Button (Refreshs the list of stargates we have) - Similar to a search, but it will find just recently added stargates
	self.VGUI.RefreshImageButton:SetPos(201,2);
	self.VGUI.RefreshImageButton:SetSize(16,16);
	self.VGUI.RefreshImageButton:SetImage("gui/silkicons/arrow_refresh");
	self.VGUI.RefreshImageButton:SetTooltip("Refresh List");
	self.VGUI.RefreshImageButton.DoClick = function(ImageButton)
		-- Immediately do a search (Maybe a stargate has recently changed it's addresse or name?)
		self:RefreshList(true);
	end
	
	--####### Apply Sizes: Address/Dial Fields - Positions set in PerformLayout
	-- The Address Label
	self.VGUI.AddressLabel:SetText("Address:");
	
	-- The Address TextEntry
	-- DMultiChoice instead of TextEntry and make it save the recent gate addresses being dialled!
	self.VGUI.AddressMultiChoice:SetSize(70,self.VGUI.AddressMultiChoice:GetTall());
	self.VGUI.AddressMultiChoice.TextEntry:SetTooltip("The Address of the Stargate to dial. It can only contain the letters 0-9,A-Z and @");
	-- This function restricts the letters you can enter to a valid address
	self.VGUI.AddressMultiChoice.TextEntry.OnTextChanged = function(TextEntry)
		local text = TextEntry:GetValue();
		if(text ~= self.Data.Address) then
			local pos = TextEntry:GetCaretPos();
			local len = text:len();
			local letters = text:upper():gsub("[^0-9A-Z@]",""):TrimExplode(""); -- Upper, remove invalid chars and split!
			local text = ""; -- Wipe
			for _,v in pairs(letters) do
				if(not text:find(v)) then
					text = text..v;
				end
			end
			text = text:sub(1,6);
			TextEntry:SetText(text);
			TextEntry:SetCaretPos(math.Clamp(pos - (len-#letters),0,text:len())); -- Reset the caretpos!
			if(self.Data.Address ~= text and self.OnTextChanged) then
				self.OnTextChanged(TextEntry);
			end
			self.Data.Address = text;
		end
	end
	
	-- Add choices (Last 15 dialled addresses)
	for _,v in pairs(self.Data.Addresses) do
		self.VGUI.AddressMultiChoice:AddChoice(v);
	end
	
	-- The DHD/SGC Dialmode Checkbox
	self.VGUI.DialTypeCheckbox:SetToolTip("Dialmode: If checked, it dials the Stargate like a DHD. If unchecked, like a Dialling Computer");
	self.VGUI.DialTypeCheckbox:SetValue(self:GetCookie("DHDDial",false));
	self.VGUI.DialTypeCheckbox.ConVarChanged = function(CheckBox)
		self:SetCookie("DHDDial",CheckBox:GetChecked());
	end
	
	-- The Dial Button
	self.VGUI.DialImageButton:SetSize(16,16);
	self.VGUI.DialImageButton:SetImage("gui/silkicons/folder_go");
	self.VGUI.DialImageButton:SetToolTip("Dial the Stargate");
	self.VGUI.DialImageButton.DoClick = function(ImageButton)
		self:DialGate(self.VGUI.AddressMultiChoice.TextEntry:GetValue());
	end
	
	-- The AbortDial Button
	self.VGUI.AbortDialImageButton:SetSize(16,16);
	self.VGUI.AbortDialImageButton:SetImage("gui/silkicons/check_off");
	self.VGUI.AbortDialImageButton:SetToolTip("Abort Dialling/Close Gate");
	self.VGUI.AbortDialImageButton.DoClick = function(ImageButton)
		if(self.OnAbort) then self.OnAbort(self.Entity) end;
	end
	
	-- Add the gates to the list
	self.Gates = self:GetGates();
	self:AddGatesToList(self.Data.LastSearchText);
end

--################# Sets the Address, entered in the dialbox @aVoN
function PANEL:SetText(text)
	self.VGUI.AddressMultiChoice.TextEntry:SetText(text or "");
end

--################# Gets the value of the addressfield @aVoN
function PANEL:GetValue()
	return self.VGUI.AddressMultiChoice.TextEntry:GetValue() or "";
end

--################# Adds the address we selected to the "LastDialled" list and plays a sound. Also calls the Dial function @aVoN
function PANEL:DialGate(address)
	address = (address or ""):upper();
	if(address:len() == 6) then
		surface.PlaySound(self.Sounds.Info);
		-- Shall we add this address to the "already dialled" list?
		for k,v in pairs(self.Data.Addresses) do
			if(self:Find(v,address,true)) then
				self.Data.Addresses[k] = nil;
			end
		end
		local addresses = {address};
		-- Keep the last 15 dialled addresses in cache...
		for k,v in pairs(table.ClearKeys(self.Data.Addresses)) do
			if(k < 15) then table.insert(addresses,v) end;
		end
		self.Data.Address = address;
		self.Data.Addresses = addresses;
		-- Clear old choices first!
		self.VGUI.AddressMultiChoice.Choices = {};
		-- Now, add the choices!
		for _,v in pairs(self.Data.Addresses) do
			self.VGUI.AddressMultiChoice:AddChoice(v);
		end
		if(self.OnDial) then self.OnDial(self.Entity,address,self.VGUI.DialTypeCheckbox:GetChecked()) end;
	else
		surface.PlaySound(self.Sounds.Warning);
	end
end

--################# The necessary Entity, this panel comes along with (the gate) @aVoN
function PANEL:SetEntity(e)
	self.Entity = e;
	--##### Now also update the Text of the search field and the multichoice's diallied addresses
	self.VGUI.AddressMultiChoice.Choices = {};
	for _,v in pairs(self.Data.Addresses) do
		self.VGUI.AddressMultiChoice:AddChoice(v);
	end
	--##### Search text
	self.VGUI.SearchTextEntry:SetText(self.Data.LastSearchText or "");
	local caret_pos = math.Clamp(self.VGUI.AddressMultiChoice.TextEntry:GetCaretPos(),0,self.Data.Address:len());
	self.VGUI.AddressMultiChoice.TextEntry:SetText(self.Data.Address);
	self.VGUI.AddressMultiChoice.TextEntry:SetCaretPos(caret_pos);
	--##### The searchfield has been reset - Update the list
	self:RefreshList(true);
	-- "SetEntity" normally also means, we are getting "opened". So use this as an event and make the TextEntry focussed (for diallign a gate)
	self.VGUI.AddressMultiChoice.TextEntry:RequestFocus();
end

--################# Perform the layout @aVoN
function PANEL:PerformLayout()
	local w,h = self:GetSize();
	-- The Address List
	self.VGUI.AddressListView:SetSize(w,h-15);
	-- Fix a bug in the DListView: We will redraw it's Lines to fit to the altered size!
	self.VGUI.AddressListView:DataLayout();
	-- The Address Label
	self.VGUI.AddressLabel:SetPos(w-172,0);
	-- The Address TextEntry
	self.VGUI.AddressMultiChoice:SetPos(w-127,0);
	self.VGUI.DialTypeCheckbox:SetPos(w-55,3);
	-- The Dial Button
	self.VGUI.DialImageButton:SetPos(w-40,2);
	-- The Abort Button
	self.VGUI.AbortDialImageButton:SetPos(w-20,2);
end

--################# Adds the gates to the AddressList @aVoN
function PANEL:AddGatesToList(s)
	if(s == "") then s = nil end;
	-- Get the last selected gate:
	local line = self.VGUI.AddressListView:GetSelectedLine();
	local last_address;
	if(line) then
		last_address = self.VGUI.AddressListView:GetLine(line):GetColumnText(1);
	end
	-- Clear old view
	self.VGUI.AddressListView:Clear();
	for k,v in pairs(self.Gates) do
		-- Never add the gate we are dialling from to this panel
		if(ValidEntity(v) and v ~= self.Entity and not v:GetPrivate()) then
			local address = v:GetGateAddress();
			if(address ~= "") then
				local name = v:GetGateName();
				if(not s or (self:Find(address,s,true) or (name ~= "" and self:Find(name,s)))) then
					if(name == "") then name = "N/A" end;
					self.VGUI.AddressListView:AddLine(address,name);
				end
			end
		end
	end
	if(last_address) then
		for _,v in pairs(self.VGUI.AddressListView.Lines) do
			if(v:GetColumnText(1) == last_address) then
				v:SetSelected(true);
				break;
			end
		end
	end
	self.VGUI.AddressListView:SortByColumn(1,true); -- Resort by addresses
end

--################# Refreshs the list of the panel
function PANEL:RefreshList(update)
	if(update) then
		self.Gates = self:GetGates();
	end
	self:AddGatesToList(self.VGUI.SearchTextEntry:GetValue());
end

--################# Finds needle (n) in haystack (h). Setting letters to true will search for the occurance of all letters (no matter in what order they are) @aVoN
function PANEL:Find(h,n,letters)
	local h = h:upper();
	local n = n:upper();
	if(letters) then
		local letters = n:gsub("[^0-9A-Z@]",""):TrimExplode(""); -- Removes illegal chars, because "letters" is only used for addresses in here
		for _,v in pairs(letters) do
			if(not h:find(v)) then return false end;
		end
		return true;
	else
		local words = n:gsub("[%s]+"," "):TrimExplode(" "); -- Remove any space character (maybe multiple) with only one
		for _,v in pairs(words) do
			if(not h:find(v)) then return false end;
		end
		return true;
	end
end

--################# Get every valid gates @aVoN
function PANEL:GetGates()
	-- Clientside does not support wildcards yet.. Shame on you, garry!
	local sg1 = ents.FindByClass("stargate_sg1");
	local sga = ents.FindByClass("stargate_atlantis");
	--local super = ents.FindByClass("stargate_supergate"); -- FIXME: Should be added, if the supergate is done - and some switch to only find supergates or normal gates
	local gates = {};
	for _,v in pairs(sg1) do
		table.insert(gates,v);
	end
	for _,v in pairs(sga) do
		table.insert(gates,v);
	end
	--[[
	for _,v in pairs(super) do
		table.insert(gates,v);
	end
	--]]
	return gates;
end

--################# Think @aVoN
function PANEL:Think()
	-- Do the search, but delayed
	if(self.Data.LastSearchTyped and CurTime() - self.Data.LastSearchTyped > 0.5) then
		self.Gates = self:GetGates(); -- First, refresh stargates!
		self:AddGatesToList(self.Data.LastSearchText);
		self.Data.LastSearchTyped = nil;
		self.LastSelected = nil;
	end
end

vgui.Register("SAddressSelect",PANEL,"Panel");


--################# Shows how this thing has to be used @aVoN
function PANEL:GenerateExample()
	local VGUI = vgui.Create("SAddressSelect");
	VGUI:SetSize(180,80);
	VGUI:SetEntity(NULL); -- This defines the stargate this Address Panel is allocated to (Necessary, so we know, what gate we are currently using)
	VGUI.OnDial = function(gate,address,fast_dhd_mode) end; -- The function which shall be triggered when the user dials a gate
	VGUI.OnAbort = function(gate) end; -- The function to call, if you want to abort dialling/close a gate
	return VGUI;
end
