#include <apps/Common/wxAbstraction.h>
#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_ppPlotValues(0), m_CurrentMaxPlotValues(), m_CurrentMinPlotValues(), m_CurrentPlotValues(),
	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"));
	AllocateDataBuffer( 1 );
	m_pens[0] = *wxRED;
	m_pens[1] = *wxGREEN;
	m_pens[2] = *wxBLUE;
	m_pens[3] = wxColour(128, 128, 0);
}

//-----------------------------------------------------------------------------
InfoPlotCanvas::~InfoPlotCanvas()
//-----------------------------------------------------------------------------
{
	DeallocateDataBuffer();
}

//-----------------------------------------------------------------------------
void InfoPlotCanvas::AllocateDataBuffer( unsigned int plotCount )
//-----------------------------------------------------------------------------
{
	DeallocateDataBuffer();
	m_PlotCount = plotCount;
	m_ppPlotValues = new std::deque<int64_type>*[m_PlotCount];
	m_CurrentMaxPlotValues.resize( m_PlotCount );
	m_CurrentMinPlotValues.resize( m_PlotCount );
	m_CurrentPlotValues.resize( m_PlotCount );
	m_PlotIdentifiers.resize( m_PlotCount );
	for( unsigned int i=0; i<m_PlotCount; i++ )
	{
		m_ppPlotValues[i] = new std::deque<int64_type>();
		m_CurrentMaxPlotValues[i] = numeric_limits<int64_type>::min();
		m_CurrentMinPlotValues[i] = numeric_limits<int64_type>::max();
		m_CurrentPlotValues[i] = 0;
	}
}

//-----------------------------------------------------------------------------
void InfoPlotCanvas::DeallocateDataBuffer( void )
//-----------------------------------------------------------------------------
{
	if( m_ppPlotValues )
	{
		for( unsigned int i=0; i<m_PlotCount; i++ )
		{
			delete m_ppPlotValues[i];
		}
		delete [] m_ppPlotValues;
		m_ppPlotValues = 0;
		m_CurrentMaxPlotValues.clear();
		m_CurrentMinPlotValues.clear();
		m_CurrentPlotValues.clear();
		m_PlotIdentifiers.clear();
		m_PlotCount = 0;
	}
}

//-----------------------------------------------------------------------------
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 );

	wxCoord textOffset = 1;

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

		// info in the top left corner
		wxString descriptionText(wxString::Format( wxT("%s: max: %8lld, min: %8lld: "), m_PlotIdentifiers[i].c_str(), m_CurrentMaxPlotValues[i], m_CurrentMinPlotValues[i] ));
		dc.DrawText( descriptionText, textOffset, 1 );
		wxCoord textWidth, textHeight;
		dc.GetTextExtent( descriptionText, &textWidth, &textHeight );
		dc.SetPen( wxPen(*wxBLACK, 1, wxSOLID ) );
		dc.SetBrush( wxBrush(m_pens[i%COLOUR_COUNT]) );
		dc.DrawRectangle( textOffset + textWidth, (((textHeight-10)/2)+1), 10, 10 );
		textOffset += textWidth + 15;
		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;
	}

	PlotHashTable::const_iterator it = m_PlotHashTable.find( infoData.settingUsed );
	unsigned int index = ( it == m_PlotHashTable.end() ) ? 0 : it->second;

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

	m_ppPlotValues[index]->push_back( newValue );
	bool boFullUpdate = RefreshPlotData();

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

	Refresh( boFullUpdate );
}

//-----------------------------------------------------------------------------
bool InfoPlotCanvas::RefreshPlotData( void )
//-----------------------------------------------------------------------------
{
	bool boFullUpdate = false;

	for( unsigned int i=0; i<m_PlotCount; i++ )
	{
		bool boMaxRemovedFromQueue = false;
		bool boMinRemovedFromQueue = false;
		while( m_ppPlotValues[i]->size() > m_HistoryDepth )
		{
			if( m_ppPlotValues[i]->front() == m_CurrentMaxPlotValues[i] )
			{
				boMaxRemovedFromQueue = true;
			}
			if( m_ppPlotValues[i]->front() == m_CurrentMinPlotValues[i] )
			{
				boMinRemovedFromQueue = true;
			}
			m_ppPlotValues[i]->pop_front();
		}

		if( boMaxRemovedFromQueue )
		{
			m_CurrentMaxPlotValues[i] = *(max_element( m_ppPlotValues[i]->begin(), m_ppPlotValues[i]->end() ));
		}
		else if( !m_ppPlotValues[i]->empty() && ( m_ppPlotValues[i]->back() > m_CurrentMaxPlotValues[i] ) )
		{
			m_CurrentMaxPlotValues[i] = m_ppPlotValues[i]->back();
		}

		if( boMinRemovedFromQueue )
		{
			m_CurrentMinPlotValues[i] = *(min_element( m_ppPlotValues[i]->begin(), m_ppPlotValues[i]->end() ));
		}
		else if( !m_ppPlotValues[i]->empty() && ( m_ppPlotValues[i]->back() < m_CurrentMinPlotValues[i] ) )
		{
			m_CurrentMinPlotValues[i] = m_ppPlotValues[i]->back();
		}
	}

	int64_type maxVal = *(max_element( m_CurrentMaxPlotValues.begin(), m_CurrentMaxPlotValues.end() ));
	if( m_CurrentMaxPlotValue != maxVal )
	{
		m_CurrentMaxPlotValue = maxVal;
		boFullUpdate = true;
	}

	int64_type minVal = *(min_element( m_CurrentMinPlotValues.begin(), m_CurrentMinPlotValues.end() ));
	if( m_CurrentMinPlotValue != minVal )
	{
		m_CurrentMinPlotValue = minVal;
		boFullUpdate = true;
	}

	return boFullUpdate;
}

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

//-----------------------------------------------------------------------------
void InfoPlotCanvas::SetupPlotIdentifiers( const vector<pair<string, int> >& plotIdentifiers )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	const unsigned int plotCount = static_cast<unsigned int>(plotIdentifiers.size());
	AllocateDataBuffer( plotCount );
	m_PlotHashTable.clear();
	for( unsigned int i=0; i<plotCount; i++ )
	{
		m_PlotHashTable.insert( make_pair( plotIdentifiers[i].second, i ) );
		m_PlotIdentifiers[i] = ConvertedString(plotIdentifiers[i].first);
	}
}

//-----------------------------------------------------------------------------
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;
	for( unsigned int i=0; i<m_PlotCount; i++ )
	{
		m_ppPlotValues[i]->clear();
		m_CurrentMaxPlotValues[i] = numeric_limits<int64_type>::min();
		m_CurrentMinPlotValues[i] = numeric_limits<int64_type>::max();
	}
	m_CurrentMaxPlotValue = numeric_limits<int64_type>::min();
	m_CurrentMinPlotValue = numeric_limits<int64_type>::max();
	Refresh( true );
}
