

local function TempBG(obj)
	local f = obj:GetGui()
	f:SetBackdrop( { 
		bgFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Background", 
		edgeFile = "edgeFile", tile = false, tileSize = 0, edgeSize = 32, 
		insets = { left = 0, right = 0, top = 0, bottom = 0 }
	});
	local color = {r=0.4,g=0.5,b=0.6}
	if GHI_EffectColors then
		color = GHI_EffectColors[GHI_ColorList[random(1,6)]];
	end
	f:SetBackdropColor(color.r, color.g, color.b,1);
end

--===================================================
--										Line
--===================================================	

function GHU_Line(_parent,_width)
	assert(type(_width)=="number","Line must be created with a width");
	local class = Class("GHU_Line");
	local parent,gui;
	
	local objects;
	
	local enabled;
	local width,yOff;
	local allignment;
	local focusObj;
	local selectionStartObj,selectionEndObj;
	local needUpdate;
	
	local GetObjectIndex = function(obj)
		for i,o in pairs(objects) do
			if o == obj then 
				return i;
			end
		end
		return;	
	end
	
	local GetEnabledObjects = function()
		local o = {};
		for _,obj in pairs(objects) do
			if obj:IsEnabled() then
				table.insert(o,obj);			
			end
		end
		return o;
	end
	
	-- ********* functions **********
			
	-- BackwardsDelete() -- attempts a backwards delete
	class.BackwardsDelete = function(self)
		local obj = focusObj;
		-- if there is no focus, go to the last enabled obj 
		if not(obj) then
			local i = #(objects)
			while(i > 0 and not(objects[i]:IsEnabled())) do i=i-1; end
			if i == 0 then -- no object found
				return true, "Beginning of line reached. Line is empty."			
			end
			obj = objects[i];
		end
		
		-- get index of obj
		local objI = GetObjectIndex(obj);
		
		-- attempt backwards delete inside the focus object
		if obj.BackwardsDelete then
			if not(obj:BackwardsDelete()) then 
				-- if it goes well _return
				return;			
			end
		elseif obj:GetType()=="GHU_Text" then -- handle backwards delete on a text
			local fPos = obj:GetFocus();
			if fPos > 0 then -- if the position is not the first
				local t = obj:GetText();
				obj:SetText(string.sub(t,0,fPos-1)..string.sub(t,fPos+1));
				self:SetFocus(objI,fPos-1);
				return;
			end
		end
		
		-- after this point the backwards delete should be preformed on previous object
		
		objI = GetObjectIndex(focusObj)-1;
		
		-- find prev enabled obj
		while(objI > 0 and not(objects[objI]:IsEnabled())) do objI=objI-1; end
		if objI == 0 then -- no object found
			return true; --, "Beginning of line reached."			
		end
		obj = objects[objI];
				
		-- attempt backwards delete inside the new object
		if obj.BackwardsDelete then
			if not(obj:BackwardsDelete()) then 
				-- if it goes well _return
				return;			
			end
		elseif obj:GetType()=="GHU_Text" then -- handle backwards delete on a text
			local fPos = obj:GetLength();
			if fPos > 0 then -- if the position is not the first
				local t = obj:GetText();
				obj:SetText(string.sub(t,0,fPos-1));
				self:SetFocus(objI,fPos-1);
				return;
			end
		
				
		end	
		-- disable the object
		obj:Disable();
				
	end
	-- DeleteSelected() -- deletes / disabled all objects which are within the selection
	class.DeleteSelected = function(self)
		if not(selectionStartObj) then selectionStartObj = objects[1]; end
		if not(selectionEndObj) then selectionEndObj = objects[#(objects)]; end
		
		-- determine if everything enabled is selected
		local first = 0;
		local last = #(objects);
		while(first <= #(objects) and not(object[first]:IsEnabled())) do
			first = first+1;
		end
		while(last > 0 and not(object[last]:IsEnabled())) do
			last = last-1;
		end
		
		if last == 0 and first > 0 then -- nothing is Enabled
			return true,"Line can be disabled. Nothing is enabled";		
		end
		if first == GetObjectIndex(selectionStartObj) and last == GetObjectIndex(selectionEndObj) then
			return true,"Line can be disabled. Everything is selected";		
		end
	
		-- disable the selected
		if selectionStartObj == selectionEndObj then -- only one object
			if selectionStartObj:DeleteSelected() then
				-- it can be disabled
				selectionStartObj:Disable();
			end
		else
			for i=GetObjectIndex(selectionStartObj),GetObjectIndex(selectionEndObj) do
				-- skip disabled objects
				if objects[i]:IsEnabled() then
					if objects[i]:DeleteSelected() then
						-- the object should be disabled
						objects[i]:Disable();					
					end
				end			
			end
		end
	end
	-- Disable
	class.Disable = function(self)
		enabled = false;
		self:Hide();	
	end
	-- Dispose	dispose self and all objects
	class.Dispose = function(self)
		for i,obj in pairs(objects) do
			if obj.Dispose then 
				obj:Dispose();
			elseif obj.Hide then
				obj:Hide();			
			end
		end
		self:Hide();
		wipe(gui)
		wipe(self);
	end
	-- Enable()
	class.Enable = function(self)
		enabled = true;
		self:Show();	
	end
	-- ForwardDelete()
	class.ForwardDelete = function(self)
		local obj = focusObj;
		-- if there is no focus, go to the first enabled obj 
		if not(obj) then
			local i = 1;
			while(i <= #(objects) and not(objects[i]:IsEnabled())) do i=i+1; end
			if i == 0 then -- no object found
				return true, "End of line reached. Line is empty."			
			end
			obj = objects[i];
		end
		
		-- attempt forward delete inside the focus object
		if obj.ForwardDelete then
			if not(obj:ForwardDelete()) then 
				-- if it goes well _return
				return;			
			end
		elseif obj:GetType()=="GHU_Text" then -- handle forward delete on a text
			local fPos = obj:GetFocus();
			if fPos < obj:GetLength() then -- if the position is not the last
				local t = obj:GetText();
				obj:SetText(string.sub(t,0,fPos)..string.sub(t,fPos+2));
				return;
			end
		end
	
		-- get index of obj
		local objI = GetObjectIndex(focusObj)+1;
		
		
		-- find enabled obj after
		while(objI <= #(objects) and not(objects[objI]:IsEnabled())) do objI=objI+1; end
		if objI > #(objects) then -- no object found
			return true, "End of line reached."			
		end
		obj = objects[objI];
		
		
		-- attempt forward delete inside the new object
		if obj.ForwardDelete then
			if not(obj:ForwardDelete()) then
				-- if it goes well _return
				return;			
			end
		elseif obj:GetType()=="GHU_Text" then -- handle forward delete on a text
			if 0 < obj:GetLength() then -- if the position is not the last
				local t = obj:GetText();
				obj:SetText(string.sub(t,2));
				self:SetFocus(objI,0);
				return;
			end
		end	
		-- disable the object
		obj:Disable();
	end
	-- GetAllignment()
	class.GetAllignment = function(self)
		return allignment;	
	end
	-- GetFocus()
	class.GetFocus = function(self) -- return index of the focus and get focus details from the object. Returns nil if no focus	
		if focusObj and focusObj:IsEnabled() then
			return GetObjectIndex(focusObj),focusObj:GetFocus();		
		else
			return;		
		end
	end
	class.GetFocusCoordinate = function(self)
		if focusObj and focusObj:IsEnabled() then
			local x,font = focusObj:GetFocusCoordinate();
			return x,self:GetOffset()+gui:GetHeight(),font;			
		end
		return 0,0;
	end
	-- GetHeight()
	class.GetHeight = function(self)
		return gui:GetHeight();
	end
	-- GetGui()
	class.GetGui = function(self)
		return gui;
	end
	-- GetNumObjects()
	class.GetNumObjects = function(self)
		local c = 0;
		for _,obj in pairs(objects) do
			if obj:IsEnabled() then
				c=c+1;			
			end
		end
		return c,#(objects);
	end
	-- GetObject(i)
	class.GetObject = function(self,i)
		return objects[i];	
	end
	-- GetOffset()
	class.GetOffset = function(self) 
		local _,_,_,_,y = gui:GetPoint(1);
		return -y;	
	end
	-- GetParent()
	class.GetParent = function(self)
		return parent;
	end
	-- GetPos(x,y) -- get the position at (x,y). The coordinates are relative to line. If the only focus flag is raised, it will only take objects that are able to handle focus
	class.GetPos = function(self,x,y,onlyFocusable)
		-- determine the object at the given x coordinate.
		local c,w = 0,0;
		local cFocusable = 0;
		while (c < #(objects)) do
			c=c+1;
			if objects[c]:IsEnabled() then
				if objects[c].SetFocus then
					cFocusable = c;
				end
				
				if objects[c]:GetWidth() + w > x then
					break;
				else
					w = objects[c]:GetWidth();				
				end
			end
		end
		
		if onlyFocusable then -- make sure that only objects that can have focus is given as result
			c = cFocusable;
		end
		
		if objects[c]:IsClass("GHU_Text") then
			return c,objects[c]:GetWidthPos(x-w);
		end		
		return c,objects[c]:GetPos(x-w,y);
	end
	-- GetSelection()
	class.GetSelection = function(self)
		if not(selectionStartObj) then return {},{}; end
	
		local s_start,_ = selectionStartObj:GetSelection();
		local _,s_end = selectionEndObj:GetSelection();
		if not(type(s_start)=="table") then s_start = {s_start} end
		if not(type(s_end)=="table") then s_end = {s_end} end
		
		table.insert(s_start,1,GetObjectIndex(selectionStartObj))
		table.insert(s_end,1,GetObjectIndex(selectionEndObj))
		
		return s_start,s_end;
	end
	-- GetWidth()
	class.GetWidth = function(self)
		return width;	
	end
	-- Hide()
	class.Hide = function(self)
		gui:Hide();
		for _,obj in pairs(objects) do
			if obj.Hide and obj.GetType then
				obj:Hide();
			end
		end
	end
	-- InsertText(str)
	class.InsertText = function(self,str) 
		if focusObj and focusObj:IsEnabled() then
			focusObj:InsertText(str);
		end
		needUpdate = true;
	end
	-- InsertObject(obj)	The text area can insert an object in the line. Always at focus or e_nd of line
	class.InsertObject = function(self,obj,asFirst)
		assert(type(obj)=="table","Object must be a class",type(obj));
		assert(obj:InheritsInterface("GHU_Object"),"Object must inherit the GHU_Object interface.");
		
		-- check if the current focus is in the middle of another object and see if it can be split
		--print("Insert object not implemented");
		
		-- temp solution
		needUpdate = true;
		if asFirst then
			table.insert(objects,1,obj);
		else
			table.insert(objects,obj);
		end
		obj:SetParent(self);
		obj:SetOffset(0);
	end
	-- IsEnabled()
	class.IsEnabled = function(self)
		return enabled;	
	end
	-- MoveLeft() -- returns position of the location one field to the left
	class.MoveLeft = function(self) 
		if not(focusObj) then
			return;		
		end
		
		-- 
		if focusObj.MoveLeft then
			if not(focusObj.MoveLeft()) then
				return;
			end
			-- it was not possible to move more left
		elseif focusObj.SetFocus then
			local fPos = focusObj:GetFocus();
			if fPos > 0 then
				focusObj:SetFocus(fPos-1);
				return			
			end		
		end
		
		-- find the next object to the left that got SetFocus
		local i = GetObjectIndex(focusObj);
		while (i > 1) do
			i = i-1;
			if objects[i]:IsEnabled() and objects[i].SetFocus then
				-- set focus to this object		
				focusObj = objects[i];
				focusObj:SetFocus(focusObj:GetLength());
				return;					
			end
		end		
		
		return true,"Reached start of line";
	end
	-- MoveRight()
	class.MoveRight = function(self)
		if not(focusObj) then
			return;		
		end
		if focusObj.MoveRight then
			if not(focusObj.MoveRight()) then
				return;
			end
			-- it was not possible to move more right
		elseif focusObj.SetFocus then
			local fPos = focusObj:GetFocus();
			if fPos < focusObj:GetLength() then
				focusObj:SetFocus(fPos+1);
				return			
			end		
		end
		
		-- remove old focus
		--if focusObj.SetFocus then -- this is triggered at end of last line
		--	focusObj:SetFocus(nil);
		--e nd
		
		
		-- find the next object to the right that got SetFocus
		local i = GetObjectIndex(focusObj);
		while (i < #(objects)) do
			i = i+1;
			if objects[i]:IsEnabled() and objects[i].SetFocus then
				-- set focus to this object		
				focusObj = objects[i];
				focusObj:SetFocus(0);
				return;					
			end
		end		
		
		return true,"Reached end of line";
	end
	-- MoveToStart()
	class.MoveHome = function(self)
		-- moves to the first position of the line. 
		local i = 1;
		while (i <= #(objects)) do
			if objects[i]:IsEnabled() and objects[i].SetFocus then
				-- remove old focus
				if focusObj then
					focusObj:SetFocus(nil);								
				end
				
				-- insert New
				focusObj = objects[i];
				objects[i]:SetFocus(0);
				return
			end		
			i=i+1;
		end
		
	end
	-- MoveToEnd()
	class.MoveEnd = function(self)-- todo
		local i = #(objects);
		while (i > 0) do
			if objects[i]:IsEnabled() and objects[i].SetFocus then
				-- remove old focus
				if focusObj then
					focusObj:SetFocus(nil);								
				end
				
				-- insert New
				focusObj = objects[i];
				focusObj:SetFocus(focusObj:GetLength());
				return
			end		
			i=i-1;
		end
	end
	-- NeedUpdate()
	class.NeedsUpdate = function(self)
		return needUpdate;	
	end
	-- RequestUpdate()
	class.RequestUpdate = function(self)
		needUpdate = true;
	end	
	class.CutFirstObjects = function(self,widthToCut)
		
	end
	-- SetAllignment(al)
	class.SetAllignment = function(self,al)
		assert(al=="l" or al=="c" or al=="r","Illigal allignment. Accepted: l, c or r");
		allignment = al;
	end
	-- SetFont(font)
	-- SetFocus(objI,pos,...) -- remove focus from old frame. Set focus on new. objI = nil => remove focus
	class.SetFocus = function(self,objI,...)
		if objI == nil then -- remove focus
			if focusObj then
				focusObj:SetFocus(nil);
			end
			focusObj = nil;
			return		
		end
	
		assert(objects[objI],"Focus object index out of bounds");
		assert(objects[objI].SetFocus,"Focus object does not have a set focus function.");
		
		local newFocusObj = objects[objI];
		
		-- remove focus from the old 
		if focusObj and not(newFocusObj == focusObj) then
			focusObj:SetFocus(nil);		
		end
		
		focusObj = newFocusObj;
		local x,font = newFocusObj:SetFocus(...);
		return x,self:GetOffset()+gui:GetHeight(),font;				
	
	end
	-- SetOffset(y)
	class.SetOffset = function(self,yOff)
		gui:ClearAllPoints();
		gui:SetPoint("TOPLEFT",gui:GetParent(),"TOPLEFT",0,-yOff);
	end
	-- SetSelection(startS,endS)
	class.SetSelection = function(self,startS,endS)
		assert(type(startS)=="table" and type(endS)=="table","Selection must be two tables with values");
		
		-- remove old Selection
		if selectionStartObj and selectionEndObj then
			for i=GetObjectIndex(selectionStartObj),GetObjectIndex(selectionEndObj) do
				-- skip disabled objects
				if objects[i]:IsEnabled() then
					objects[i]:SetSelection();
				end			
			end
		end
			
		-- unpack
		
		startPos = startS
		local startI = table.remove(startPos,1);
		endPos = endS;
		local endI = table.remove(endPos,1);
		
		-- boundries and std
		startI = max(0,min(#(objects),startI or 0));
		endI = max(0,min(#(objects),endI or #(objects)));
		--print("from",startI,"to",endI);
		assert(startI <= endI,"start index higher than end index");
		
		if startI == endI then
			if objects[startI]:IsEnabled() then
				objects[startI]:SetSelection(startPos,endPos)
			end
		else
			-- select first obj
			if objects[startI]:IsEnabled() then
				objects[startI]:SetSelection(startPos)
			end
			
			-- select middle objects
			for i=startI+1,endI-1 do
				if objects[i]:IsEnabled() then
					object[i]:SetSelection();
				end	
			end
			
			-- select last obj
			if objects[endI]:IsEnabled() then
				objects[endI]:SetSelection(nil,endPos)
			end
		end
		selectionStartObj = objects[startI];
		selectionEndObj = objects[endI];
		
	end
	-- SetWidth(y)
	class.SetWidth = function(self,y)
		width = max(0,y);	
	end
	-- Show()
	class.Show = function(self)
		gui:Show();
		for _,object in pairs(objects) do
			if object:IsEnabled() then
				object:Show();
			end
		end
	end
	
	local function MergeSimilarAdjacentObjects()
		-- Merge similar adjacent objects. The old object is keept, but disabled, to use for undo/redo
		for i=2,#(objects) do
			local j = i-1;
			local obj1 = objects[j];			
			
			-- find the closest enabled object before obj2
			while (not(obj1:IsEnabled()) and j > 1) do
				j=j-1;
				obj1 = objects[j];	
			end
			
			local obj2 = objects[i];
			if obj1:IsEnabled() and obj2:IsEnabled() and obj1:IsSameClass(obj2) then -- the objects are similar
				if obj1:IsClass("GHU_Text") then -- Checks for GHU_Text
					if obj1:GetFont():IsSame(obj2:GetFont()) and not(obj1:GetReqNewline()) then print("merging two objects");
						-- merge the text together
						obj1:SetText(obj1:GetText()..obj2:GetText());
						obj1:SetReqNewline(obj2:GetReqNewline());
						obj2:SetText("");
						obj2:SetReqNewline(false);
						obj2:Disable();
						
					end				
				end
				-- evt other types that can be merged
				
			end					
		end
	end
	
	-- Update()	-- merge eventual similar adjacent objects, set offset of objects, cut and return surplus objects or a number which is the extra free space
	class.Update = function(self) 
		
		needUpdate = false;
		
		MergeSimilarAdjacentObjects()
		
		-- run trough all objects
		local surplus = {};
		local x,h=0,0;
		local i=1;
		local newFocusPos;	
		while( i <= #(objects)) do
			
			local object = objects[i];
			
			-- only look at the enabled objects
			if object:IsEnabled() then
				-- update the largest height
				h = max(h,object:GetHeight());
				
				if x + object:GetWidth() <= width then
					object:SetOffset(x);
					x = x + object:GetWidth();	
					
					if object.GetReqNewline and object:GetReqNewline() then
						i=i+1;
						break;
					end								
				else
					-- if the last object fit is a text object try to break it up in a fitting size
					if object:IsClass("GHU_Text") then

						local pos = object:GetWidthPos(width-x);
						local text = object:GetText();
						
						-- find a fitting place to split
						pos = pos+1
						while (pos > 1) do
							pos = pos-1;
							local charByte = strbyte(text,pos);
							if charByte == 32 or  charByte == 45 then -- space or -
								break;
							end
						end
						
						object:SetText(text:sub(0,pos));
						
						local newObj = GHU_Text(self);
						newObj:SetText(text:sub(pos+1));
						newObj:SetFont(object:GetFont());
						newObj:Show();
						
						-- update evt focus pos
						if focusObj == object then
							local fpos = object:GetFocus();
							if fpos > pos then
								print(fpos-pos);
							end
						end
						
						-- req new line inheriatage
						newObj:SetReqNewline(object:GetReqNewline());
						object:SetReqNewline(false);
						i=i+1;
						table.insert(objects,i,newObj);
					end
					
					break;						
				end	
			end 
			i=i+1;	
		end
				
		-- set height
		gui:SetHeight(max(8,h));
		
		
		
		-- add surplus objects
		while (i <= #(objects) and i > 0) do
			table.insert(surplus,table.remove(objects,i)); 
			-- no reason to increase i, as the object table srink in size
		end
		
		-- todo: update the position of the non surplus frames if the allignment is not left
		
		
		
		
		
		return surplus,newFocusPos;
	end
	
	-- ============== setup ==============
	
	parent = _parent;
	width = _width;
	objects = {};
	
	-- gui
	local f = parent;
	if (parent.GetGui) then 
		f = parent:GetGui();
	end

	gui = CreateFrame("Frame",nil,f);
	gui:ClearAllPoints();
	gui:SetPoint("TOPLEFT",f,"TOPLEFT",0,0);
	gui:SetHeight(20);
	gui:SetWidth(width);
	gui:Hide();
	gui.model = class;
		
	enabled = true;
	yOff = 0;
	allignment = "l";
	
	-- insert a basic text obj
	--table.insert(objects,GHU_Text(class))
	
	
	selectionStartObj = objects[1];
	selectionEndObj = objects[1];
	
	
	class:Update();
	
	--GHU_TestBG(class);
	
	return class;
end


