/////////////////////////////////////////////////////////////////////////////
// Name:        wxroicontrol.cpp
// Purpose:		
// 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/>.
/////////////////////////////////////////////////////////////////////////////
#include "wxroicontrol.h"

#include <wx/event.h>
#include <wx/window.h>
#include <math.h>
#include <cv.h>
#include <limits>

#define SELECTION_TOLERANCE 3

using namespace std;

namespace mod_camera {

WXRoiControls::WXRoiControls(ROIModifiedNotification rmn) 
: m_prevCursorLocation(std::numeric_limits<int>::min(), std::numeric_limits<int>::min())
, m_roiModifiedNotification(rmn)
{			
	m_mouseHoverInfo.loc= OVER_NONE;
	m_mouseHoverInfo.roi= NULL;
	m_temporaryROI= CTypeROI::CreateInstance();
}

WXRoiControls::~WXRoiControls()
{	
}


void WXRoiControls::UpdateRootROI (const CTypeROI& roi)
{
	wxCriticalSectionLocker locker(m_csROIAccess);

	// Find if previously registered
	vector <SmartPtr<CTypeROI> >::const_iterator it= m_rootROIs.begin();

	for (; it!= m_rootROIs.end(); ++it) {
		if ((*it)->GetRegistrationId()== roi.GetRegistrationId()) break;	// found
	}

	SmartPtr<CTypeROI> dst_roi;
	if (it== m_rootROIs.end()) {
		// not found, create a new instance
		dst_roi= CTypeROI::CreateInstance();
		m_rootROIs.push_back(dst_roi);
	}
	else {
		dst_roi= *it;
	}

	roi.Clone(dst_roi.get(), true);
}

void WXRoiControls::ClearRootROIs ()
{
	wxCriticalSectionLocker locker(m_csROIAccess);

	m_rootROIs.clear();
}

//
// Mouse events processing code
//
bool WXRoiControls::MouseEvent (wxMouseEvent& event)
{
	assert (wxThread::IsMain());

	// obtain window that sent the event
	wxWindow* win= dynamic_cast<wxWindow *> (event.GetEventObject());
	assert (win);
	if (!win) return false;

	// win size
	wxSize winSize= win->GetClientSize();

	// pointer location
	wxPoint location= event.GetPosition();

	bool processed= true;

	if (!event.LeftIsDown()) {
		m_csROIAccess.Enter();

		// No click. Check if the pointer is hovering some control and update the pointer icon
		vector <SmartPtr<CTypeROI> >::const_iterator i_root= m_rootROIs.begin();
		for (; i_root!= m_rootROIs.end(); ++i_root) {
			WXRoiControls::UpdateMouseHoverInfoRec (*(*i_root), winSize,  location, m_mouseHoverInfo);
			if (m_mouseHoverInfo.loc!= OVER_NONE) break;		// Found
		}

		switch (m_mouseHoverInfo.loc)
		{
		case WXRoiControls::OVER_NONE:
			win->SetCursor (wxNullCursor);
			processed= false;
			break;
		case WXRoiControls::OVER_LEFT_LINE:
		case WXRoiControls::OVER_RIGHT_LINE:
			{
				wxCursor cur (wxCURSOR_SIZEWE);
				win->SetCursor (cur);
			}
			break;
		case WXRoiControls::OVER_BOTTOM_LINE:
		case WXRoiControls::OVER_UPPER_LINE:
			{
				wxCursor cur (wxCURSOR_SIZENS);
				win->SetCursor (cur);
			}
			break;
		case WXRoiControls::OVER_ARROW:
		case WXRoiControls::OVER_UL_CORNER:
		case WXRoiControls::OVER_BR_CORNER:
			{
				wxCursor cur (wxCURSOR_HAND);
				win->SetCursor (cur);
			}
			break;
		default:
			assert (false);
		}

		m_csROIAccess.Leave();
	}
	else {
		m_csROIAccess.Enter();

		// When click
		assert (m_prevCursorLocation.x> std::numeric_limits<int>::min() && m_prevCursorLocation.y> std::numeric_limits<int>::min());
		processed= false;
		vector<SmartPtr<CTypeROI> >::const_iterator i_root;
		if (m_mouseHoverInfo.loc!= OVER_NONE) {
			for (i_root= m_rootROIs.begin(); i_root!= m_rootROIs.end(); ++i_root) {
				processed= ModifyROIRec (*(*i_root), winSize, location, m_prevCursorLocation, m_mouseHoverInfo);
				if (processed) break;
			}			
		}

		// Notify listener
		if (processed && m_roiModifiedNotification) {
			assert (m_mouseHoverInfo.loc!= OVER_NONE && m_mouseHoverInfo.roi);			
			assert (i_root!= m_rootROIs.end() && *i_root);
			(*i_root)->Clone(m_temporaryROI.get(), true);
			m_csROIAccess.Leave();

			m_roiModifiedNotification(m_temporaryROI);
		}
		else
			m_csROIAccess.Leave();
	}

	m_prevCursorLocation= location;

	return processed;	
} 

// Compute both points of the arrow used to set the orientation for a given ROI
void WXRoiControls::GetArrowSegment (const CTypeROI& roi, const wxSize& winSize, wxPoint& p1, wxPoint& p2)
{
	Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x, roi.m_y, p1.x, p1.y);
	Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x + roi.m_width, roi.m_y + roi.m_height, p2.x, p2.y);

	float line_lenght= sqrtf (powf(p2.x - p1.x, 2) + powf(p2.y - p1.y,2)) * 1.1f / 2.0f;
	
	float cx= (p1.x + p2.x) / 2.0f;
	float cy= (p1.y + p2.y) / 2.0f;

	p1.x= static_cast<int>(cx);
	p1.y= static_cast<int>(cy);
	float direction= roi.m_direction;
	p2.x= static_cast<int>(cx + line_lenght * cosf (direction));
	p2.y= static_cast<int>(cy - line_lenght * sinf (direction));
}

void WXRoiControls::UpdateMouseHoverInfoRec (
		const CTypeROI& roi, const wxSize& winSize, 
		const wxPoint& location, MouseHoverInfo& info)
{
	info.loc= OVER_NONE;
	info.roi= NULL;

	// 
	// First look this ROI before checking children
	//
	if (roi.m_isVisible && roi.m_isEditable) {
		wxPoint p1, p2;

		// Pointer is over arrow?
		if (roi.m_useDirection) {	
			GetArrowSegment (roi, winSize, p1, p2);		
			if (location.x>= p2.x - SELECTION_TOLERANCE && location.x<= p2.x + SELECTION_TOLERANCE &&
				location.y>= p2.y - SELECTION_TOLERANCE && location.y<= p2.y + SELECTION_TOLERANCE)	{
				info.loc= OVER_ARROW;
				info.roi= &roi;
				return;
			}
		}

		// obtain ROI coordinates
		Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x, roi.m_y, p1.x, p1.y);
		Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x + roi.m_width, roi.m_y + roi.m_height, p2.x, p2.y);

		if (location.x>= p1.x - SELECTION_TOLERANCE && location.x<= p1.x + SELECTION_TOLERANCE &&
				 location.y>= p1.y - SELECTION_TOLERANCE && location.y<= p1.y + SELECTION_TOLERANCE)
		{
			info.loc= OVER_UL_CORNER;
		}
		else if (location.x>= p2.x - SELECTION_TOLERANCE && location.x<= p2.x + SELECTION_TOLERANCE &&
				 location.y>= p2.y - SELECTION_TOLERANCE && location.y<= p2.y + SELECTION_TOLERANCE)
		{	
			info.loc= OVER_BR_CORNER;
		}
		else
		{
			if (location.y>= p1.y - SELECTION_TOLERANCE && location.y<= p2.y + SELECTION_TOLERANCE)
			{
				// Cursor in Y range, check is cursor over vertical lines
				if (location.x>= p1.x - SELECTION_TOLERANCE && location.x<= p1.x + SELECTION_TOLERANCE) 
					info.loc= OVER_LEFT_LINE;
				else if (location.x>= p2.x - SELECTION_TOLERANCE && location.x<= p2.x + SELECTION_TOLERANCE)
					info.loc= OVER_RIGHT_LINE;
			}
			if (location.x>= p1.x - SELECTION_TOLERANCE && location.x<= p2.x + SELECTION_TOLERANCE)
			{
				// Cursor in X range, check is cursor over horizontal lines
				if (location.y>=  p1.y - SELECTION_TOLERANCE && location.y<=  p1.y + SELECTION_TOLERANCE) 
					info.loc= OVER_UPPER_LINE;
				else if (location.y>= p2.y - SELECTION_TOLERANCE && location.y<= p2.y + SELECTION_TOLERANCE)
					info.loc= OVER_BOTTOM_LINE;
			}
		}	

		// any control below the pointer?
		if (info.loc!= OVER_NONE) {
			// yes, finish lookup
			info.roi= &roi;
			return;
		}
	}

	//
	// process children ROIs
	//
	CTypeROIContents::ROICollection::const_iterator i_children= roi.m_childROIs.begin();
	for (; i_children!= roi.m_childROIs.end(); ++i_children) {
		UpdateMouseHoverInfoRec (*static_cast<CTypeROI*>(*i_children), winSize, location, info);
		if (info.loc!= OVER_NONE) return;	// Found
	}
}

bool WXRoiControls::ModifyROIRec (
		CTypeROI& roi, const wxSize& winSize, const wxPoint& location, 
		const wxPoint& location_prev, const MouseHoverInfo& info)
{
	// 
	// First look this ROI before checking children
	//
	if (roi.m_isVisible && roi.m_isEditable && info.roi== &roi) {
		wxPoint p1, p2;

		if (info.loc== OVER_ARROW) {
			GetArrowSegment (roi, winSize, p1, p2);
			p2.x= location.x;
			p2.y= location.y;
			roi.SetDirection (atan2f(static_cast<float>(p1.y - p2.y), static_cast<float>(p2.x - p1.x)));			
		}
		else {		
			// obtain ROI coordinates
			Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x, roi.m_y, p1.x, p1.y);
			Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x + roi.m_width, roi.m_y + roi.m_height, p2.x, p2.y);
							
			switch (info.loc)
			{
				float norm_x;
				float norm_y;

				case OVER_LEFT_LINE: 
					p1.x+= location.x - location_prev.x;
					Real2Norm(winSize.GetWidth(), winSize.GetHeight(), p1.x, p1.y, norm_x, norm_y);
					roi.SetP1Resize (norm_x, norm_y);
					break;
				case OVER_BOTTOM_LINE: 
					p2.y+= location.y - location_prev.y;
					Real2Norm(winSize.GetWidth(), winSize.GetHeight(), p2.x, p2.y, norm_x, norm_y);
					roi.SetP2Resize (norm_x, norm_y);
					break;
				case OVER_RIGHT_LINE: 
					p2.x+= location.x - location_prev.x;
					Real2Norm(winSize.GetWidth(), winSize.GetHeight(), p2.x, p2.y, norm_x, norm_y);
					roi.SetP2Resize (norm_x, norm_y);
					break;			
				case OVER_UPPER_LINE: 
					p1.y+= location.y - location_prev.y;
					Real2Norm(winSize.GetWidth(), winSize.GetHeight(), p1.x, p1.y, norm_x, norm_y);
					roi.SetP1Resize (norm_x, norm_y);
					break;				
				case OVER_UL_CORNER:
				case OVER_BR_CORNER:
					p1.x+= location.x - location_prev.x;
					p1.y+= location.y - location_prev.y;
					Real2Norm(winSize.GetWidth(), winSize.GetHeight(), p1.x, p1.y, norm_x, norm_y);
					roi.SetP1Move (norm_x, norm_y);					
					break;
				default:
					assert(false);
			}
		}

		return true;
	}

	//
	// process children ROIs
	//
	CTypeROIContents::ROICollection::const_iterator i_children= roi.m_childROIs.begin();
	for (; i_children!= roi.m_childROIs.end(); ++i_children) {
		if (ModifyROIRec (*static_cast<CTypeROI*>(*i_children), winSize, location, location_prev, info)) return true;
	}

	return false;
}

/*
bool CWXNormROI::OnEvtLeftDClick ( wxMouseEvent& WXUNUSED(event) )
{
	//m_pVisibleNormROI->SetCenterWindow (m_pWindow->GetClientSize(), event.GetX(), event.GetY());
	return true;
}*/

/////////////////////////////////////////////////////////////////////////////////77
//
// Painting code
//

void WXRoiControls::Paint (IplImage& img, const wxWindow& win)
{
	assert (wxThread::IsMain());

	wxSize winSize= win.GetClientSize();

	wxCriticalSectionLocker locker(m_csROIAccess);

	vector <SmartPtr<CTypeROI> >::const_iterator i_root= m_rootROIs.begin();
	for (; i_root!= m_rootROIs.end(); ++i_root) {
		PaintRec (*(*i_root), img, winSize, m_mouseHoverInfo);		
	}
}

void WXRoiControls::PaintRec (const CTypeROI& roi, IplImage& img, const wxSize& winSize, const MouseHoverInfo& info)
{
	if (roi.m_isVisible) {
		CvPoint p1, p2;

		// obtain ROI coordinates
		Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x, roi.m_y, p1.x, p1.y);
		Norm2Real (winSize.GetWidth(), winSize.GetHeight(), roi.m_x + roi.m_width, roi.m_y + roi.m_height, p2.x, p2.y);

		int thickness= 1;
		if (roi.m_isEditable && info.roi== &roi && info.loc>= OVER_LEFT_LINE && info.loc<= OVER_BR_CORNER)  
			thickness= 3;

		unsigned int color= roi.GetColor(); 
		CvScalar cv_color= CV_RGB ( color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF );
		// TODO: color
		cvRectangle (&img, p1, p2, cv_color, thickness, 4);

		// Affordances
		if (roi.GetIsEditable()) {
			CvPoint pa, pb;
			pa.x= p1.x - thickness;
			pa.y= p1.y - thickness;
			pb.x= p1.x + thickness;
			pb.y= p1.y + thickness;

			cvRectangle (&img, pa, pb, cv_color, CV_FILLED );

			pa.x= p2.x - thickness;
			pa.y= p2.y - thickness;
			pb.x= p2.x + thickness;
			pb.y= p2.y + thickness;

			cvRectangle (&img, pa, pb, cv_color, CV_FILLED );
		}

		if (roi.m_useDirection)
		{
			wxPoint tmp_p1, tmp_p2;
			GetArrowSegment (roi, winSize, tmp_p1, tmp_p2);
			p1.x= tmp_p1.x; p1.y= tmp_p1.y;
			p2.x= tmp_p2.x; p2.y= tmp_p2.y;
			if (roi.m_isEditable && info.roi== &roi && info.loc== OVER_ARROW)  thickness= 3;
			else thickness= 1;
			cvLine (&img, p1, p2, cv_color, thickness, CV_AA );
			cvCircle(&img, p2, SELECTION_TOLERANCE, cv_color, thickness, CV_AA );
		}
	}

	//
	// process children ROIs
	//
	CTypeROIContents::ROICollection::const_iterator i_children= roi.m_childROIs.begin();
	for (; i_children!= roi.m_childROIs.end(); ++i_children) {
		PaintRec (*static_cast<CTypeROI*>(*i_children), img, winSize, info);
	}
} 

}