//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include <assert.h>
#include <math.h>
#include <stdio.h>

#include <vgui_controls/CircularProgressBar.h>
#include <vgui_controls/Controls.h>

#include <vgui/ILocalize.h>
#include <vgui/IScheme.h>
#include <vgui/ISurface.h>
#include <KeyValues.h>

#include "mathlib/mathlib.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

using namespace vgui;

DECLARE_BUILD_FACTORY( CircularProgressBar );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CircularProgressBar::CircularProgressBar(Panel *parent, const char *panelName) 
	: ProgressBar(parent, panelName)
	, m_bReverseProgress( false )
{
	m_iProgressDirection = CircularProgressBar::PROGRESS_CW;

	for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ )
	{
		m_nTextureId[i] = -1;
		m_pszImageName[i] = NULL;
		m_lenImageName[i] = 0;
	}

	m_iStartSegment = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CircularProgressBar::~CircularProgressBar()
{
	for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ )
	{
		if ( vgui::surface() && m_nTextureId[i] )
		{
			vgui::surface()->DestroyTextureID( m_nTextureId[i] );
			m_nTextureId[i] = -1;
		}

		delete [] m_pszImageName[i];
		m_lenImageName[i] = 0;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CircularProgressBar::ApplySettings(KeyValues *inResourceData)
{
	for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ )
	{
		delete [] m_pszImageName[i];
		m_pszImageName[i] = NULL;
		m_lenImageName[i] = 0;
	}

	const char *imageName = inResourceData->GetString("fg_image", "");
	if (*imageName)
	{
		SetFgImage( imageName );
	}
	imageName = inResourceData->GetString("bg_image", "");
	if (*imageName)
	{
		SetBgImage( imageName );
	}

	BaseClass::ApplySettings( inResourceData );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CircularProgressBar::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);

	SetFgColor(GetSchemeColor("CircularProgressBar.FgColor", pScheme));
	SetBgColor(GetSchemeColor("CircularProgressBar.BgColor", pScheme));
	SetBorder(NULL);

	for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ )
	{
		if ( m_pszImageName[i] && strlen( m_pszImageName[i] ) > 0 )
		{
			if ( m_nTextureId[i] == -1 )
			{
				m_nTextureId[i] = surface()->CreateNewTextureID();
			}

			surface()->DrawSetTextureFile( m_nTextureId[i], m_pszImageName[i], true, false);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: sets an image by file name
//-----------------------------------------------------------------------------
void CircularProgressBar::SetImage(const char *imageName, progress_textures_t iPos)
{
	const char *pszDir = "vgui/";
	int len = Q_strlen(imageName) + 1;
	len += strlen(pszDir);
	
	if ( m_pszImageName[iPos] && ( m_lenImageName[iPos] < len ) )
	{
		// If we already have a buffer, but it is too short, then free the buffer
		delete [] m_pszImageName[iPos];
		m_pszImageName[iPos] = NULL;
		m_lenImageName[iPos] = 0;
	}

	if ( !m_pszImageName[iPos] )
	{
		m_pszImageName[iPos] = new char[ len ];
		m_lenImageName[iPos] = len;
	}

	Q_snprintf( m_pszImageName[iPos], len, "%s%s", pszDir, imageName );
	InvalidateLayout(false, true); // force applyschemesettings to run
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CircularProgressBar::PaintBackground()
{
	// If we don't have a Bg image, use the foreground
	int iTextureID = m_nTextureId[PROGRESS_TEXTURE_BG] != -1 ? m_nTextureId[PROGRESS_TEXTURE_BG] : m_nTextureId[PROGRESS_TEXTURE_FG];
	vgui::surface()->DrawSetTexture( iTextureID );
	vgui::surface()->DrawSetColor( GetBgColor() );

	int wide, tall;
	GetSize(wide, tall);

	vgui::surface()->DrawTexturedRect( 0, 0, wide, tall );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CircularProgressBar::Paint()
{
	float flProgress = GetProgress();
	float flEndAngle;

	flEndAngle = m_bReverseProgress ? ( 1.0 - flProgress ) : flProgress;
	flEndAngle = m_iProgressDirection == PROGRESS_CCW ? ( 1.0 - flEndAngle ) : flEndAngle;

	DrawCircleSegment( GetFgColor(), flEndAngle, ( m_iProgressDirection == PROGRESS_CW ) );
}

typedef struct
{
	float minProgressRadians;

	float vert1x;
	float vert1y;
	float vert2x;
	float vert2y;

	int swipe_dir_x;
	int swipe_dir_y;
} circular_progress_segment_t;

namespace vgui
{
// This defines the properties of the 8 circle segments
// in the circular progress bar.
circular_progress_segment_t Segments[8] = 
{
	{ 0.0,			0.5, 0.0, 1.0, 0.0, 1, 0 },
	{ M_PI * 0.25,	1.0, 0.0, 1.0, 0.5, 0, 1 },
	{ M_PI * 0.5,	1.0, 0.5, 1.0, 1.0, 0, 1 },
	{ M_PI * 0.75,	1.0, 1.0, 0.5, 1.0, -1, 0 },
	{ M_PI,			0.5, 1.0, 0.0, 1.0, -1, 0 },
	{ M_PI * 1.25,	0.0, 1.0, 0.0, 0.5, 0, -1 },
	{ M_PI * 1.5,	0.0, 0.5, 0.0, 0.0, 0, -1 },
	{ M_PI * 1.75,	0.0, 0.0, 0.5, 0.0, 1, 0 },
};

};

#define SEGMENT_ANGLE	( M_PI / 4 )

// function to draw from A to B degrees, with a direction
// we draw starting from the top ( 0 progress )
void CircularProgressBar::DrawCircleSegment( Color c, float flEndProgress, bool bClockwise )
{
	if ( m_nTextureId[PROGRESS_TEXTURE_FG] == -1 )
		return;

	int wide, tall;
	GetSize(wide, tall);

	float flWide = (float)wide;
	float flTall = (float)tall;

	float flHalfWide = (float)wide / 2;
	float flHalfTall = (float)tall / 2;

	vgui::surface()->DrawSetTexture( m_nTextureId[PROGRESS_TEXTURE_FG] );
	vgui::surface()->DrawSetColor( c );

	// if we want to progress CCW, reverse a few things
	if ( !bClockwise )
	{
		float flEndProgressRadians = flEndProgress * M_PI * 2;

		int i;
		for ( i=0;i<8;i++ )
		{
			float segmentRadiansMin = Segments[i].minProgressRadians;
			float segmentRadiansMax = segmentRadiansMin + SEGMENT_ANGLE;

			if ( flEndProgressRadians < segmentRadiansMax )
			{
				vgui::Vertex_t v[3];

				// vert 0 is ( 0.5, 0.5 )
				v[0].m_Position.Init( flHalfWide, flHalfTall );
				v[0].m_TexCoord.Init( 0.5f, 0.5f );

				float flInternalProgress = segmentRadiansMax - flEndProgressRadians;

				if ( flInternalProgress < SEGMENT_ANGLE )
				{
					// Calc how much of this slice we should be drawing
					flInternalProgress = SEGMENT_ANGLE - flInternalProgress;

					if ( i % 2 == 1 )
					{
						flInternalProgress = SEGMENT_ANGLE - flInternalProgress;
					}

					float flTan = tan(flInternalProgress);

					float flDeltaX, flDeltaY;

					if ( i % 2 == 1 )
					{
						flDeltaX = ( flHalfWide - flHalfTall * flTan ) * Segments[i].swipe_dir_x;
						flDeltaY = ( flHalfTall - flHalfWide * flTan ) * Segments[i].swipe_dir_y;
					}
					else
					{
						flDeltaX = flHalfTall * flTan * Segments[i].swipe_dir_x;
						flDeltaY = flHalfWide * flTan * Segments[i].swipe_dir_y;
					}

					v[1].m_Position.Init( Segments[i].vert1x * flWide + flDeltaX, Segments[i].vert1y * flTall + flDeltaY );
					v[1].m_TexCoord.Init( Segments[i].vert1x + ( flDeltaX / flHalfWide ) * 0.5, Segments[i].vert1y + ( flDeltaY / flHalfTall ) * 0.5 );
				}
				else
				{
					// full segment, easy calculation
					v[1].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert1x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert1y - 0.5 ) );
					v[1].m_TexCoord.Init( Segments[i].vert1x, Segments[i].vert1y );
				}

				// vert 2 is ( Segments[i].vert1x, Segments[i].vert1y )
				v[2].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert2x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert2y - 0.5 ) );
				v[2].m_TexCoord.Init( Segments[i].vert2x, Segments[i].vert2y );

				vgui::surface()->DrawTexturedPolygon( 3, v );
			}
		}
		return;
	}


	float flEndProgressRadians = flEndProgress * M_PI * 2;

	int cur_wedge = m_iStartSegment;
	for ( int i=0;i<8;i++ )
	{
		if ( flEndProgressRadians > Segments[cur_wedge].minProgressRadians)
		{
			vgui::Vertex_t v[3];

			// vert 0 is ( 0.5, 0.5 )
			v[0].m_Position.Init( flHalfWide, flHalfTall );
			v[0].m_TexCoord.Init( 0.5f, 0.5f );

			float flInternalProgress = flEndProgressRadians - Segments[cur_wedge].minProgressRadians;

			if ( flInternalProgress < SEGMENT_ANGLE )
			{
				// Calc how much of this slice we should be drawing

				if ( i % 2 == 1 )
				{
					flInternalProgress = SEGMENT_ANGLE - flInternalProgress;
				}

				float flTan = tan(flInternalProgress);
	
				float flDeltaX, flDeltaY;

				if ( i % 2 == 1 )
				{
					flDeltaX = ( flHalfWide - flHalfTall * flTan ) * Segments[i].swipe_dir_x;
					flDeltaY = ( flHalfTall - flHalfWide * flTan ) * Segments[i].swipe_dir_y;
				}
				else
				{
					flDeltaX = flHalfTall * flTan * Segments[i].swipe_dir_x;
					flDeltaY = flHalfWide * flTan * Segments[i].swipe_dir_y;
				}

				v[2].m_Position.Init( Segments[i].vert1x * flWide + flDeltaX, Segments[i].vert1y * flTall + flDeltaY );
				v[2].m_TexCoord.Init( Segments[i].vert1x + ( flDeltaX / flHalfWide ) * 0.5, Segments[i].vert1y + ( flDeltaY / flHalfTall ) * 0.5 );
			}
			else
			{
				// full segment, easy calculation
				v[2].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert2x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert2y - 0.5 ) );
				v[2].m_TexCoord.Init( Segments[i].vert2x, Segments[i].vert2y );
			}

			// vert 2 is ( Segments[i].vert1x, Segments[i].vert1y )
			v[1].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert1x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert1y - 0.5 ) );
			v[1].m_TexCoord.Init( Segments[i].vert1x, Segments[i].vert1y );

			vgui::surface()->DrawTexturedPolygon( 3, v );
		}

		cur_wedge++;
		if ( cur_wedge >= 8)
			cur_wedge = 0;
	}
}