#include "DrawingScope.h"
#include "ImageCanvas.h"
#include "PropViewFrame.h"
#include <wx/dcbuffer.h>
#include <wx/settings.h>
#ifdef USE_RAW_BITMAP_SUPPORT
#	include <wx/rawbmp.h>
#else
#	include <wx/image.h>
#endif // USE_RAW_BITMAP_SUPPORT

IMPLEMENT_DYNAMIC_CLASS(ImageCanvas, DrawingCanvas)

BEGIN_EVENT_TABLE(ImageCanvas, DrawingCanvas)
  EVT_PAINT(ImageCanvas::OnPaint)
  EVT_LEFT_DCLICK(ImageCanvas::OnDblClick)
END_EVENT_TABLE()

#ifdef USE_RAW_BITMAP_SUPPORT
	typedef wxPixelData<wxBitmap, wxNativePixelFormat> PixelData;
#endif // USE_RAW_BITMAP_SUPPORT

using namespace std;

//-----------------------------------------------------------------------------
struct ImageCanvasImpl
//-----------------------------------------------------------------------------
{
	wxBrush backgroundBrush;
	wxBitmap currentImage;
	TImageBufferPixelFormat format;
	ImageCanvasImpl() : backgroundBrush(wxSystemSettings::GetColour( wxSYS_COLOUR_APPWORKSPACE ), wxCROSSDIAG_HATCH),
		format(ibpfRaw) {}
};


#ifdef USE_RAW_BITMAP_SUPPORT
	//-----------------------------------------------------------------------------
	void Copy2ByteMonoImage( const ImageBuffer* pIB, PixelData::Iterator& p, PixelData& data, unsigned int shift )
	//-----------------------------------------------------------------------------
	{
		unsigned short* pSrc = reinterpret_cast<unsigned short*>(pIB->vpData);
		for( int y=0; y<pIB->iHeight; ++y )
		{
			PixelData::Iterator rowStart = p;
			char val;
			for( int x=0; x<pIB->iWidth; ++x, ++p )
			{
				val = (*pSrc) >> shift;
				++pSrc;
				p.Red() = val;
				p.Green() = val;
				p.Blue() = val;
			}
			p = rowStart;
			p.OffsetY(data, 1);
		}
	}

	//-----------------------------------------------------------------------------
	void ConvertYUVToRGB( const ImageBuffer* pIB, PixelData::Iterator& p, PixelData& data )
	//-----------------------------------------------------------------------------
	{
		unsigned char* pSrc = reinterpret_cast<unsigned char*>(pIB->vpData);
		for( int line=0; line<pIB->iHeight; ++line )
		{
			int r, g, b, src_bypp = 2;
			PixelData::Iterator rowStart = p;
			unsigned char* y = pSrc;
			unsigned char* v = pSrc + 1;
			unsigned char* u = pSrc + src_bypp + 1;

			for( int x=0; x<pIB->iWidth; x++, ++p )
			{
				b = static_cast<int>(1.164*(static_cast<double>(*y)-16) + 1.596*(static_cast<double>(*v)-128));
				g = static_cast<int>(1.164*(static_cast<double>(*y)-16) - 0.813*(static_cast<double>(*v)-128) - 0.391*(static_cast<double>(*u)-128));
				r = static_cast<int>(1.164*(static_cast<double>(*y)-16)                                       + 2.018*(static_cast<double>(*u)-128));
				p.Red() = max( 0, min( r, 255 ) );
				p.Green() = max( 0, min( g, 255 ) );
				p.Blue() = max( 0, min( b, 255 ) );
				y += src_bypp;

				if( x & 1 )
				{
					u += 2 * src_bypp;
					v += 2 * src_bypp;
				}
			}
			p = rowStart;
			p.OffsetY( data, 1 );
			pSrc += pIB->pChannels[0].iLinePitch;
		}
	}
#else
	//-----------------------------------------------------------------------------
	void Copy2ByteMonoImage( const ImageBuffer* pIB, unsigned char* pDst, unsigned int shift )
	//-----------------------------------------------------------------------------
	{
		unsigned short* pSrc = reinterpret_cast<unsigned short*>(pIB->vpData);
		unsigned char val;
		for( int i=0; i<pIB->iWidth * pIB->iHeight; i++ )
		{
			val = (*pSrc) >> shift;
			++pSrc;
			*pDst++ = val;
			*pDst++ = val;
			*pDst++ = val;
		}
	}

	//-----------------------------------------------------------------------------
	void ConvertYUVToRGB( const ImageBuffer* pIB, unsigned char* pDst )
	//-----------------------------------------------------------------------------
	{
		unsigned char* pSrc = reinterpret_cast<unsigned char*>(pIB->vpData);
		for( int line=0; line<pIB->iHeight; ++line )
		{
			int r, g, b, src_bypp = 2;
			unsigned char* y = pSrc;
			unsigned char* v = pSrc + 1;
			unsigned char* u = pSrc + src_bypp + 1;

			for( int x=0; x<pIB->iWidth; x++ )
			{
				b = static_cast<int>(1.164*(static_cast<double>(*y)-16) + 1.596*(static_cast<double>(*v)-128));
				g = static_cast<int>(1.164*(static_cast<double>(*y)-16) - 0.813*(static_cast<double>(*v)-128) - 0.391*(static_cast<double>(*u)-128));
				r = static_cast<int>(1.164*(static_cast<double>(*y)-16)                                       + 2.018*(static_cast<double>(*u)-128));
				*pDst++ = max( 0, min( r, 255 ) );
				*pDst++ = max( 0, min( g, 255 ) );
				*pDst++ = max( 0, min( b, 255 ) );
				y += src_bypp;

				if( x & 1 )
				{
					u += 2 * src_bypp;
					v += 2 * src_bypp;
				}
			}
			pSrc += pIB->pChannels[0].iLinePitch;
		}
	}
#endif // #ifdef USE_RAW_BITMAP_SUPPORT

//-----------------------------------------------------------------------------
ImageCanvas::ImageCanvas( wxWindow* pApp, wxWindow* parent, wxWindowID id /* = -1 */, const wxPoint& pos /* = wxDefaultPosition */,
	const wxSize& size /* = wxDefaultSize */, long style /* = wxSUNKEN_BORDER */, const wxString& name /* = "ImageCanvas" */, bool boActive /* = true */ )
	: DrawingCanvas( parent, id, pos, size, style, name, boActive ), m_boScaling(false), 
	  m_pAppEventHandler(pApp->GetEventHandler()), m_pAppWinID(pApp->GetId())
//-----------------------------------------------------------------------------
{
	m_pImpl = new ImageCanvasImpl();
}

//-----------------------------------------------------------------------------
ImageCanvas::~ImageCanvas()
//-----------------------------------------------------------------------------
{
	AOISet::iterator it = m_aoiSet.begin();
	while( it != m_aoiSet.end() )
	{
		delete *it;
		++it;
	}
	delete m_pImpl;
}

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

	if( !IsActive() )
	{
		return;
	}

	wxMemoryDC memDC;
	int bmpH, bmpW, bmpXOff = 0, bmpYOff = 0;
	wxCoord w, h;

	dc.GetSize(&w, &h);

	DrawingScope scope(dc);
	dc.SetBrush( m_pImpl->backgroundBrush );
	dc.SetPen( *wxTRANSPARENT_PEN );
	dc.SetBackground( *wxWHITE_BRUSH );

	if( !m_pImpl->currentImage.Ok() )
	{
		// display background!
		dc.DrawRectangle( 0, 0, w, h );
	}
	else
	{
		bmpH = m_pImpl->currentImage.GetHeight();
		bmpW = m_pImpl->currentImage.GetWidth();
		memDC.SelectObject(m_pImpl->currentImage);
		if( ( bmpH > 0 ) && ( bmpW > 0 ) )
		{

			double scaleFactor = 1.0;
			if( m_boScaling )
			{
				double scaleX = static_cast<double>(w) / static_cast<double>(bmpW);
				double scaleY = static_cast<double>(h) / static_cast<double>(bmpH);
				// always keep the aspect ratio
				scaleFactor = ( scaleX <= scaleY ) ? scaleX : scaleY;
			}
			double imgW = bmpW * scaleFactor;
			double imgH = bmpH * scaleFactor;
			// draw bounding box pattern with the background brush...
			if( w > imgW )
			{
				// ... on the left and right
				bmpXOff = static_cast<int>(w - imgW) / 2;
				dc.DrawRectangle( 0, 0, bmpXOff, h );
				dc.DrawRectangle( static_cast<int>(imgW + bmpXOff + 1), 0, bmpXOff - 1, h );
			}
			if( h > imgH )
			{
				// ... on top and bottom
				bmpYOff = static_cast<int>(h - imgH) / 2;
				dc.DrawRectangle( 0, 0, w, bmpYOff - 1 );
				dc.DrawRectangle( 0, static_cast<int>(imgH + bmpYOff + 1), w, static_cast<int>(imgH + bmpYOff - 1) );
			}
			dc.SetUserScale( scaleFactor, scaleFactor );
			// produces false colors when scaled and is not faster then the blit method!
			// dc.DrawBitmap( m_pImpl->currentImage, bmpXOff/scaleFactor, bmpYOff/scaleFactor, false );
			dc.Blit( static_cast<int>(bmpXOff/scaleFactor), static_cast<int>(bmpYOff/scaleFactor), bmpW, bmpH , &memDC, 0, 0 );
			dc.SetUserScale( 1.0, 1.0 );
			dc.SetBrush( *wxTRANSPARENT_BRUSH );
			AOISet::iterator it = m_aoiSet.begin();
			while( it != m_aoiSet.end() )
			{
				dc.SetPen( (*it)->m_colour );
				int aoix = (*it)->m_rect.GetX();
				int aoiy = (*it)->m_rect.GetY();
				int aoiw = (*it)->m_rect.GetWidth();
				int aoih = (*it)->m_rect.GetHeight();
				dc.DrawRectangle(	aoix + bmpXOff,
									aoiy + bmpYOff,
									( aoiw + aoix > bmpW ) ? static_cast<int>(( bmpW - aoix ) * scaleFactor ) : static_cast<int>( aoiw * scaleFactor ),
									( aoih + aoiy > bmpH ) ? static_cast<int>(( bmpH - aoiy ) * scaleFactor ) : static_cast<int>( aoih * scaleFactor ));
				dc.SetTextForeground( (*it)->m_colour );
				wxCoord textWidth, textHeight;
				dc.GetTextExtent( (*it)->m_description, &textWidth, &textHeight );
				dc.DrawText( (*it)->m_description,
							 aoix + bmpXOff + ( ( aoiw - textWidth ) / 2 ),
							 aoiy + bmpYOff + ( ( aoih - textHeight ) / 2 ) );
				dc.SetPen( wxNullPen );
				++it;
			}
			if( m_boShowOverlayMessages )
			{
				bool boFormatWarning = ( m_pImpl->format != ibpfMono8 ) && ( m_pImpl->format != ibpfRGBx888Packed );
				dc.SetTextForeground( wxColour( 255, 0, 0 ) );
				if( boFormatWarning && m_boScaling )
				{
					dc.DrawText( wxT("Performance loss because of pixel format conversion for display and scaling"), bmpXOff, bmpYOff + 20 );
				}
				else if( boFormatWarning )
				{
					dc.DrawText( wxT("Performance loss because of pixel format conversion for display"), bmpXOff, bmpYOff + 20 );
				}
				else if( m_boScaling )
				{
					dc.DrawText( wxT("Performance loss because of scaling"), bmpXOff, bmpYOff + 20 );
				}
			}
			// restore old brush
			dc.SetBrush( wxNullBrush );
		}
		memDC.SelectObject( wxNullBitmap );
	}
	dc.SetPen( wxNullPen );
	dc.SetBrush( wxNullBrush );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnDblClick( wxMouseEvent& )
//-----------------------------------------------------------------------------
{
	wxCommandEvent event(toggleDisplayArea, m_pAppWinID);
	wxPostEvent( m_pAppEventHandler, event );
}

//-----------------------------------------------------------------------------
AOI* ImageCanvas::RegisterAOI( const wxRect& r, const wxColour& c, const wxString& d )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOI* pAOI = new AOI(r, c, d);
	m_aoiSet.insert(pAOI);
	return pAOI;
}

//-----------------------------------------------------------------------------
bool ImageCanvas::RemoveAOI( AOI* pAOI )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOISet::iterator it = m_aoiSet.find(pAOI);
	if( it != m_aoiSet.end() )
	{
		delete *it;
		m_aoiSet.erase(it);
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
ImageCanvas::TSaveResult ImageCanvas::SaveCurrentImageAsBitmap( const wxString& filenameAndPath ) const
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker scopeLock(m_critSect);
	if( !m_pImpl->currentImage.Ok() )
	{
		return srNoImage;
	}

	if( !m_pImpl->currentImage.SaveFile( filenameAndPath, wxBITMAP_TYPE_BMP ) )
	{
		return srFailedToSave;
	}

	return srOK;
}

//-----------------------------------------------------------------------------
bool ImageCanvas::SetAOI( AOI* pAOI, int x, int y, int w, int h )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOISet::iterator it = m_aoiSet.find( pAOI );
	if( it != m_aoiSet.end() )
	{
		if( x >= 0 )
		{
			pAOI->m_rect.SetX(x);
		}
		if( y >= 0 )
		{
			pAOI->m_rect.SetY(y);
		}
		if( w >= 0 )
		{
			pAOI->m_rect.SetWidth(w);
		}
		if( h >= 0 )
		{
			pAOI->m_rect.SetHeight(h);
		}
		Refresh( false );
		return true;
	}
	return false;

}

//-----------------------------------------------------------------------------
bool ImageCanvas::SetImage( const ImageBuffer* pIB )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker( m_critSect );

	if( !IsActive() || !pIB )
	{
		return true;
	}

	bool boImageSizeChanged = false;
	if( !m_pImpl->currentImage.Ok() || ( m_pImpl->currentImage.GetWidth() != pIB->iWidth ) ||
		( m_pImpl->currentImage.GetHeight() != pIB->iHeight ) )
	{
		boImageSizeChanged = true;
	}

	if( !pIB->vpData )
	{
		// restore initial status as no valid image is currently available
		m_pImpl->currentImage = wxBitmap();
		Refresh( false );
		return true;
	}
#ifdef USE_RAW_BITMAP_SUPPORT
	if( boImageSizeChanged )
	{
		m_pImpl->currentImage = wxBitmap( pIB->iWidth, pIB->iHeight, 24 );
	}
	PixelData data(m_pImpl->currentImage);
	PixelData::Iterator p(data);
	switch( pIB->pixelFormat )
	{
	case ibpfMono8:
		{
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int y = 0; y < pIB->iHeight; ++y )
			{
				PixelData::Iterator rowStart = p;
				for ( int x = 0; x < pIB->iWidth; ++x, ++p )
				{
					p.Red() = *pSrc;
					p.Green() = *pSrc;
					p.Blue() = *pSrc++;
				}
				p = rowStart;
				p.OffsetY(data, 1);
			}
		}
		break;
	case ibpfMono16:
		Copy2ByteMonoImage( pIB, p, data, 8 );
		break;
	case ibpfRGBx888Packed:
		{
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int y = 0; y < pIB->iHeight; ++y )
			{
				PixelData::Iterator rowStart = p;
				for ( int x = 0; x < pIB->iWidth; ++x, ++p )
				{
					p.Blue() = *pSrc++;
					p.Green() = *pSrc++;
					p.Red() = *pSrc++;
					++pSrc;
				}
				p = rowStart;
				p.OffsetY(data, 1);
			}
		}
		break;
	case ibpfRGB888Packed:
		{
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int y = 0; y < pIB->iHeight; ++y )
			{
				PixelData::Iterator rowStart = p;
				for ( int x = 0; x < pIB->iWidth; ++x, ++p )
				{
					p.Blue() = *pSrc++;
					p.Green() = *pSrc++;
					p.Red() = *pSrc++;
				}
				p = rowStart;
				p.OffsetY(data, 1);
			}
		}
		break;
	case ibpfYUV422Packed:
		ConvertYUVToRGB( pIB, p, data );
		break;
	case ibpfRGBx888Planar:
		{
			unsigned char* pr = static_cast<unsigned char*>(pIB->vpData);
			unsigned char* pg = static_cast<unsigned char*>(pIB->vpData) + pIB->pChannels[1].iChannelOffset;
			unsigned char* pb = static_cast<unsigned char*>(pIB->vpData) + pIB->pChannels[2].iChannelOffset;
			for( int y = 0; y < pIB->iHeight; ++y )
			{
				PixelData::Iterator rowStart = p;
				for ( int x = 0; x < pIB->iWidth; ++x, ++p )
				{
					p.Red() = *pr++;
					p.Green() = *pg++;
					p.Blue() = *pb++;
				}
				p = rowStart;
				p.OffsetY(data, 1);
			}
		}
		break;
	case ibpfMono10:
		Copy2ByteMonoImage( pIB, p, data, 2 );
		break;
	case ibpfMono12:
		Copy2ByteMonoImage( pIB, p, data, 4 );
		break;
	case ibpfMono14:
		Copy2ByteMonoImage( pIB, p, data, 6 );
		break;
	default:
		return false;
		break;
	}
#else
	wxImage img( pIB->iWidth, pIB->iHeight, false );
	switch( pIB->pixelFormat )
	{
	case ibpfMono8:
		{
			unsigned char* pDst = img.GetData();
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int i=0; i<pIB->iWidth * pIB->iHeight; i++ )
			{
				*pDst++ = *pSrc;
				*pDst++ = *pSrc;
				*pDst++ = *pSrc++;
			}
		}
		break;
	case ibpfMono16:
		Copy2ByteMonoImage( pIB, img.GetData(), 8 );
		break;
	case ibpfRGBx888Packed:
		{
			unsigned char* pDst = img.GetData();
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int i=0; i<pIB->iWidth * pIB->iHeight; i++ )
			{
				*pDst++ = pSrc[2];
				*pDst++ = pSrc[1];
				*pDst++ = *pSrc;
				pSrc += 4;
			}
		}
		break;
	case ibpfRGB888Packed:
		{
			unsigned char* pDst = img.GetData();
			unsigned char* pSrc = (unsigned char*)pIB->vpData;
			for( int i=0; i<pIB->iWidth * pIB->iHeight; i++ )
			{
				*pDst++ = pSrc[2];
				*pDst++ = pSrc[1];
				*pDst++ = *pSrc;
				pSrc += 3;
			}
		}
		break;
	case ibpfYUV422Packed:
		ConvertYUVToRGB( pIB, img.GetData() );
		break;
	case ibpfRGBx888Planar:
		{
			unsigned char* pDst = img.GetData();
			unsigned char* pr = static_cast<unsigned char*>(pIB->vpData);
			unsigned char* pg = static_cast<unsigned char*>(pIB->vpData) + pIB->pChannels[1].iChannelOffset;
			unsigned char* pb = static_cast<unsigned char*>(pIB->vpData) + pIB->pChannels[2].iChannelOffset;
			for( int i=0; i<pIB->iWidth * pIB->iHeight; i++ )
			{
				*pDst++ = *pr++;
				*pDst++ = *pg++;
				*pDst++ = *pb++;
			}
		}
		break;
	case ibpfMono10:
		Copy2ByteMonoImage( pIB, img.GetData(), 2 );
		break;
	case ibpfMono12:
		Copy2ByteMonoImage( pIB, img.GetData(), 4 );
		break;
	case ibpfMono14:
		Copy2ByteMonoImage( pIB, img.GetData(), 6 );
		break;
	default:
		return false;
		break;
	}
	m_pImpl->currentImage = wxBitmap( img, -1 );
#endif // USE_RAW_BITMAP_SUPPORT
	m_pImpl->format = pIB->pixelFormat;
	Refresh( boImageSizeChanged );
	return true;
}

