#include <algorithm>
#include <limits>
#include "InfoPlotCanvas.h"
#include <wx/dcbuffer.h>

using namespace std;
using namespace mvIMPACT::acquire;

IMPLEMENT_DYNAMIC_CLASS(InfoPlotCanvas, PlotCanvas)

BEGIN_EVENT_TABLE(InfoPlotCanvas, PlotCanvas)
  EVT_PAINT(InfoPlotCanvas::OnPaint)
END_EVENT_TABLE()

//-----------------------------------------------------------------------------
InfoPlotCanvas::InfoPlotCanvas( wxWindow* parent, wxWindowID id /* = -1 */, const wxPoint& pos /* = wxDefaultPosition */,
				   const wxSize& size /* = wxDefaultSize */, long style /* = wxSUNKEN_BORDER */,
				   const wxString& name /* = "info plot" */, bool boActive /* = false */ )
	: PlotCanvas(parent, id, pos, size, style, name, boActive), m_SelectedPlot(psTimestampDifference),
	m_HistoryDepth(20), m_CurrentPlotValue(0), m_CurrentMaxPlotValue(numeric_limits<int64_type>::min()), m_CurrentMinPlotValue(numeric_limits<int64_type>::max())
//-----------------------------------------------------------------------------
{
	m_PlotNames[psTimestampDifference] = wxString(wxT("Timestamp difference"));
	m_PlotNames[psExposureTime_us] = wxString(wxT("Exposure time (us)"));
	m_PlotNames[psFrameIDDifference] = wxString(wxT("Frame ID difference"));
	m_PlotNames[psFrameID] = wxString(wxT("Frame ID"));
	m_PlotNames[psGain_mdB] = wxString(wxT("Gain (milli-dB)"));
	m_PlotNames[psLineCounter] = wxString(wxT("Line counter"));
}

//-----------------------------------------------------------------------------
InfoPlotCanvas::~InfoPlotCanvas()
//-----------------------------------------------------------------------------
{

}


//-----------------------------------------------------------------------------
void InfoPlotCanvas::OnPaint( wxPaintEvent & )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	wxPaintDC dc( this );

	if( !IsActive() )
	{
		return;
	}

	wxCoord w, h;
	dc.GetSize(&w, &h);
	int borderWidth = GetBorderWidth();
	int64_type offset = ( m_CurrentMinPlotValue < 0 ) ? m_CurrentMinPlotValue : 0;
	int64_type currentYRange = m_CurrentMaxPlotValue - offset;
	double scaleY = static_cast<double>(h-2*borderWidth)/static_cast<double>(( currentYRange != 0 ) ? currentYRange : 1 );
	double scaleX = static_cast<double>(w-2*borderWidth)/static_cast<double>(m_HistoryDepth);

	// white rectangle for the background
	dc.SetPen( *wxBLACK );
	dc.SetBrush( *wxWHITE_BRUSH );
	dc.DrawRectangle( 0, 0, w, h );
	dc.SetBrush( wxNullBrush );
	// lower line
	dc.DrawLine( borderWidth, h-borderWidth, w-borderWidth, h-borderWidth );
	// line on left side
	dc.DrawLine( borderWidth, h-borderWidth, borderWidth, borderWidth );

	// markers for X-axis
	int markerStartHeight = h - borderWidth - 3;
	int markerEndHeight = h - borderWidth + 4;
	const int XMarkerStepWidth = m_HistoryDepth / 5;
	for( unsigned int i=0; i<m_HistoryDepth; i += XMarkerStepWidth )
	{
		dc.DrawLine( static_cast<int>( borderWidth + ( i * scaleX )), markerStartHeight, static_cast<int>( borderWidth + ( i * scaleX )), markerEndHeight );
		dc.DrawText( wxString::Format( wxT("%d"), i ), static_cast<int>( borderWidth + ( i * scaleX )), h - borderWidth + 3 );
	}

	// markers for y-axis
	int markerStart = borderWidth - 3;
	int markerEnd = borderWidth + 4;
	dc.DrawLine( markerStart, borderWidth, markerEnd, borderWidth );

	const unsigned int valCount = static_cast<unsigned int>(m_PlotValues.size());
	if( valCount > 1 )
	{
		int lowerStart = h - GetBorderWidth();
		dc.SetPen( *wxRED );
		for( unsigned int i=0; i<valCount-1; i++ )
		{
			dc.DrawLine( static_cast<int>( GetBorderWidth() + ( i*scaleX ) + 1 ),
						 static_cast<int>( lowerStart - ( ( m_PlotValues[i] - offset ) * scaleY ) ),
						 static_cast<int>( GetBorderWidth() + ( ( i+1 ) * scaleX ) + 1 ),
						 static_cast<int>( lowerStart - ( ( m_PlotValues[i+1] - offset ) * scaleY ) ) );
		}
		dc.SetPen( wxNullPen );
	}

	// info in the top left corner
	wxString descriptionText(wxString::Format( wxT("%s(current max: %8lld, current min: %8lld): "), m_PlotNames[m_SelectedPlot].c_str(), m_CurrentMaxPlotValue, m_CurrentMinPlotValue ));
	dc.DrawText( descriptionText, 1, 1 );
	wxCoord textWidth, textHeight;
	dc.GetTextExtent( descriptionText, &textWidth, &textHeight );
	dc.SetPen( wxPen(*wxBLACK, 1, wxSOLID ) );
	dc.SetBrush( wxBrush(*wxRED) );
	dc.DrawRectangle( textWidth, (((textHeight-10)/2)+1), 10, 10 );
	dc.SetBrush( wxNullBrush );
	dc.SetPen( wxNullPen );

	dc.SetBrush( wxNullBrush );
	dc.SetPen( wxNullPen );
}

//-----------------------------------------------------------------------------
bool InfoPlotCanvas::MustUpdate( bool boForceRefresh )
//-----------------------------------------------------------------------------
{
	if( ( ( m_ImageCount++ % m_UpdateFrequency ) == 0 ) || boForceRefresh )
	{
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
void InfoPlotCanvas::RefreshData( const RequestInfoData& infoData, bool boForceRefresh /* = false */ )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	if( !IsActive() )
	{
		return;
	}

	int64_type newValue = 0;
	switch( m_SelectedPlot )
	{
	case psTimestampDifference:
		newValue = infoData.timeStamp_us - m_CurrentPlotValue;
		m_CurrentPlotValue = infoData.timeStamp_us;
		break;
	case psExposureTime_us:
		newValue = infoData.exposeTime_us;
		m_CurrentPlotValue = infoData.exposeTime_us;
		break;
	case psFrameIDDifference:
		newValue = infoData.frameID - m_CurrentPlotValue;
		m_CurrentPlotValue = infoData.frameID;
		break;
	case psFrameID:
		newValue = infoData.frameID;
		m_CurrentPlotValue = infoData.frameID;
		break;
	case psGain_mdB:
		newValue = static_cast<int64_type>(infoData.gain_dB * 1000);
		m_CurrentPlotValue = static_cast<int64_type>(infoData.gain_dB * 1000);
		break;
	case psLineCounter:
		newValue = infoData.lineCounter;
		m_CurrentPlotValue = infoData.lineCounter;
		break;
	case psLAST:
		return;
		break;
	// don't add a default here. If an enum is not handled here the compiler can tell us then
	}

	m_PlotValues.push_back( newValue );
	bool boFullUpdate = RefreshPlotData();
	
	if( m_CurrentMaxPlotValue < newValue )
	{
		m_CurrentMaxPlotValue = newValue;
		boFullUpdate = true;
	}

	if( m_CurrentMinPlotValue > newValue )
	{
		m_CurrentMinPlotValue = newValue;
		boFullUpdate = true;
	}

	if( !MustUpdate( boForceRefresh ) )
	{
		return;
	}

	Refresh( boFullUpdate );
}

//-----------------------------------------------------------------------------
bool InfoPlotCanvas::RefreshPlotData( void )
//-----------------------------------------------------------------------------
{
	bool boMaxRemovedFromQueue = false;
	bool boMinRemovedFromQueue = false;
	while( m_PlotValues.size() > m_HistoryDepth )
	{
		if( m_PlotValues.front() == m_CurrentMaxPlotValue )
		{
			boMaxRemovedFromQueue = true;
		}
		if( m_PlotValues.front() == m_CurrentMinPlotValue )
		{
			boMinRemovedFromQueue = true;
		}
		m_PlotValues.pop_front();
	}

	bool boFullUpdate = false;
	if( boMaxRemovedFromQueue )
	{
		m_CurrentMaxPlotValue = *(max_element( m_PlotValues.begin(), m_PlotValues.end() ));
		boFullUpdate = true;
	}

	if( boMinRemovedFromQueue )
	{
		m_CurrentMinPlotValue = *(min_element( m_PlotValues.begin(), m_PlotValues.end() ));
		boFullUpdate = true;
	}
	return boFullUpdate;
}

//-----------------------------------------------------------------------------
void InfoPlotCanvas::SetHistoryDepth( unsigned int historyDepth )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	m_HistoryDepth = historyDepth;
	RefreshPlotData();
}

//-----------------------------------------------------------------------------
void InfoPlotCanvas::SetPlotSelection( TPlotSelection plotSelection )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	if( m_SelectedPlot == plotSelection )
	{
		return;
	}

	// ignore invalid values
	if( ( plotSelection >= psLAST ) || ( plotSelection < psFIRST ) )
	{
		return;
	}

	m_SelectedPlot = plotSelection;
	m_PlotValues.clear();
	m_CurrentPlotValue = 0;
	m_CurrentMaxPlotValue = numeric_limits<int64_type>::min();
	m_CurrentMinPlotValue = numeric_limits<int64_type>::max();
	Refresh( true );
}
