-- local functions
local UnitCastingInfo = UnitCastingInfo;
local UnitChannelInfo = UnitChannelInfo;
local UnitAttackSpeed = UnitAttackSpeed;
local UnitRangedDamage = UnitRangedDamage;
local UnitName = UnitName;
local UnitIsEnemy = UnitIsEnemy;
local GetSpellInfo = GetSpellInfo;
local GetNumPartyMembers = GetNumPartyMembers;
local GetNumRaidMembers = GetNumRaidMembers;
local tonumber = tonumber;
local pairs = pairs;
local type = type;
local min = min;
local max = max;
local floor = floor;
local abs = abs;
local unpack = unpack;
local string_format = string.format;
local string_sub = string.sub;
local string_gsub = string.gsub;
local string_len = string.len;
local string_match = string.match;
local table_insert = table.insert;

function Gnosis:UpgradeTable( dst, src )
	for key, value in pairs( src ) do
		if( dst[key] == nil ) then
			dst[key] = value;
		elseif( type(value) == "table" ) then
			self:UpgradeTable( dst[key], value );
		end
	end
end

function Gnosis:CheckStoredCastbarOptions()
	local iUpgrade150 = 0;
	local iUpgrade195 = 0;
	local strUpgrade150 = "upgrading bars to v1.50 options:\n  ";
	local strUpgrade195 = "upgrading bars to v1.95 options:\n  ";

	for cbname, cbopt in pairs( self.s.cbconf ) do
			-- upgrade castbar if needed (v1.50)
		if( not cbopt.cboptver and cbopt.width and cbopt.height and cbopt.anchor ) then
			iUpgrade150 = iUpgrade150 + 1;
			strUpgrade150 = strUpgrade150 .. cbname .. "  ";

			cbopt.cboptver = 1.50;
			cbopt.width = cbopt.width - cbopt.height;
			if( cbopt.anchor and cbopt.anchor.s ) then
				cbopt.anchor.x = cbopt.anchor.x + cbopt.height / 2 * cbopt.anchor.s;
			end
		end

		-- upgrade to v1.95 (new coordinates)
		if( cbopt.cboptver < 1.95 ) then
			iUpgrade195 = iUpgrade195 + 1;
			strUpgrade195 = strUpgrade195 .. cbname .. "  ";

			cbopt.cboptver = 1.95;
			local xm, ym = GetScreenWidth(), GetScreenHeight();

			if( cbopt.anchor and cbopt.anchor.s ) then
				local x, y, s = cbopt.anchor.x, cbopt.anchor.y, cbopt.anchor.s;
				cbopt.anchor = {
					["px"] = x * cbopt.scale / (s*xm),
					["py"] = y * cbopt.scale / (s*ym),
				};
			else
				cbopt.anchor = nil;
			end
		end

		-- add missing option values
		self:UpgradeTable( cbopt, self.tCastbarDefaults );

		if( cbopt.unit == "gcd_reverse" ) then
			-- remove gcd_reverse unit (-> to gcd with inversed bar direction)
			cbopt.unit = "gcd";
			cbopt.bInvDir = not cbopt.bInvDir;
		end

		-- new unit names for swing timers
		if( cbopt.unit == "mel" ) then
			cbopt.unit = "sm";
		elseif( cbopt.unit == "rng" ) then
			cbopt.unit = "sr";
		elseif( cbopt.unit == "melrng" ) then
			cbopt.unit = "smr";
		end
	end

	if( iUpgrade150 > 0 ) then
		strUpgrade150 = strUpgrade150 .. "\n  ..." .. iUpgrade150 .. " bars upgraded\n  ...please check those bars' options";
		self:Print( strUpgrade150 );
	end

	if( iUpgrade195 > 0 ) then
		strUpgrade195 = strUpgrade195 .. "\n  ..." .. iUpgrade195 .. " bars upgraded\n  ...please check those bars' options";
		self:Print( strUpgrade195 );
	end

	self:CreateCBTables();
end

function Gnosis:CreateCBTables()
	-- table for units to scan (no events)
	self:SetupScanUnits();

	-- create fast lookup table
	self:CreateFastLookupCastbars();
end

function Gnosis:SetupScanUnits()
	self.bScan = false;
	self.scan = {};

	for cbname, cbopt in pairs( self.s.cbconf ) do
		if( cbopt.bEn ) then
			local u = string_match( cbopt.unit, "(.+)target" ) or string_match( cbopt.unit, "(mouseover)" );

			if( u ) then
				self.scan[cbopt.unit] = {};
				self.bScan = true;
			end
		end
	end
end

function Gnosis:ScanUnit( unit, nfo )
	local name, rank, displayName, texture, startTime, endTime, isTradeSkill, id, notInterruptible;

	name, rank, displayName, texture, startTime, endTime, isTradeSkill, id, notInterruptible = UnitCastingInfo( unit );
	if( name ) then
		-- casting
		if( nfo.name and nfo.name == name and nfo.startTime == startTime ) then
			-- not interruptible?
			if( nfo.notInt ~= notInterruptible ) then
				nfo.notInt = notInterruptible;
				if( notInterruptible ) then
					self:UNIT_SPELLCAST_NOT_INTERRUPTIBLE( "UNIT_SPELLCAST_NOT_INTERRUPTIBLE", unit );
				else
					self:UNIT_SPELLCAST_INTERRUPTIBLE( "UNIT_SPELLCAST_INTERRUPTIBLE", unit );
				end
			end
		elseif( nfo.name and nfo.name == name and nfo.startTime < startTime and startTime < nfo.endTime ) then
			nfo.startTime = startTime;
			nfo.endTime = endTime;
			self:UNIT_SPELLCAST_DELAYED( "UNIT_SPELLCAST_DELAYED", unit );
		else
			if( nfo.isChannel ) then
				self:UNIT_SPELLCAST_CHANNEL_STOP( "UNIT_SPELLCAST_CHANNEL_STOP", unit, name );
			else
				self:UNIT_SPELLCAST_STOP( "UNIT_SPELLCAST_STOP", unit, name );
			end

			nfo.name = name;
			nfo.isChannel = false;
			nfo.startTime = startTime;
			nfo.endTime = endTime;
			nfo.notInt = notInterruptible;

			self:UNIT_SPELLCAST_START( "UNIT_SPELLCAST_START", unit, name, rank );
		end

		return;
	end

	name, rank, displayName, texture, startTime, endTime, isTradeSkill, notInterruptible = UnitChannelInfo( unit );
	if( name ) then
		-- channeling
		if( nfo.name and nfo.name == name and nfo.endTime == endTime ) then
			-- not interruptible?
			if( nfo.notInt ~= notInterruptible ) then
				nfo.notInt = notInterruptible;
				if( notInterruptible ) then
					self:UNIT_SPELLCAST_NOT_INTERRUPTIBLE( "UNIT_SPELLCAST_NOT_INTERRUPTIBLE", unit );
				else
					self:UNIT_SPELLCAST_INTERRUPTIBLE( "UNIT_SPELLCAST_INTERRUPTIBLE", unit );
				end
			end
		elseif( nfo.name and nfo.name == name and startTime < nfo.startTime and endTime < nfo.endTime ) then
			nfo.startTime = startTime;
			nfo.endTime = endTime;
			self:UNIT_SPELLCAST_CHANNEL_UPDATE( "UNIT_SPELLCAST_CHANNEL_UPDATE", unit );
		else
			if( nfo.isChannel ) then
				self:UNIT_SPELLCAST_CHANNEL_STOP( "UNIT_SPELLCAST_CHANNEL_STOP", unit, name );
			else
				self:UNIT_SPELLCAST_STOP( "UNIT_SPELLCAST_STOP", unit, name );
			end

			nfo.name = name;
			nfo.isChannel = true;
			nfo.startTime = startTime;
			nfo.endTime = endTime;
			nfo.notInt = notInterruptible;

			self:UNIT_SPELLCAST_CHANNEL_START( "UNIT_SPELLCAST_CHANNEL_START", unit, name );
		end

		return;
	end

	if( nfo.name ) then
		-- stop, no interrupt or failed detection
		if( nfo.isChannel ) then
			self:UNIT_SPELLCAST_CHANNEL_STOP( "UNIT_SPELLCAST_CHANNEL_STOP", unit, name );
		else
			self:UNIT_SPELLCAST_STOP( "UNIT_SPELLCAST_STOP", unit, name );
		end

		nfo.name = nil;
	end
end

function Gnosis:InitialCreateCastbars()
	for key, value in pairs( self.s.cbconf ) do
		self.castbars[key] = self:CreateBarFrame( key, nil, 0, 1.0 );
		self:SetBarParams( key );
	end
	self:CreateCBTables();
end

function Gnosis:CreateFastLookupCastbars()
	self.cb_fl = {};

	for key, value in pairs( self.castbars ) do
		local conf = Gnosis.s.cbconf[key];
		value.conf = conf;

		if( conf.bEn ) then
			if( not self.cb_fl[conf.unit] ) then
				self.cb_fl[conf.unit] = {};
			end

			table_insert( self.cb_fl[conf.unit], value );
		end
	end
end

local iFindCB;
local tFindCB;
function Gnosis:FindCB( unit )
	tFindCB = self.cb_fl[unit];

	iFindCB = 1;
	if( tFindCB ) then
		return tFindCB[iFindCB];
	end

	return nil;
end

function Gnosis:FindCBNext( unit )
	iFindCB = iFindCB + 1;
	return tFindCB[iFindCB];
end

function Gnosis:FindGCDBars( spell, rank, fCurTime )
	local cb = self:FindCB( "gcd" );
	while( cb ) do
		self:SetupGCDbar( cb, spell, rank, fCurTime, false );
		cb = self:FindCBNext( "gcd" );
	end
end

function Gnosis:FindSwingTimers( unit, spell, icon, fCurTime, bType )
	local cb = self:FindCB( unit );
	while( cb ) do
		self:SetupSwingBar( cb, spell, icon, fCurTime, bType );
		cb = self:FindCBNext( unit );
	end
end

function Gnosis:FindSwingTimersParry( unit, fCurTime )
	local cb = self:FindCB( unit );
	while( cb ) do
		self:SetupSwingBarForParry( cb, fCurTime )
		cb = self:FindCBNext( unit );
	end
end

function Gnosis:FontString( bar, height )
	local fs = bar:CreateFontString( nil, "OVERLAY", "GameFontHighlightSmallOutline" );
	fs:SetFont( GameFontNormal:GetFont(), height );
	return fs;
end

function Gnosis:SetBorderColor( name, colBorder, colBarBg )
	local bar = self.castbars[name];

	if( not bar ) then
		return;
	end

	bar.btop:SetTexture( unpack( colBorder ) );
	bar.bbottom:SetTexture( unpack( colBorder ) );
	bar.bleft:SetTexture( unpack( colBorder ) );
	bar.bright:SetTexture( unpack( colBorder ) );
	bar.bbg:SetTexture( unpack( colBarBg ) );
	bar.bicon:SetTexture( unpack( colBorder ) );
end

function Gnosis:SetBarParams( name, cfgtab, bartab )
	local bar = bartab and self[bartab][name] or self.castbars[name];
	local tParams = cfgtab and self.s[cfgtab][name] or self.s.cbconf[name];

	-- setup bar parameters
	bar:SetWidth( tParams.width );
	bar:SetHeight( tParams.height );

	-- status bar
	bar.barwidth = tParams.width;
	bar.barheight = tParams.height;
	bar.bar:SetHeight( tParams.height );
	bar.bar:SetWidth( tParams.width );

	-- bar texture
	bar.bar:SetStatusBarTexture( self.lsm:Fetch( "statusbar", tParams.bartexture ), "BORDER" );
	bar.bar:SetStatusBarColor( unpack( tParams.colBar ) );

	-- statusbar orienation
	if( tParams.orient == 2 ) then
		bar.bar:SetOrientation( "VERTICAL", tParams.bInvDir );
	else
		bar.bar:SetOrientation( "HORIZONTAL", tParams.bInvDir );
	end

	bar.bar:ClearAllPoints();
	bar.bar:SetAllPoints( bar );

	-- bar border
	local bsize, bnsize = tParams.border, -tParams.border;
	local bisize, binsize = tParams.bIconUnlocked and tParams.bordericon or bsize, tParams.bIconUnlocked and -tParams.bordericon or bnsize;
	bar.btop:ClearAllPoints();
	bar.btop:SetPoint( "TOPLEFT", bar, "TOPLEFT", bnsize, bsize );
	bar.btop:SetPoint( "BOTTOMRIGHT", bar, "TOPRIGHT", bsize, 0 );
	bar.bbottom:ClearAllPoints();
	bar.bbottom:SetPoint( "TOPLEFT", bar, "BOTTOMLEFT", bnsize, 0 );
	bar.bbottom:SetPoint( "BOTTOMRIGHT", bar, "BOTTOMRIGHT", bsize, bnsize );
	bar.bleft:ClearAllPoints();
	bar.bleft:SetPoint( "TOPLEFT", bar, "TOPLEFT", bnsize, 0 );
	bar.bleft:SetPoint( "BOTTOMRIGHT", bar, "BOTTOMLEFT", 0, 0 );
	bar.bright:ClearAllPoints();
	bar.bright:SetPoint( "TOPLEFT", bar, "TOPRIGHT", 0, 0 );
	bar.bright:SetPoint( "BOTTOMRIGHT", bar, "BOTTOMRIGHT", bsize, 0 );

	-- bar background
	bar.bbg:ClearAllPoints();
	bar.bbg:SetAllPoints( bar );

	-- icon
	local fRemoveLeftIconBorder, fRemoveRightIconBorder, fScaleIcon, bShowIcon =
		(tParams.iconside == "RIGHT" and not tParams.bIconUnlocked) and 0 or 1,
		(tParams.iconside == "LEFT" and not tParams.bIconUnlocked) and 0 or 1,
		tParams.bIconUnlocked and tParams.scaleicon or 1,
		(tParams.iconside == "LEFT" or tParams.iconside == "RIGHT") and true or false;
	local reverseside, movex, movey =
		bShowIcon and (tParams.iconside == "LEFT" and "RIGHT" or "LEFT") or nil,
		tParams.bIconUnlocked and tParams.coord.casticon.x or 0,
		tParams.bIconUnlocked and tParams.coord.casticon.y or 0;

	if( bShowIcon ) then
		bar.icon:ClearAllPoints();
		bar.bicon:ClearAllPoints();
		bar.icon:SetPoint( reverseside, bar, tParams.iconside, ((tParams.iconside == "LEFT" and binsize or bisize)+movex) * fScaleIcon, movey * fScaleIcon );
		bar.icon:SetWidth( (tParams.bIconUnlocked and min(tParams.height,tParams.width) or tParams.height) * fScaleIcon );
		bar.icon:SetHeight( (tParams.bIconUnlocked and min(tParams.height,tParams.width) or tParams.height) * fScaleIcon );
		bar.bicon:ClearAllPoints();
		bar.bicon:SetPoint( "TOPLEFT", bar.icon, "TOPLEFT", binsize * fScaleIcon * fRemoveLeftIconBorder, bisize * fScaleIcon );
		bar.bicon:SetPoint( "BOTTOMRIGHT", bar.icon, "BOTTOMRIGHT", bisize * fScaleIcon * fRemoveRightIconBorder, binsize * fScaleIcon );
		bar.icon:SetTexture( tParams.bUnlocked and self.toyIcon or nil );

		bar.icon:Show();
		bar.bicon:Show();
	else
		bar.icon:Hide();
		bar.bicon:Hide();
	end

	-- icon rotate
	self:Rotate( bar.ian, tParams.bIconUnlocked and tParams.rotateicon or 0 );
	self:Rotate( bar.bian, tParams.bIconUnlocked and tParams.rotateicon or 0 );

	local curFont = GameFontNormal:GetFont();
	if( tParams.font ) then
		curFont = self.lsm:Fetch( "font", tParams.font );
	end
	local fo = nil;
	if( tParams.fontoutline and self.fontoutlines[tParams.fontoutline] and self.fontoutlines[tParams.fontoutline] ~= "NONE" ) then
		fo = self.fontoutlines[tParams.fontoutline];
	end
	local fssizeparam = tParams.orient == 2 and tParams.width or tParams.height;
	local fs = fssizeparam <= (40/0.65) and fssizeparam or (40/0.65);
	if( tParams.fontsize and tParams.fontsize > 0 ) then
		fs = tParams.fontsize;
		bar.ctext:SetFont( curFont, fs, fo );
	else
		bar.ctext:SetFont( curFont, max(fs*0.65,1), fo );
	end

	bar.defFont = curFont;
	bar.defFS = fs;
	bar.defFO = fo;

	fs = fssizeparam <= (40/0.65) and fssizeparam or (40/0.65);
	-- timer
	if( tParams.fontsize_timer and tParams.fontsize_timer > 0 ) then
		bar.rtext:SetFont( curFont, max(tParams.fontsize_timer*0.65,1), fo );
	else
		bar.rtext:SetFont( curFont, max(fs*0.55,1), fo );
	end
	-- latency
	if( tParams.fontsize_lat and tParams.fontsize_lat > 0 ) then
		bar.brtext:SetFont( curFont, max(tParams.fontsize_lat*0.65,1), fo );
		bar.bltext:SetFont( curFont, max(tParams.fontsize_lat*0.65,1), fo );
	else
		bar.brtext:SetFont( curFont, max(fs/2*0.85,1), fo );
		bar.bltext:SetFont( curFont, max(fs/2*0.85,1), fo );
	end

	for i = 1, 15 do
		if( tParams.orient == 2 ) then
			bar.lb[i]:SetWidth( tParams.width );
		else
			bar.lb[i]:SetHeight( tParams.height );
		end
		bar.lb[i]:SetTexture( unpack( tParams.colLagBar ) );
	end

	-- castbar spark
	if( tParams.orient == 2 ) then
		bar.cbs:SetWidth( tParams.fSparkHeightMulti * tParams.width * 0.3 );	-- 0.3
		bar.cbs:SetHeight( tParams.fSparkWidthMulti * tParams.width *  1.6 );	-- 1.6
		self:Rotate( bar.cbsan, 90 );
	else
		bar.cbs:SetWidth( tParams.fSparkWidthMulti * tParams.height * 0.4 );
		bar.cbs:SetHeight( tParams.fSparkHeightMulti * tParams.height * 1.5 );
		self:Rotate( bar.cbsan, 0 );
	end
	bar.cbs:SetVertexColor( unpack( tParams.colSpark ) );
	bar:SetScale( tParams.scale );

	-- set border color
	self:SetBorderColor( name, tParams.colBorder, tParams.colBarBg );

	-- text colors
	bar.ctext:SetTextColor( unpack( tParams.colText ) );
	bar.rtext:SetTextColor( unpack( tParams.colTextTime and tParams.colTextTime or tParams.colText ) );
	bar.brtext:SetTextColor( unpack( tParams.colTextLag and tParams.colTextLag or tParams.colText ) );
	bar.bltext:SetTextColor( unpack( tParams.colTextLag and tParams.colTextLag or tParams.colText ) );

	-- text shadow
	if( tParams.bEnShadowOffset ) then
		bar.ctext:SetShadowOffset( tParams.coord.shadow.x, tParams.coord.shadow.y );
		bar.rtext:SetShadowOffset( tParams.coord.shadow.x, tParams.coord.shadow.y );
		bar.brtext:SetShadowOffset( tParams.coord.shadow.x, tParams.coord.shadow.y );
		bar.bltext:SetShadowOffset( tParams.coord.shadow.x, tParams.coord.shadow.y );
	end

	if( tParams.bEnShadowCol ) then
		bar.ctext:SetShadowColor( unpack( tParams.colShadow ) );
		bar.rtext:SetShadowColor( unpack( tParams.colShadow ) );
		bar.brtext:SetShadowColor( unpack( tParams.colShadow ) );
		bar.bltext:SetShadowColor( unpack( tParams.colShadow ) );
	end

	-- anchor bar
	self:AnchorBar( name );

	-- bar alpha
	bar:SetAlpha( tParams.alpha );

	-- setup by bar type
	bar.ctext:ClearAllPoints();
	bar.rtext:ClearAllPoints();

	-- alignments
	if( tParams.alignment == "FREE" ) then
		bar.rtext:SetPoint( tParams.aligntime, bar, tParams.coord["casttime"].x, tParams.coord["casttime"].y );
		bar.ctext:SetPoint( tParams.alignname, bar, tParams.coord["castname"].x, tParams.coord["castname"].y );
	elseif( tParams.alignment == "TIMENAME" ) then
		bar.rtext:SetPoint( "LEFT", bar, "LEFT", tParams.coord["casttime"].x, tParams.coord["casttime"].y );
		bar.ctext:SetPoint( "RIGHT", bar, "RIGHT", tParams.coord["castname"].x, tParams.coord["castname"].y );
		if( tParams.alignname == "LEFT" or tParams.alignname == "CENTER" ) then
			bar.ctext:SetPoint( "LEFT", bar.rtext, "RIGHT", 0, 0 );
		else
			bar.rtext:SetPoint( "RIGHT", bar.ctext, "LEFT", 0, 0 );
		end
	else
		bar.rtext:SetPoint( "RIGHT", bar, "RIGHT", tParams.coord["casttime"].x, tParams.coord["casttime"].y );
		bar.ctext:SetPoint( "LEFT", bar, "LEFT", tParams.coord["castname"].x, tParams.coord["castname"].y );
		if( tParams.alignname == "RIGHT" or tParams.alignname == "CENTER" ) then
			bar.ctext:SetPoint( "RIGHT", bar.rtext, "LEFT", 0, 0 );
		else
			bar.rtext:SetPoint( "LEFT", bar.ctext, "RIGHT", 0, 0 );
		end
	end

	bar.ctext:SetJustifyH( tParams.alignname );
	bar.rtext:SetJustifyH( tParams.aligntime );

	-- latency text blocks
	bar.brtext:SetPoint( "BOTTOMRIGHT", bar, -tParams.coord["latency"].x, tParams.coord["latency"].y );
	bar.bltext:SetPoint( "BOTTOMLEFT", bar, tParams.coord["latency"].x, tParams.coord["latency"].y );

	if( tParams.strata ) then
		bar:SetFrameStrata( tParams.strata );
	end

	if( not tParams.bEn ) then
		bar:Hide();		-- bar disabled
	else
		if( tParams.bUnlocked ) then
			self:MakeBarMovable( name, true );
			bar:Show();
		elseif(	tParams.bShowWNC ) then
			self:MakeBarMovable( name, false );
			bar:Show();
		elseif( not bar.bIsActive ) then
			self:MakeBarMovable( name, false );
			bar:Hide();
		end
	end

	-- castbar? if not set bnIsCB to true
	if( tParams.unit == "gcd" or tParams.unit == "sm" or tParams.unit == "sr"
		or tParams.unit == "smr" ) then
		bar.bnIsCB = true;
	else
		bar.bnIsCB = nil;
	end
end

function Gnosis:RAG( tex )
	local ag = tex:CreateAnimationGroup();
	local an = ag:CreateAnimation( "Rotation" );

	-- start animation group
	ag:Play();

	return ag, an;
end

function Gnosis:RotateOnUpdate()
	self:Pause();
end

function Gnosis:Rotate( an, degrees, pt )
	an:Stop();

	if( degrees ~= 0 ) then
		an:SetOrigin( pt and pt or "CENTER", 0, 0 );
		an:SetDegrees( degrees );
		an:SetDuration( 0 );
		an:SetEndDelay( 100 );
		an:SetScript( "OnUpdate", Gnosis.RotateOnUpdate );
		an:Play();
	end
end

function Gnosis:CreateBarFrame( name, iconpath, minval, maxval )
	local f = CreateFrame( "Frame", name, UIParent );
	f:SetFrameStrata( "MEDIUM" );	-- default, correct value will be set when calling Gnosis:SetBarParams()
	f.name = name;

	-- border textures
	f.btop, f.bbottom, f.bleft, f.bright, f.bbg = f:CreateTexture( nil, "BACKGROUND" ), f:CreateTexture( nil, "BACKGROUND" ), f:CreateTexture( nil, "BACKGROUND" ), f:CreateTexture( nil, "BACKGROUND" ), f:CreateTexture( nil, "BACKGROUND" );

	local bar = self:CreateStatusBar( f );
	bar:SetMinMaxValues( minval, maxval );
	bar:SetValue( 0.0 );
	f.bar = bar;

	f:Hide();

	f.bicon = f.bar:CreateTexture( nil, "ARTWORK" );
	local icon = f.bar:CreateTexture( nil, "OVERLAY" );
	icon:SetTexCoord( 0.09, 0.91, 0.09, 0.91 );
	icon:SetTexture( iconpath );
	icon:Show();
	f.icon = icon;

	f.ctext = self:FontString( f.bar, 20*0.65 );
	f.rtext = self:FontString( f.bar, 20*0.55 );
	f.brtext = self:FontString( f.bar, 20/2*0.95 );
	f.bltext = self:FontString( f.bar, 20/2*0.95 );

	f.ctext:SetText( name );

	-- latency bar
	f.lb = {};
	for i = 1, 15 do		-- might be a bit extreme (hellfire ticks 15x)
		f.lb[i] = f.bar:CreateTexture( nil, "ARTWORK" );
		f.lb[i]:SetPoint( "LEFT", (i-1)*0.2, 0 );
		f.lb[i]:Hide();
	end

	local cbs = f.bar:CreateTexture( nil, "OVERLAY" );
	cbs:SetTexture( "Interface\\CastingBar\\UI-CastingBar-Spark" );
	cbs:SetBlendMode( "ADD" );
	cbs:Hide();
	f.cbs = cbs;

	-- animations for rotations
	f.iag, f.ian = self:RAG( f.icon );
	f.ibag, f.bian = self:RAG( f.bicon );
	f.cbsag, f.cbsan = self:RAG( f.cbs );

	return f;
end

function Gnosis:CreateDefaultBarTable( unit )
	local tBar = self:deepcopy( self.tCastbarDefaults );
	tBar["unit"] = unit;

	return tBar;
end

function Gnosis:UIScaleUpdate()
	self:AnchorAllBars();

	-- update coordinates of config options
	if( Gnosis.optCBs:IsShown() ) then
		Gnosis.optCBs:Hide();
		Gnosis.optCBs:Show();
	end
end

function Gnosis:ReAnchorBar( cb )	-- for cursor anchoring
	local cx, cy = GetCursorPosition();
	local uis = UIParent:GetEffectiveScale();
	cb:SetPoint( cb.afrom, UIParent, "BOTTOMLEFT", (cx + cb.ax) / uis, (cy + cb.ay) / uis );
end

function Gnosis:AnchorAllBars()
	for k, v in pairs( self.castbars ) do
		self:AnchorBar( k );
	end
end

function Gnosis:AnchorBar( name )
	local cb, cfg = self.castbars[name], self.s.cbconf[name];
	cb.afrom, cb.ato = self.tPoints[cfg.anchorfrom], self.tPoints[cfg.anchorto];
	cb.ax, cb.ay = cfg.anchor_x, cfg.anchor_y;

	local uis = UIParent:GetEffectiveScale();
	local cbs = cb:GetEffectiveScale();
	local mult = uis / cbs;
	local xm, ym = GetScreenWidth(), GetScreenHeight();

	if( not cfg.anchor or not( tonumber(cfg.anchor.px) and tonumber(cfg.anchor.py) ) ) then
		cfg.anchor = {
			["px"] = 0.5,		-- CENTER
			["py"] = 0.5,		-- CENTER
		};
	end

	local px, py = cfg.anchor.px, cfg.anchor.py;
	cb.reanchor = false;
	cb:ClearAllPoints();
	if( cfg.anchortype == 3 ) then		-- anchor to cursor
		cb.reanchor = true;
		self:ReAnchorBar( cb );
	elseif( cfg.anchortype == 2 and _G[cfg.anchorframe] ) then	-- anchor to frame
		cb:SetPoint( cb.afrom, _G[cfg.anchorframe], cb.ato, cb.ax / uis, cb.ay / uis );
	else	-- no anchor
		cb:SetPoint( "CENTER", UIParent, "BOTTOMLEFT", px * xm * mult, py * ym * mult );
	end
end

function Gnosis:CenterCastbar( key )
	self.s.cbconf[key].anchor = nil;
	self:AnchorBar( key );
end

function Gnosis:RemoveCastbar( key )
	if( self.activebars[key] ) then
		self.activebars[key] = nil;	-- remove bar so it won't be updated
	end
	self.castbars[key]:Hide();
	self.castbars[key] = nil;
	self.s.cbconf[key] = nil;

	-- update config options
	self:CreateCastbarsOpt();

	-- new castbar lookup tables
	self:CreateCBTables();
end

function Gnosis:OnDragStart()
	self:StartMoving();
end

function Gnosis:OnDragStop()
	self:StopMovingOrSizing();
	local uis = UIParent:GetEffectiveScale();
	local cbs = self:GetEffectiveScale();
	local xm, ym = GetScreenWidth(), GetScreenHeight();
	local px, py = self:GetCenter();
	px, py = px * cbs / uis / xm, py * cbs / uis / ym;

	Gnosis.s.cbconf[self.name].anchor = {
		["px"] = px,
		["py"] = py,
	};

	-- update coordinates of config options
	if( Gnosis.optCBs:IsShown() ) then
		Gnosis.optCBs:Hide();
		Gnosis.optCBs:Show();
	end
end

function Gnosis:OnMouseUp( button )
	if( button == "RightButton" ) then
		InterfaceOptionsFrame_OpenToCategory( Gnosis.optCBs );
	end
end

function Gnosis:MakeBarMovable( name, status )
	if( status ) then
		self.castbars[name]:EnableMouse( true );
		self.castbars[name]:SetMovable( true );
		self.castbars[name]:RegisterForDrag( "LeftButton" );
		self.castbars[name]:SetScript( "OnDragStart", Gnosis.OnDragStart );
		self.castbars[name]:SetScript( "OnDragStop", Gnosis.OnDragStop );
		self.castbars[name]:SetScript( "OnMouseUp", Gnosis.OnMouseUp );
	else
		self.castbars[name]:EnableMouse( false );
	end
end

function Gnosis:CheckGroupLayout( cfg )
	if( cfg.ingroupsel == 1 ) then
		return true;
	elseif( cfg.ingroupsel == 2 ) then
		return( GetNumPartyMembers() == 0 and GetNumRaidMembers() == 0 );
	elseif( cfg.ingroupsel == 3 ) then
		return( GetNumPartyMembers() > 0 and GetNumRaidMembers() == 0 );
	elseif( cfg.ingroupsel == 4 ) then
		return( GetNumRaidMembers() > 0 );
	end

	return true;
end

function Gnosis:SetupSwingBarForParry( cb, fCurTime )
	local barname, cfg = cb.name, cb.conf;

	if( not self.activebars[barname] or cb.hasted ) then
		return;
	end

	local timeremain = cb.endTime - fCurTime;
	local timeremainperc = timeremain / cb.duration;

	-- information taken from http://elitistjerks.com/f31/t15257-melee_combat_riddle_me_parry_mechanics/
		-- accuracy of the given formulae and latency problems can screw up parry haste estimations quite badly
	if( timeremainperc >= 0.6 ) then
		cb.endTime = cb.endTime - cb.duration * 0.4;
		cb.duration = (cb.endTime - fCurTime) / timeremainperc;
	elseif( timeremainperc >= 0.2 ) then
		cb.endTime = cb.endTime - cb.duration / (1 + 2.5*timeremainperc*timeremainperc - 0.36*timeremainperc - 0.005);
		cb.duration = (cb.endTime - fCurTime) / timeremainperc;
	end
end

function Gnosis:SetupSwingBar( cb, spell, icon, fCurTime, bMeleeSwing )
	local barname, cfg = cb.name, cb.conf;

		-- valid group layout?
	if( not self:CheckGroupLayout( cfg ) ) then
		return;
	end

	if( self.activebars[barname] or self.fadeoutbars[barname] ) then
		self:CleanupCastbar( cb );
	end

	cb.channel = false;
	cb.duration = (bMeleeSwing and UnitAttackSpeed( "player" ) or UnitRangedDamage( "player" )) * 1000;
	cb.endTime = fCurTime + cb.duration;
	cb.icon:SetTexture( icon );
	cb.id = nil;
	cb.hasted = false;	-- do not allow more than one parry haste per swing (even though that might be wrong)

	cb.ctext:SetText( self:CreateCastname( cb, cfg, spell, "" ) );
	cb.castname = "";

	local val = (cb.endTime - fCurTime) / (cb.duration);
	cb.bar:SetValue( (cb.channel and (1-val) or val) );
	cb:SetAlpha( cfg.alpha );
	cb:Show();

	-- setup strings, including automatic resize of long strings
	self:SetupBarString( cb, cfg, fCurTime, true );

	-- castbar spark
	if( cfg.bShowCBS ) then
		cb.cbs:SetPoint( "CENTER", cb.bar, "LEFT", (cb.channel and (1-val) or val) * cb.barwidth, 0 );
		cb.cbs:Show();
	end

	-- pushback (also vital for clipping test)
	cb.pushback = 0;

	-- set bar active
	cb.bActive = true;
	self.activebars[barname] = cb;
end

function Gnosis:SetupGCDbar( cb, spell, rank, fCurTime, right2left )
	local barname, cfg = cb.name, cb.conf;

	local start, cd = GetSpellCooldown( spell );
	if( not start or not( cd > 0 and cd <= 1.5) ) then
		return;
	end

		-- valid group layout?
	if( not self:CheckGroupLayout( cfg ) ) then
		return;
	end

	-- blacklisted?
	if( cfg.bnwtypesel == 2 and cfg.bnwlist ) then
		for key, value in pairs(cfg.bnwlist) do
			if( value == spell ) then
				return;
			end
		end
	-- not whitelisted?
	elseif( cfg.bnwtypesel == 3 and cfg.bnwlist ) then
		local bReturn = true;
		for key, value in pairs(cfg.bnwlist) do
			if( value == spell ) then
				bReturn = false;
				break;
			end
		end

		if( bReturn ) then
			return;
		end
	end

	if( self.activebars[barname] ~= nil or self.fadeoutbars[barname] ~= nil ) then
		self:CleanupCastbar( cb );
	end

	local _, _, icon = GetSpellInfo( spell );
	cb.channel = right2left;
	cb.duration = cd * 1000;
	cb.endTime = start * 1000 + cd * 1000;
	cb.icon:SetTexture( icon );
	cb.id = nil;

	cb.ctext:SetText( self:CreateCastname( cb, cfg, spell, rank ) );
	cb.castname = "";

	local val = (cb.endTime - fCurTime) / (cb.duration);
	cb.bar:SetValue( (cb.channel and (1-val) or val) );
	cb:SetAlpha( cfg.alpha );
	cb:Show();

	-- setup strings, including automatic resize of long strings
	self:SetupBarString( cb, cfg, fCurTime, true );

	-- castbar spark
	if( cfg.bShowCBS ) then
		cb.cbs:SetPoint( "CENTER", cb.bar, "LEFT", (cb.channel and (1-val) or val) * cb.barwidth, 0 );
		cb.cbs:Show();
	end

	-- pushback (also vital for clipping test)
	cb.pushback = 0;

	-- set bar active
	cb.bActive = true;
	self.activebars[barname] = cb;
end

function Gnosis:SetupMirrorbar( cb, label, channel, curval, maxval, fCurTime, timer )
	local barname, cfg = cb.name, cb.conf;

	-- valid group layout?
	if( not self:CheckGroupLayout( cfg ) ) then
		return;
	end

	-- blacklisted?
	if( cfg.bnwtypesel == 2 and cfg.bnwlist ) then
		for key, value in pairs(cfg.bnwlist) do
			if( value == label ) then
				return false;
			end
		end
	-- not whitelisted?
	elseif( cfg.bnwtypesel == 3 and cfg.bnwlist ) then
		local bReturn = true;
		for key, value in pairs(cfg.bnwlist) do
			if( value == label ) then
				bReturn = false;
				break;
			end
		end

		if( bReturn ) then
			return false;
		end
	end

	if( self.activebars[barname] ~= nil or self.fadeoutbars[barname] ~= nil ) then
		self:CleanupCastbar( cb );
	end

	cb.channel = channel;
	cb.duration = maxval;
	cb.endTime = channel and fCurTime + curval or fCurTime + maxval - curval;
	cb.icon:SetTexture( self.tMirrorIcons[timer] );
	cb.id = nil;

	cb.ctext:SetText( self:CreateCastname( cb, cfg, label, "" ) );
	cb.castname = timer;

	cb.bar:SetValue( curval );
	cb:SetAlpha( cfg.alpha );
	cb:Show();

	-- setup strings, including automatic resize of long strings
	self:SetupBarString( cb, cfg, fCurTime, true );

	-- castbar spark
	if( cfg.bShowCBS ) then
		cb.cbs:SetPoint( "CENTER", cb.bar, "LEFT", (channel and curval or maxval - curval) * cb.barwidth, 0 );
		cb.cbs:Show();
	end

	-- pushback (also vital for clipping test)
	cb.pushback = 0;

	-- set bar active
	cb.bActive = true;
	self.activebars[barname] = cb;

	return true;
end

function Gnosis:CreateCastname( cb, cfg, name, rank )
	local castname = "";
	if( cfg.bUseNameFormat ) then
		if( cfg.strNameFormat ~= "" ) then
			castname = self:CreateCastnameFromString( name, rank, cb, cfg );
		end
	elseif( cfg.bShowCastname and cfg.bShowCastrank ) then
		castname = name .. ((rank ~= "") and (" (" .. rank .. ")") or "");
	elseif( cfg.bShowCastname ) then
		castname = name;
	elseif( cfg.bShowCastrank ) then
		castname = rank;
	end

	return castname;
end

function Gnosis:CreateCastnameFromString( name, rank, cb, cfg )
	-- substitute strings
	local bRank = false;
	local bOther = false;
	local iRank, strRankArabic;
	local strRankRoman = "";
	local lenname;
	local strFormat, unit = cfg.strNameFormat, cfg.unit;
	local uname = UnitName( unit );
	local str = strFormat;

	-- target information (player castbar only)
	local strTarget = nil;
	if( unit == "player" and self.strLastTarget ) then
		strTarget = self.strLastTarget;
	end

	-- spell school colors, format is col<r,g,b[,a]> or col<spellschool>
		-- e.g. col<1.0,0.5,0.5,1.0>; col<shadow>
		-- col<pre> will be replace with "|r"
	str = self:ReplaceColorStrings( str, strTarget and self.strLastTargetClass or nil );

	strRankArabic = string_match( rank, "(%d+)" );
	if( strRankArabic ) then
		local i;
		iRank = tonumber( strRankArabic );

		-- create roman numeral 1..38, since 50 'L' is not supported (yet)
		while( true ) do
			if( iRank > 38 ) then
				-- probably no rank! abort
				break;
			elseif( iRank >= 9 ) then
				strRankRoman = strRankRoman .. "X";
				iRank = iRank - 10;
			elseif( iRank >= 4 ) then
				strRankRoman = strRankRoman .. "V";
				iRank = iRank - 5;
			elseif( iRank >= 1 ) then
				strRankRoman = strRankRoman .. "I";
				iRank = iRank - 1;
			elseif( iRank == 0 ) then
				-- done
				bRank = true;
				break;
			elseif( iRank >= -4 ) then
				strRankRoman = "I" .. strRankRoman;
				iRank = iRank + 1;
			elseif( iRank >= -9 ) then
				strRankRoman = "V" .. strRankRoman;
				iRank = iRank + 5;
			end
		end
	end

	if( not bRank and rank and rank ~= "" ) then
		bOther = true;
	end

	lenname = string_match( str, "abbr<(%d*)>" );
	str = string_gsub( str, "abbr<([^>]*)>", "abbr" );

	lenname = tonumber( lenname ) and tonumber( lenname ) or nil;
	if( lenname ) then
		local bMulti = false;

		if( string_len( name ) > lenname ) then
			-- string too long, abbreviate
			local abbrstr = "";
			local ncpy = name;
			local i = 0;

			while i < 10 do
				i = i + 1;	-- maximize word count to 10, also make sure while loop won't lock client up (especially with specific locales)

				local ltmp = string_match( ncpy, "([^%s%-%/%_%:%.]+)" );

				if( not ltmp ) then
					break;
				end

				ncpy = string_gsub( ncpy, ltmp, "" );

				if( not bMulti and string_match( ncpy, "([^%s]+)" ) ) then
					bMulti = true;
				end

				if( bMulti ) then
					abbrstr = abbrstr .. (string_match( ltmp, "(%u+)" ) and string_match( ltmp, "(%u+)" ) or string_match( ltmp, "(%l?)" ));
				else
					abbrstr = string_sub( ltmp, 1, lenname );
				end
			end

			str = string_gsub( str, "abbr", abbrstr );
		else
			str = string_gsub( str, "abbr", name );
		end
	end

	str = string_gsub( str, "name", name );
	if( bRank ) then
		str = string_gsub( str, "arabic", strRankArabic );
		str = string_gsub( str, "roman", strRankRoman );
		str = string_gsub( str, "rank<([^>]+)>", "%1" );
		str = string_gsub( str, "txr<([^>]+)>", "%1" );
	else
		str = string_gsub( str, "arabic", "" );
		str = string_gsub( str, "roman", "" );
		str = string_gsub( str, "rank<([^>]+)>", "" );
		str = string_gsub( str, "txr<([^>]+)>", "" );
	end

	if( bOther ) then
		str = string_gsub( str, "misc", rank );
		str = string_gsub( str, "txm<([^>]+)>", "%1" );
	else
		str = string_gsub( str, "misc", "" );
		str = string_gsub( str, "txm<([^>]+)>", "" );
	end

	if( cb.bIsTrade ) then
		str = string_gsub( str, "tscur", string_format( "%.0f", cb.tscnt ) );
		str = string_gsub( str, "tstot", string_format( "%.0f", cb.tstot ) );
		str = string_gsub( str, "txts<([^>]+)>", "%1" );
	else
		str = string_gsub( str, "tscur", "" );
		str = string_gsub( str, "tstot", "" );
		str = string_gsub( str, "txts<([^>]+)>", "" );
	end

	if( strTarget ) then
		str = string_gsub( str, "tar<([^>]+)>", "%1" );
		str = string_gsub( str, "tar%[([^%]]+)%]", "%1" );	-- added to allow ->
		str = string_gsub( str, "target", strTarget );
	else
		str = string_gsub( str, "tar<([^>]+)>", "" );
		str = string_gsub( str, "tar%[([^%]]+)%]", "" );
		str = string_gsub( str, "target", "" );
	end

	-- unit name
	if( uname ) then
		str = string_gsub( str, "who", uname );
	else
		str = string_gsub( str, "who", "" );
	end

	-- new line
	str = string_gsub( str, "\\n", "\n" );

	return str;
end

function Gnosis:ReplaceStringWithTime( strSrc, strCmd, fTime, bRemove )
	local rep, sub, n, repstr, bMin, bSign, subs;
	rep = string_match( strSrc, strCmd .. "<(.-)>" );

	if( rep ) then
		sub, subs = string_gsub( rep, "m", "" );
		bMin = subs > 0;
		sub, subs = string_gsub( sub, "s", "" );
		bSign = subs > 0;

		if( bRemove ) then
			strSrc = string_gsub( strSrc, strCmd .. "<" .. rep .. ">", "" );
		else
			n = tonumber( sub );
			if( n ) then
				strf = "%" .. (bSign and "+" or "") .. "." .. n .. "f";

				if( bMin and fTime > 60.0 ) then
					repstr = string_format( "%.0f:%02.0f", floor(fTime / 60.0), floor(fTime) % 60.0 )
				else
					repstr = string_format( strf, fTime );
				end
				strSrc = string_gsub( strSrc, strCmd .. "<" .. rep .. ">", repstr );
			else
				strSrc = string_gsub( strSrc, strCmd .. "<" .. rep .. ">", "" );
			end
		end
	end

	return strSrc;
end

function Gnosis:GenerateTimerString( cb, conf, fCurTime )
	local val = (cb.endTime - fCurTime) / (cb.duration);
	local valleft, valtot = (cb.endTime - fCurTime) / 1000, cb.duration / 1000;

	local tx;
	-- add timer
	if( conf.bUseTimeFormat ) then
		local str, sub, n;
		-- add times
		tx = self:ReplaceColorStrings( conf.strTimeFormat );
		tx = self:ReplaceStringWithTime( tx, "r", valleft );
		tx = self:ReplaceStringWithTime( tx, "t", valtot );
		tx = self:ReplaceStringWithTime( tx, "c", valtot - valleft );

		-- new line
		tx = string_gsub( tx, "\\n", "\n" );

		if( cb.pushback and cb.pushback ~= 0.0 ) then
			tx = self:ReplaceStringWithTime( tx, "p", cb.pushback / 1000 );
		else
			tx = self:ReplaceStringWithTime( tx, "p", nil, true );
		end
	else
		if( conf.bHideCasttime and conf.bHideCasttimeTotal ) then
			tx = "";
		elseif( conf.bHideCasttimeTotal ) then
			if( conf.bShowAsMinutes and valleft >= 60.0 ) then
				tx = string_format( "%.0f:%02.0f", floor(valleft / 60.0), floor(valleft) % 60.0 );
			else
				tx = string_format( "%.1f", valleft );
			end
		elseif( conf.bHideCasttime ) then
			if( conf.bShowAsMinutes and valtot >= 60.0 ) then
				tx = string_format( "%.0f:%02.0f", floor(valtot / 60.0), floor(valtot) % 60.0 );
			else
				tx = string_format( "%.2f", valtot );
			end
		else
			if( conf.bShowAsMinutes ) then
				if( valleft >= 60.0 ) then
					tx = string_format( "%.0f:%02.0f / ", floor(valleft / 60.0), floor(valleft) % 60.0 );
				else
					tx = string_format( "%.1f / ", valleft );
				end

				if( valtot >= 60.0 ) then
					tx = tx .. string_format( "%.0f:%02.0f", floor(valtot / 60.0), floor(valtot) % 60.0 );
				else
					tx = tx .. string_format( "%.2f", valtot );
				end
			else
				tx = string_format( "%.1f / %.2f", valleft, valtot );
			end
		end

		-- add pushback text
		if( cb.pushback and cb.pushback ~= 0.0 and (not conf.bHidePushbackTime) ) then
			local r, g, b = unpack( conf.colTextPB );
			r, g, b = r*255, g*255, b*255;
			tx = string_format( "|cff%02x%02x%02x%+.2f|r ", r, g, b, cb.pushback / 1000 ) .. tx;
		end
	end

	return tx;
end

function Gnosis:SetupCastbar( cb, bIsChannel, fCurTime )
	local barname, cfg = cb.name, cb.conf;

	local name, rank, displayName, texture, startTime, endTime, isTradeSkill, notInterruptible, id;
	if( bIsChannel ) then
		name, rank, displayName, texture, startTime, endTime, isTradeSkill, notInterruptible = UnitChannelInfo( cfg.unit );
	else
		name, rank, displayName, texture, startTime, endTime, isTradeSkill, id, notInterruptible = UnitCastingInfo( cfg.unit );
	end

	if( not name ) then
		return;
	end

	-- unit relation
	local bEnemy = UnitIsEnemy( "player", cfg.unit );
	if( (bEnemy and cfg.relationsel == 3) or (not bEnemy and cfg.relationsel == 2) ) then
		return;
	end

		-- valid group layout?
	if( not self:CheckGroupLayout( cfg ) ) then
		return;
	end

	-- blacklisted?
	if( cfg.bnwtypesel == 2 and cfg.bnwlist ) then
		for key, value in pairs(cfg.bnwlist) do
			if( value == name ) then
				return;
			end
		end
	-- not whitelisted?
	elseif( cfg.bnwtypesel == 3 and cfg.bnwlist ) then
		local bReturn = true;
		for key, value in pairs(cfg.bnwlist) do
			if( value == name ) then
				bReturn = false;
				break;
			end
		end

		if( bReturn ) then
			return;
		end
	end

	-- tradeskill stuff
	local bDoResize = true;
	local bnTS = true;
	if( cfg.unit == "player" and cfg.bMergeTrade ) then
		if( isTradeSkill ) then
			bnTS = false;
			cb.tscnt = self.iLastTradeSkillCnt and self.iLastTradeSkillCnt or 1;
			if( cb.bIsTrade and (not self.bNewTradeSkill) ) then
				-- tradeskill in progress
				cb.duration = (fCurTime - cb.starttime) * cb.tstot / (cb.tstot - cb.tscnt);
				cb.endTime = cb.duration + cb.starttime;

				bDoResize = false;
			elseif( cb.tscnt > 1 and self.bNewTradeSkill ) then
				-- new tradeskill merge, don't start if repeat count less than 2
				if( (self.activebars[barname] or self.fadeoutbars[barname]) ) then
					self:CleanupCastbar( cb );
				end

				cb.bIsTrade = true;
				cb.starttime = fCurTime;
				cb.tstot = cb.tscnt;
				cb.duration = (endTime - startTime) * cb.tscnt + self.lag * (cb.tscnt - 1);
				cb.endTime = cb.duration + cb.starttime;
			else
				cb.bIsTrade = nil;

				if( (self.activebars[barname] or self.fadeoutbars[barname]) ) then
					self:CleanupCastbar( cb );
				end

				cb.duration = endTime - startTime;
				cb.endTime = endTime;
			end
		else
			self.bNewTradeSkill = nil;
			self.iLastTradeSkillCnt = nil;
		end
	end

	if( bnTS ) then
		cb.bIsTrade = nil;

		if( (self.activebars[barname] or self.fadeoutbars[barname]) ) then
			self:CleanupCastbar( cb );
		end

		cb.duration = endTime - startTime;
		cb.endTime = endTime;
	end

	-- castbar values
	cb.channel = bIsChannel;
	cb.icon:SetTexture( texture );
	cb.id = id;

	-- show castbar text
	cb.ctext:SetText( self:CreateCastname( cb, cfg, name, rank ) );
	cb.castname = name;

	-- set statusbar value
	local val = (cb.endTime - fCurTime) / (cb.duration);
	val = cb.channel and val or (1 - val);
	cb.bar:SetValue( val );
	cb:SetAlpha( cfg.alpha );
	cb:Show();

	-- setup strings, including automatic resize of long strings
	self:SetupBarString( cb, cfg, fCurTime, bDoResize );

	-- border color
	if( notInterruptible ) then
		self:SetBorderColor( barname, cfg.colBorderNI, cfg.colBarBg );
	else
		self:SetBorderColor( barname, cfg.colBorder, cfg.colBarBg );
	end

	-- castbar spark
	if( cfg.bShowCBS ) then
		cb.cbs:SetPoint( "CENTER", cb.bar, "LEFT", val * cb.barwidth, 0 );
		cb.cbs:Show();
	end

	-- latency & ticks
	local cs = self.s.channeledspells[cb.castname];
	if( cs and cs.ben and not(cs.bhidenonplayer and cfg.unit ~= "player") ) then
		local totalticks = cs.ticks;
		local noninitticks = cs.binit and (totalticks-1) or totalticks;
		local shownticks = cs.bars;

		if( cfg.unit == "player" and cs.baddticks and cs.hastespell and cs.basecasttime ) then
			-- estimate amount of ticks due to haste
			local _, _, _, _, _, _, castTime = GetSpellInfo( cs.hastespell );
			if ( castTime ) then
				local haste = cs.basecasttime / castTime;
				noninitticks = floor( noninitticks * haste + 0.5 );
				totalticks = cs.binit and (noninitticks+1) or noninitticks;
				shownticks = shownticks + (totalticks - cs.ticks);
			end
		end

		local ftbt = 1 / noninitticks;
		local valLBperc = ((cfg.unit == "player") and max( min( self.lag / cb.duration, cfg.latbarsize ), cfg.latbarfixed ) or cfg.latbarfixed);
		-- never create latency box larger than half of distance between ticks
		-- vital for long channels with high number of ticks (e.g. hellfire with 15 ticks)
		valLBperc = min( valLBperc, ftbt * 0.5 );
		local valLB = (cfg.orient == 2 and cb.barheight or cb.barwidth) * valLBperc;

		if( cfg.bShowTicks ) then
			for i = 1, totalticks do
				if( i <= shownticks ) then
					cb.lb[i]:ClearAllPoints();
					if( cfg.orient == 2 ) then
						if( cfg.bInvDir ) then
							cb.lb[i]:SetPoint( "TOP", 0, -cb.barheight * (i-1) * ftbt );
						else
							cb.lb[i]:SetPoint( "BOTTOM", 0, cb.barheight * (i-1) * ftbt );
						end
						cb.lb[i]:SetHeight( valLB );
					else
						if( cfg.bInvDir ) then
							cb.lb[i]:SetPoint( "RIGHT", -cb.barwidth * (i-1) * ftbt, 0 );
						else
							cb.lb[i]:SetPoint( "LEFT", cb.barwidth * (i-1) * ftbt, 0 );
						end
						cb.lb[i]:SetWidth( valLB );
					end
					cb.lb[i]:Show();
				end
			end
		end

		-- spell is channeled, store tick information for possible clip test
		cb.ticks = {};
		for i = 1, totalticks do
			cb.ticks[i] = cb.endTime - (i-1) * ftbt * cb.duration;
		end

		cb.totalticks = cfg.bShowTicks and totalticks or 0;
		cb.shownticks = cfg.bShowTicks and shownticks or 0;
	else
		cb.totalticks = 0;
		cb.shownticks = 0;
	end

	if( cfg.unit == "player" and self.lag < 10000 ) then
		-- < 10000ms should filter latency for most summoning stone timers, also hide latency when just too extreme

		-- latency box, played only
		if( not(cs and cs.ben and cfg.bShowTicks) and cfg.bShowLat ) then
			cb.lb[1]:ClearAllPoints();
			if( cfg.orient == 2 ) then
				cb.lb[1]:SetHeight( cb.barheight * min( self.lag / cb.duration, cfg.latbarsize ) );
				if( cfg.bInvDir ) then
					cb.lb[1]:SetPoint( cb.channel and "TOP" or "BOTTOM" );
				else
					cb.lb[1]:SetPoint( cb.channel and "BOTTOM" or "TOP" );
				end
			else
				cb.lb[1]:SetWidth( cb.barwidth * min( self.lag / cb.duration, cfg.latbarsize ) );
				if( cfg.bInvDir ) then
					cb.lb[1]:SetPoint( cb.channel and "RIGHT" or "LEFT" );
				else
					cb.lb[1]:SetPoint( cb.channel and "LEFT" or "RIGHT" );
				end
			end
			cb.lb[1]:Show();
		end

		-- latency text
		if( cfg.bShowPlayerLatency ) then
			-- < 10000ms should filter latency for most summoning stone timers, also hide latency when just too extreme
			if( cfg.alignlat == "LEFT" ) then
				cb.bltext:SetText( string_format( "%d", self.lag ) );
			elseif( cfg.alignlat == "RIGHT" ) then
				cb.brtext:SetText( string_format( "%d", self.lag ) );
			else
				if( cb.channel ) then
					cb.bltext:SetText( string_format( "%d", self.lag ) );
				else
					cb.brtext:SetText( string_format( "%d", self.lag ) );
				end
			end
		end
	end

	-- pushback (also vital for clipping test)
	cb.pushback = 0;

	-- set bar active
	cb.bActive = true;
	self.activebars[barname] = cb;
end

function Gnosis:CloseAllTradeskillBars()
	local fCurTime = GetTime() * 1000.0;

	local cb = self:FindCB( "player" );
	if( cb ) then
		repeat
			if( cb.bIsTrade ) then
				local conf = cb.conf;
				if( conf.bUnlocked or conf.bShowWNC ) then
					self:CleanupCastbar( cb, fCurTime );
				else
					self:PrepareCastbarForFadeout( cb, fCurTime );
				end
			end
			cb = self:FindCBNext( "player" );
		until cb == nil;
	end
end

function Gnosis:UpdateCastbar( cb, startTime, endTime, spell )
	local barname, cfg = cb.name, cb.conf;

	local fSPB = endTime - cb.endTime;		-- added spell pushback
	if( fSPB ~= 0 ) then
		cb.pushback = cb.pushback + fSPB;
		cb.endTime = endTime;

		if( cb.shownticks and cb.shownticks > 0 ) then
			for i = 1, cb.totalticks do
				if( i <= cb.shownticks ) then
					if( cb.ticks[i] > cb.endTime ) then
						cb.lb[i]:Hide();
					else
						cb.lb[i]:ClearAllPoints();

						if( cfg.orient == 2 ) then
							if( cfg.bInvDir ) then
								cb.lb[i]:SetPoint( "TOP", 0, -cb.barheight * (cb.endTime-cb.ticks[i])/cb.duration );
							else
								cb.lb[i]:SetPoint( "BOTTOM", 0, cb.barheight * (cb.endTime-cb.ticks[i])/cb.duration );
							end
						else
							if( cfg.bInvDir ) then
								cb.lb[i]:SetPoint( "RIGHT", -cb.barwidth * (cb.endTime-cb.ticks[i])/cb.duration, 0 );
							else
								cb.lb[i]:SetPoint( "LEFT", cb.barwidth * (cb.endTime-cb.ticks[i])/cb.duration, 0 );
							end
						end
					end
				end
			end
		end
	end
end

function Gnosis:PrepareCastbarForFadeout( cb, fCurTime )
	local barname, cfg = cb.name, cb.conf;

	if( self.activebars[barname] ) then
		cb.bActive = false;
		self.activebars[barname] = nil;
		self.fadeoutbars[barname] = cb;
		cb.endTime = fCurTime + cfg.fadeout * 1000;
		cb.duration = cfg.fadeout * 1000;
		cb.alpha = cfg.alpha;

		if( cfg.bFillup ) then
			cb.bar:SetValue( cb.channel and 0 or 1.0 );
			cb.cbs:Hide();
		end

		cb.bIsTrade = nil;
	end
end

function Gnosis:CleanupCastbar( cb )
	local barname, cfg = cb.name, cb.conf;

	if( not cb ) then
		return;
	end

	cb.bActive = false;
	if( self.activebars[barname] ) then
		self.activebars[barname] = nil;
	end
	if( self.fadeoutbars[barname] ) then
		self.fadeoutbars[barname] = nil;
	end

	for i = 1, 15 do cb.lb[i]:Hide(); end
	cb.shownticks = 0;
	cb.cbs:Hide();

	cb.ctext:SetText( cb.name );
	cb.rtext:SetText( "" );
	cb.bltext:SetText( "" );
	cb.brtext:SetText( "" );
	cb.icon:SetTexture( cfg.bUnlocked and self.toyIcon or nil );
	cb.bar:SetValue( 0 );

	cb.bar:SetStatusBarColor( unpack( cfg.colBar ) );
	self:SetBorderColor( barname, cfg.colBorder, cfg.colBarBg );

	cb.bIsTrade = nil;
	cb.tscnt = 0;
	cb.tstot = 0;
end

function Gnosis:SetupBarString( cb, cfg, fCurTime, bDoResize )
	-- adjust text size (long strings), only if "free alignment" is not selected
	if( cfg.alignment ~= "FREE" and cfg.bResizeLongName and bDoResize ) then
		-- set default font size
		cb.ctext:SetFont( cb.defFont, cb.defFS * 0.65, cb.defFO );

		-- calculate space available for name string
		local bRenewTimeString = false;
		if( cb.duration <= 10000 ) then
			-- make sure name string font size is not dependant on fCurTime
			bRenewTimeString = true;
			cb.rtext:SetText( self:GenerateTimerString( cb, cfg, cb.endTime - cb.duration ) );
		else
			cb.rtext:SetText( self:GenerateTimerString( cb, cfg, fCurTime ) );
		end

		local ctextmax = cb.barwidth - (cb.rtext:GetStringWidth() + cfg.strGap);
		local factor = 1.0;

		while ctextmax < cb.ctext:GetStringWidth() do
			local refactor = ctextmax / cb.ctext:GetStringWidth();
			if( refactor > 0.9 ) then
				refactor = 0.9;	-- reduce required iterations
			end
			factor = factor * refactor;
			local newFS = cb.defFS * 0.65 * factor;
			if( newFS < 0.1 ) then
				break;
			end
			cb.ctext:SetFont( cb.defFont, newFS, cb.defFO );
		end

		if( bRenewTimeString ) then
			cb.rtext:SetText( self:GenerateTimerString( cb, cfg, fCurTime ) );
		end
	else
		cb.rtext:SetText( self:GenerateTimerString( cb, cfg, fCurTime ) );
	end
end
