/////////////////////////////////////////////////////////////////////////////
// Name:        wcamerapanel.cpp
// Purpose:		wxPanel derived class to show live camera image
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Modified by: 
// Created:     
// Copyright:   (C) 2008-11 Cesar Mauri Loba - CREA Software Systems
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////

//
// TODO
// 
// - On CameraPanel::OnPaint when (m_autoResize== true) check if is this 
//   the best method to notify parent a resize
// - On CameraPanel::DrawCam avoid copying image when the call is 
//   made from the main thread
//

#include "wcamerapanel.h"
#include "wxroicontrol.h"

#include <wx/dcclient.h>
#include <wx/image.h>
#include <wx/string.h>
#include <highgui.h>

namespace mod_camera {

#define MIN_WIDTH 160
#define MIN_HEIGHT 120
#define VP_ALIGNMENT 4

// New event to comunicate worker and GUI
DECLARE_LOCAL_EVENT_TYPE(wxEVT_MY_REFRESH, -1)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_MY_REFRESH)

// implement message map
BEGIN_EVENT_TABLE(CameraPanel, wxPanel)
	EVT_PAINT (CameraPanel::OnPaint)
//	EVT_SIZE (CameraPanel::OnSize) 
	EVT_MOUSE_EVENTS(CameraPanel::OnMouse)
	EVT_COMMAND  (wxID_ANY, wxEVT_MY_REFRESH, CameraPanel::OnRecvRefresh)	
END_EVENT_TABLE()

CameraPanel::CameraPanel(CleanupFunctor functor, WXRoiControls* roi_controls)
{
	Init();
	m_cleanupFunctor= functor;
	m_roiControls= roi_controls;
}

CameraPanel::CameraPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
{
	Init();
    Create( parent, id, pos, size, style, name );
}

bool CameraPanel::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
{
	if (size.GetWidth()== -1 && size.GetHeight()== -1) 
		m_autoResize= true;
	else 
		m_autoResize= false;
	bool retval= wxPanel::Create (parent, id, pos, size, style, name);

//	SetMinSize(wxSize (MIN_WIDTH, MIN_HEIGHT));

	return retval;
}

// asserts if registered controls found
CameraPanel::~CameraPanel(void)
{
	if (m_cleanupFunctor!= NULL) {
		m_cleanupFunctor();
		m_cleanupFunctor= NULL;
	}
}

void CameraPanel::RemoveCleanupFunctor()
{
	m_cleanupFunctor= NULL;
}

void CameraPanel::Init()
{
	// Get canvas width/height
	m_nImgWidth= 1;
	m_nImgHeight= 1;

	m_ImageShowed= true;
	m_AccessingImage= false;

	m_SharedImage.Create (1, 1);
	m_DisplayImage.Create (1, 1);	

	m_autoResize= false;

	m_cleanupFunctor= NULL;
	m_roiControls= NULL;	
}

#if 0
// If parent is a top level window resizes it's client area
void CameraPanel::ResizeParentClientArea(int width, int height)
{	
	assert ((width % VP_ALIGNMENT)== 0);

	if (m_resizeParent) {
		// TODO: parent should set size automatically changing own size
		// as a workdaround size is propagated to parent if is top level window
		wxWindow* pParent= GetParent();

		if (pParent) pParent->SetClientSize (width, height);
		//if (pParent->IsTopLevel()) pParent->SetClientSize (width, height);	
	}
}


// OnSize
void CameraPanel::OnSize (wxSizeEvent& event)
{
	bool changed= false;

	int width = event.GetSize().GetWidth();
	int height = event.GetSize().GetHeight();
	
	// New requested size should be aligned to 4 or 8 bytes.
	// So test it and force the alignment when necessary
	if (width % VP_ALIGNMENT) 
	{
		width= width + VP_ALIGNMENT - (width % VP_ALIGNMENT);
		changed= true;
	}
	
	if (width< MIN_WIDTH) { width= MIN_WIDTH; changed= true; }
	if (height< MIN_HEIGHT) { height= MIN_HEIGHT; changed= true; }
	
	if (changed) ResizeParentClientArea(width, height);
	event.Skip (true);
}
#endif

wxSize CameraPanel::DoGetBestSize() const
{
	if (m_autoResize) {

		// Return best size based on the size of the image to show or minimum size
		int width= MIN_WIDTH;
		int height= MIN_HEIGHT;

		if (m_nImgWidth> width) width= m_nImgWidth;
		if (m_nImgHeight> height) height= m_nImgHeight;
		return wxSize(width, height);
	}
	else
		return wxPanel::DoGetBestSize();
}

// DrawCam. Called from the worker thread
void CameraPanel::DrawCam (IplImage const * pImg)
{	
	int convertFlags= 0;

	// If last image not shown yet don't update
	assert (pImg);
	if (m_ImageShowed && pImg)
	{
		m_ImageCopyMutex.Enter();

		if (m_AccessingImage) 
		{
			// GUI thread is processing image, don't update
			m_ImageCopyMutex.Leave();
			return;
		}
		else
		{
			// Exclusive access to shared image
			m_AccessingImage= true;		
		}
		m_ImageCopyMutex.Leave();
		
		// Check that image is RGB with channel order RGB or BGR
		wxASSERT_MSG 
			(pImg->nChannels== 3 &&
			((pImg->channelSeq[0]== 'R' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'B') ||
			(pImg->channelSeq[0]== 'B' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'R')),
			_T("Wrong image format. It should be RGB or BGR") );
		
		//
		// Adapt image format to show on the screen
		//			
		
		// Allocate shared image if size changed	
		if (pImg->width!= m_SharedImage.Width() || pImg->height!= m_SharedImage.Height())
			m_SharedImage.Create (pImg->width, pImg->height, pImg->depth, "RGB", pImg->origin, pImg->align);
		
		
		assert (pImg->origin== 0);
		if (pImg->channelSeq[0]== 'B' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'R')
		{
			convertFlags|= CV_CVTIMG_SWAP_RB;
		//	pImg->channelSeq[0]= 'R';
		//	pImg->channelSeq[2]= 'B';
		}
		if (convertFlags) cvConvertImage ( pImg, m_SharedImage.ptr(), convertFlags );
		else cvCopy( pImg, m_SharedImage.ptr() );
		m_ImageShowed= false;
		
		// Release exclusive access to image
		m_AccessingImage= false;

		// When calling a GUI function from a thread different than 
		// the main one (this method is usually called from a worker thread)
		// synchonization is needed (under GTK+ is mandatory, for example).
		// but it seems that under Windows no GUI Mutex is needed. Futhermore,
		// wxMutexGuiEnter() blocks when pull down a menu from the menu bar
		// For more info check WX source thread sample 
		
		// As wxPostEvent is the recommended synchronization mechanism we'll use it.
		// Hug! wxPostEvent also blocks if main thread is processing an event :-(		
		// This problem has been reported as being caused on gtk by the call to
		// wxWakeUpIdle() inside wxPostEvent. See, for instance:
		// http://osdir.com/ml/lib.wxwindows.general/2003-10/msg00026.html
		// In theory if the macro __WXGTK20__ is defined this must not happen.
		// Since version 2.9.0 there is another funtion to post events "wxQueueEvent"
		// That should be investigated
		wxCommandEvent event(wxEVT_MY_REFRESH);
		wxPostEvent(this, event);
	}	
}

// OnPaint. Called on paint event
void CameraPanel::OnPaint (wxPaintEvent& event)
{
	event.Skip();	// Avoid compilation warning. This is the default behavior

	// Create and check DC 
	// Note that In a paint event handler, the application must always create a wxPaintDC object, 
	// even if you do not use it. Otherwise, under MS Windows, refreshing for this and other 
	// windows will go wrong.
	wxPaintDC dc(this);
	if(!dc.IsOk()) return;

	// TODO: does nothing under Linux KDE	
	if (!IsShown()) return;

	// Not implemented for 2.6?
	//if (!IsShownOnScreen()) return;	

	// If current image already shown wait for the next frame.
	// Note this is also necessary to avoid program crash due a void 
	// shared image during the initialization process
	if (m_ImageShowed) return;

	// Exclusive access to shared image
	m_ImageCopyMutex.Enter();
	if (m_AccessingImage) 
	{
		// Worker thread is processing image, don't update
		m_ImageCopyMutex.Leave();
		return;
	}
	else
	{
		// Exclusive access to shared image
		m_AccessingImage= true;	
	}
	m_ImageCopyMutex.Leave();

	// Image size changed
	if (m_nImgWidth!= m_SharedImage.Width() || m_nImgHeight!= m_SharedImage.Height())
	{
		m_nImgWidth= m_SharedImage.Width();
		m_nImgHeight= m_SharedImage.Height();
		if (m_autoResize) {
			SetSize (m_nImgWidth, m_nImgHeight);
			InvalidateBestSize();

			if (GetParent()) {
				wxSizeEvent event;
				wxPostEvent (GetParent(), event);
				//ResizeParentClientArea(m_nImgWidth, m_nImgHeight);
			}
		}		
	}

	// Allocate DisplayImage when needed
	int vpWidth, vpHeight;
	GetSize(&vpWidth, &vpHeight);
	if (vpWidth % 4) vpWidth= vpWidth + 4 - (vpWidth % 4);
	if (vpWidth!= m_DisplayImage.Width() || vpHeight!= m_DisplayImage.Height())
	{
		// Allocate shared image if size changed
		m_DisplayImage.Create (vpWidth, vpHeight, m_SharedImage.Depth(), "RGB", m_SharedImage.Origin(), m_SharedImage.Align());		
	}

	// Scale image
	cvResize( m_SharedImage.ptr(), m_DisplayImage.ptr(), CV_INTER_NN );
	
   	// Working with shared image finished
	m_AccessingImage= false;

	// Draw controls
	if (m_roiControls) {
		m_roiControls->Paint (*m_DisplayImage.ptr(), *this);
	}

	// To wxWidgets
	unsigned char *rawData;
	CvSize roiSize;
	int step = 0;
	cvGetRawData( m_DisplayImage.ptr(), &rawData, &step, &roiSize );
	
	wxImage wxImg= wxImage(vpWidth, vpHeight, rawData, true );
	
	// convert to bitmap to be used by the window to draw
	m_Bitmap= wxBitmap( wxImg );
		
	wxCoord x, y, width, height;
	dc.GetClippingBox( &x, &y, &width, &height );
	dc.DrawBitmap( m_Bitmap, x, y );
	
	m_ImageShowed= true;
}

void CameraPanel::OnMouse ( wxMouseEvent& event )
{
	if (m_roiControls) {
		m_roiControls->MouseEvent (event);		
	}
}

void CameraPanel::OnRecvRefresh( wxCommandEvent& WXUNUSED(event) )
{
	Refresh(false);		
}

}