--[[---------------------------------------------------------------------------

Copyright (c) 2008 by K. Scott Piel 
All Rights Reserved

E-mail: < kscottpiel@gmail.com >
Web:    < http://www.scottpiel.com >

This file is part of nUI.

    nUI 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.

    nUI 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 nUI.  If not, see <http://www.gnu.org/licenses/>.
	
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

--]]---------------------------------------------------------------------------

if not nUI_Options then nUI_Options = {}; end
if not nUI_Bars then nUI_Bars = {}; end

local CreateFrame = CreateFrame;

local BarFrames   = {};

local event_frame = CreateFrame( "Frame", "nUI_StatusBarEvents", WorldFrame );
local timer       = 1 / nUI_DEFAULT_FRAME_RATE;

nUI_Options.show_anim = true;

-------------------------------------------------------------------------------
-- bar event management

local function onBarEvent()
	
	if event == "VARIABLES_LOADED" then
		
		-- set up a slash command handler for dealing with setting the tooltip mode
		
		local option = nUI_SlashCommands[nUI_SLASHCMD_SHOWANIM];
		
		nUI_SlashCommands:setHandler( option.command, 
			
			function( msg )
				
				nUI_Options.show_anim = not nUI_Options.show_anim;
				
				DEFAULT_CHAT_FRAME:AddMessage( (option.message):format( nUI_Options.show_anim and nUI_L["|cFF00FF00ENABLED|r"] or nUI_L["|cFFFF0000DISABLED|r"] ), 1, 0.83, 0 );
				
			end
		);
		
	end
end

event_frame:SetScript( "OnEvent", onBarEvent );
event_frame:RegisterEvent( "VARIABLES_LOADED" );

-------------------------------------------------------------------------------
-- bar update management handles displaying changes in bar length and animation
-- 
-- this bar updater helps throttle hammering of the graphics engine by health
-- and mana updates, etc. particularly when in a large raid and battlegrounds
-- where you could potentially be slamming the GPU with changes in health
-- and mana for 30, 40 or more unit IDs across who knows how many bars. This
-- engine ensures a smooth bar update frequency regardless of how many times
-- the values may have changed in the interim. It also handles update optimizatio
-- by ensuring the bar actually changed before hitting the GPU as well as doing
-- the work required to animate bars that are so configured presuming the user
-- has not disabled animation.

local function onBarUpdate( who, elapsed )
	
	timer = timer - elapsed;
	
	if timer <= 0 then

		timer = nUI_Unit.frame_rate;

		for i=#BarFrames, 1, -1 do

			local frame  = BarFrames[i];
			
			if frame.enabled and frame.active then
					
				local x1     = frame.x1;
				local x2     = frame.x2;
				local y1     = frame.y1;
				local y2     = frame.y2
				local xOfs   = 0;
				local yOfs   = 0;
				local bar    = frame.bar;			
				local anim   = frame.anim;
	
				if anim and nUI_Options.show_anim then
					
					local step = (anim.step + 1) % anim.num_steps;
					anim.step  = step;
					xOfs       = step * anim.xOfs;
					yOfs       = step * anim.yOfs;
					x1         = x1 + xOfs;
					x2         = x2 + xOfs;
					y1         = y1 + yOfs;
					y2         = y2 + yOfs;
					
				end
	
				if bar.x1 ~= x1
				or bar.x2 ~= x2
				or bar.y1 ~= y1
				or bar.y2 ~= y2
				then
					
					bar.x1 = x1;
					bar.x2 = x2;
					bar.y1 = y1;
					bar.y2 = y2;

--					nUI:debug( ("nUI_StatusBar: "..frame:GetName().." -- start=%0.3f, window=%0.3f, pct=%0.3f, height=%0.1f"):format( frame.start, frame.window, frame.pct, frame.start + frame.window * frame.pct ) );
					
					if frame.horizontal then bar:SetWidth(  frame.start + frame.window * frame.pct );
					else bar:SetHeight(  frame.start + frame.window * frame.pct );
					end

--					nUI:debug( ("nUI_StatusBar: "..frame:GetName().." -- x1=%0.3f, x2=%0.3f, y1=%0.3f, y2=%0.3f"):format( x1, x2, y1, y2 ) );
				
					bar:SetTexCoord( x1, y1, x1, y2, x2, y1, x2, y2 );
				end
			end
		end	
	end
end

event_frame:SetScript( "OnUpdate", onBarUpdate );

-------------------------------------------------------------------------------
-- creates a new generic status bar frame tied directly to its parent frame
-- including taking its height and width from the parent frame (at all times)
-- the resulting status bar frame has no functionality other than to create
-- a bar frame and set the size of a colored bar within it.
--
-- Note: the status bar created will set a "OnSizeChanged" method for the 
--       parent frame. If you assign a method to OnSizeChanged for the parent
--       AFTER you cresate the status bar frame, then you should be sure to
--       call the status bar's onSizeChanged() method from inside your parent
--       frames OnSizeChanged event.
--
-- name:	  the name to give this bar's primary frame
-- parent:	  a reference to the parent frame this bar will be attached to
-- orient:    where is the origin of the colored bar? "TOP", "BOTTOM", "LEFT" or "RIGHT"
-- height:    the height of the bar texture (not of the rendered bar!)
-- width:     the width of the bar texture (not of the rendered bar!)
-- bar:       the underlying bar texture
-- min:		  given the origin edge of the bar, how far from the edge is the start of the bar or "min" value (0 <= value <= 1) where 0 is the origin and 1 is the opposite side
-- max:		  again, given the origin, how far across the texture is the end of the bar or "max" value
-- overlay:   the path to a BLP or TGA file to overlay the bar with (for style -- such as a frame around the bar)

function nUI_Bars:createStatusBar( name, parent )
	
	local frame = CreateFrame( "Frame", name, parent );
	
	frame.parent             = parent;
	frame.active             = false;
	frame.horizontal         = true;
	frame.orient             = "LEFT";
	frame.left               = true;
	frame.view_size          = 1;
	frame.bar                = frame:CreateTexture( "$parentBar", "BORDER" );
	frame.overlay            = frame:CreateTexture( "$parentOverlay", "ARTWORK" );
	frame.parent.sizeChanged = frame.parent:GetScript( "OnSizeChanged" );
	
	frame.bar:SetAlpha( 0 );	
	frame.bar:SetPoint( "LEFT", frame, "LEFT", 0, 0 );
	frame.overlay:SetAllPoints( frame );
	
	frame:SetScript( "OnSizeChanged", function() frame.onSizeChanged(); end );

	-- notify the bar it is no longer needed
	
	frame.deleteBar = function()
	
		nUI:TableRemoveByValue( BarFrames, frame );
		
	end
	
	-- set bar animation on or off.. passing a nil value for anim will disabled animation (the default)
	-- otherwise, anim should be a table structure containing the following information...
	--
	-- anim =
	-- {
	--		int num_steps	-- how many steps are there in a complete cycle? The animation will update the animation a one step every frame cycle based on the user's frame rate, normally 30 steps per second.
	--		float xOfs		-- how far to step the animation texture horizontally in a single step as a percent of the texture width ( 0 < xOfs < 1), negative values move the animation right to left.
	--		float yOfs		-- how far to step the animation texture veritically in a single step
	-- }
	
	frame.setAnimation = function( anim )
	
		frame.anim = anim;
		
		if anim then anim.step = 0; end;
		
	end
	
	-- update the sizing of the status bar texture if the frame size changes
	
	frame.onSizeChanged = function()
		
		frame.height = frame:GetHeight();
		frame.width  = frame:GetWidth();
		
		if frame.horizontal then
			
			frame.start  = frame.width * frame.min_offset;
			frame.window = frame.width * frame.view_size;
			
			frame.bar:SetHeight( frame.height );
			frame.bar:SetWidth(  frame.start + frame.window * frame.pct );

		else
			
			frame.start  = frame.height * frame.min_offset;
			frame.window = frame.height * (frame.view_size or 1);
			
			frame.bar:SetWidth( frame.width );
			frame.bar:SetHeight(  frame.start + frame.window * frame.pct );

		end
	end
	
	-- set the orientation of the bar relative to the parent frame... as in
	-- which direction the status bar will grow in. If the value of the orientation
	-- is "LEFT" then the status bar is anchored to the left edge of the parent 
	-- frame and will grow to the right, "BOTTOM" anchors to the bottom edge of
	-- the parent and will then grow upwards, etc.
	
	frame.setOrientation = function( orientation )
	
		local orient = strupper( orientation );
		
		-- we're only going to update if we're actually changing the orientation
		
		if frame.orient ~= orient then
			
			-- make sure we have a sane value for the orientation
			
			frame.left   = orient == "LEFT"
			frame.right  = orient == "RIGHT"
			frame.bottom = orient == "BOTTOM"
			frame.top    = orient == "TOP"
			
			if  not frame.left
			and not frame.right
			and not frame.top
			and not frame.bottom
			then 
				
				frame.left = true;
				orient     = "LEFT";
				
				DEFAULT_CHAT_FRAME:AddMessage( nUI_L["nUI: [%s] is not a valid option for orienting a status bar... use LEFT, RIGHT, TOP or BOTTOM"]:format( orient ), 1, 0.5, 0.5 );
				
			end

			-- remember what our current orientation is
			
			frame.orient = orient;
			
			-- reset the anchor point for the status bar
			
			frame.bar:ClearAllPoints();				
			frame.bar:SetPoint( orient, frame, orient, 0, 0 );
			
			-- if the orientation is horizontal, then note it and set
			-- the height of the bar (which will not change again unless
			-- the parent is resized or we change orientation again)
			
			local reoriented = false;

			if frame.left
			or frame.right
			then

				if not frame.horizontal then
					
					reoriented       = true;
					frame.horizontal = true;
					frame.bar:SetHeight( frame:GetHeight() );

				end
		
			-- otherwise we have a vertical orientation and set the width
			-- of the bar (which will, also, not change again unless the
			-- parent is resized or the orientation is changed again)
			
			else
				
				if frame.horizontal then
					
					reoriented       = true;
					frame.horizontal = false;
					frame.bar:SetWidth( frame:GetWidth() );
				
				end
				
			end
			
			-- if the bar has been reoriented, then update it since we
			-- need to set its lenght on a different axis
			
			if reoriented then
				frame.updateBar();
			end
		end
	end;
		
	-- this method sets (or clears) the overlay graphic for the status bar... the
	-- overlay argument is a path to a BLP or TGA file, typically at least semi-transparent
	-- to give the statusbar a visual style or frame. Passing a nil overlay will hide the layer
	
	frame.setOverlay = function( overlay )

		if frame.overlay_texture ~= overlay then
			
			frame.overlay_texture = overlay;
			
			if not overlay then
				
				frame.overlay:SetAlpha( 0 );
				
			else
				
				frame.overlay:SetAlpha( 1 );
				frame.overlay:SetTexture( overlay );
				frame.overlay:SetTexCoord( 0, 0, 0, 1, 1, 0, 1, 1 );
				
			end
		end
	end

	-- likewise, this method sets the actual bar texture
	--
	-- bar			-- string path to the texture file to use or nil to use a solid bar color
	-- min_offset	-- offset from the origin edge of the bar texture to the start of the bar itself... 0 is at the origin edge, 1 is at the opposite edge -- default is 0 is min is nil
	-- max_offset	-- offset from the origin edge of the bar texture to the end of the bar iself -- default is 1 if max is nil
	
	frame.setBar = function( bar, min_offset, max_offset )
			
		if bar then frame.bar:SetTexture( bar );
		else frame.bar:SetTexture( 1, 1, 1, 1 );
		end

		frame.pct = 0;
		frame.bar:SetTexCoord( 0, 0, 0, 1, 1, 0, 1, 1 );
		frame.bar:ClearAllPoints();
		frame.bar:SetPoint( frame.orient or "LEFT", frame, frame.orient or "LEFT", 0, 0 );
		
		frame.min_offset = max( 0, min( 1, min_offset or 0 ) );
		frame.max_offset = max( 0, min( 1, max_offset or 1 ) );
		frame.view_size  = frame.max_offset - frame.min_offset;

		frame.onSizeChanged();
		
	end
	
	-- set the enabled state of the bar... when disabled, the bar is removed from the bar update
	-- loop to reduce frame drag/game engine load
	
	frame.setEnabled = function( enabled )
	
		if frame.enabled ~= enabled then
			
			frame.enabled = enabled;
			
			if enabled and frame.active then nUI:TableInsertByValue( BarFrames, frame );
			elseif not enabled then nUI:TableRemoveByValue( BarFrames, frame );
			end
			
		end
	end
	
	-- returns the current color set for the bar
	
	frame.getBarColor = function()
		
		local r = frame.cur_r or 0;
		local g = frame.cur_g or 0;
		local b = frame.cur_b or 0;
		local a = frame.cur_a or 0;
	
		return r, g, b, a;
		
	end

	-- set the length and, if required, the color of the status bar
	--
	-- note: this method expends extra energy in state management... as in knowing
	--       exactly what state it is currently in and only updating the frame text,
	--       content, colors, alphas, etc. when a change in state occurs. The extra
	--       effort is spent on state management in order to reduce impact to the
	--       graphis engine so as to preserve frame rate. It costs far less to check
	--		 a memory value than to burn through the graphics geometry. It does not
	--       matter how many times the unit changes GUID or how many times this 
	--       method will call, it will only alter the graphics elements when its
	--       relative state changes.

	frame.updateBar = function( percent, color )
	
		if frame.enabled then
				
			local bar  = frame.bar;
			local size = frame.view_size;	
			local pct  = max( 0, min( 1, percent or 0) );
			local r    = color and color.r or frame.r or 0;
			local g    = color and color.g or frame.g or 0;
			local b    = color and color.b or frame.b or 0;
			local a    = color and color.a or frame.a or 0;
			
			-- if the bar is visible, check to see if we need to recolor it
			
			if frame.r ~= r
			or frame.g ~= g
			or frame.b ~= b
			or frame.a ~= a
			then
		
				frame.r = r;
				frame.g = g;
				frame.b = b;
				frame.a = a;
				
				frame.bar:SetVertexColor( r, g, b, 1 );
				frame.bar:SetAlpha( a );
				
			end
			
			-- set the bar size if the pecentage of the bar  has changed
				
			if not percent or pct == 0 then
				
				frame.pct = 0;
				
				if frame.active then

					nUI:TableRemoveByValue( BarFrames, frame );
					
					frame.active = false;
					frame.bar:SetAlpha( 0 );
		
				end				
	
			elseif frame.pct ~= pct
			then

				frame.pct = pct;			
				
				-- how much of the bar should we be revealing now?
				
				local bar_length = size * pct;
				
				if frame.bar_length ~= bar_length 
				then
					
					frame.bar_length = bar_length;
					
					if frame.left then 
	
						frame.x1 = frame.min_offset;
						frame.x2 = frame.x1 + bar_length;
						frame.y1 = 0;
						frame.y2 = 1;
	
					elseif frame.right then
	
						frame.x2 = 1 - frame.min_offset;
						frame.x1 = frame.x2 - bar_length;
						frame.y1 = 0;
						frame.y2 = 1;
	
					elseif frame.top then
	
						frame.x1 = 0;
						frame.x2 = 1;
						frame.y1 = frame.min_offset;
						frame.y2 = frame.y2 + bar_length;
	
					elseif frame.bottom then
	
						frame.x1 = 0;
						frame.x2 = 1;
						frame.y2 = 1 - frame.min_offset;
						frame.y1 = frame.y2 - bar_length;
						
					end							
				end			
	
				if frame.x1 == frame.x2
				or frame.y1 == frame.y2
				then
					
					if frame.active then
						
						nUI:TableRemoveByValue( BarFrames, frame );

						frame.active = false;
						frame.bar:SetAlpha( 0 );
						
					end
					
				-- otherwise show the bar if it is not already visible
	
				elseif not frame.active 
				then
	
					nUI:TableInsertByValue( BarFrames, frame );
					bar:SetAlpha( 1 );
					
					frame.active = true;
	
				end
			end
		end
	end	

	frame.setBar();
	
	return frame;
	
end
