/////////////////////////////////////////////////////////////////////////////
// Name:        roitype.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Modified by: 
// Created:     11/02/2011
// Copyright:   (C) 2011 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 "mod_camera/roitype.h"
#ifdef WIN32
// TODO: remove the following line when boost gets updated
// See: https://svn.boost.org/trac/boost/ticket/4649
#pragma warning (disable:4127)
#endif
#include <boost/tokenizer.hpp>

#include <boost/program_options.hpp>

#include <iostream>

namespace po = boost::program_options;

#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
//#include <boost/lexical_cast.hpp>

#define MIN_WIDTH_HEIGHT	0.041666666666666666666666666666667f		// 6 píxels over 144
#define MAX_WIDTH_HEIGHT	1.0f 

using namespace spcore;
using namespace boost;
using namespace std;

namespace mod_camera {

// delete registered childs
CTypeROIContents::~CTypeROIContents()
{
	// Destroy children ROI
	ROICollection::iterator i;
	while ((i= m_childROIs.begin())!= m_childROIs.end()) {	
#if !defined(NDEBUG)
		CTypeROI* child= static_cast<CTypeROI*>(*i);
		child->AddRef();
#endif
		// Note that this effectively removes the child
		// and invalidates iterator
		UnregisterChildROI(static_cast<CTypeROI*>(*i));
#if !defined(NDEBUG)
		assert (child->m_pParentROI== NULL);
		child->Release();
#endif		
	}

	m_childROIs.clear();
	
	assert (m_childROIs.empty());

	// Detach itself from parent
	if (m_pParentROI) {
		ROICollection::iterator i= find (m_pParentROI->m_childROIs.begin(), m_pParentROI->m_childROIs.end(), this);
		assert (i!= m_pParentROI->m_childROIs.end());
		m_pParentROI->m_childROIs.erase(i);			
		m_pParentROI= NULL;		
	}
}

bool CTypeROIContents::ParseCommandline (int argc, const char* argv[])
{
	if (!argc) return true;

	bool no_errors= true;
	string errMsg;

	try {
		po::options_description desc("Allowed roi options");
		desc.add_options()
			("help,h", "produce help message")
			("size,s", po::value<vector<float> >()->multitoken(), "roi size. two floats between 0 and 1. (default 1 1)")
			("center,c", po::value<vector<float> >()->multitoken(), "roi size. two floats between 0 and 1. (default 0.5 0.5)")
			("visible,v", po::value<bool>(&m_isVisible)->default_value(true), "is visible")
			("editable,e", po::value<bool>(&m_isEditable)->default_value(true), "is editable")
			("direction,d", po::value<bool>(&m_useDirection)->default_value(false), "use direction arrow")
			("color", po::value<unsigned int>(&m_color)->default_value(0), "color of the rectangle")			
		;

		try {			
			// TODO: parse directly argc, argv instead of building a vector of tokens
			vector< string > tokens;
			for (int i= 0; i< argc; i++) tokens.push_back(argv[i]);

			// Try to process input parameters
			po::variables_map vm;
			po::store(po::command_line_parser(tokens).options(desc).run(), vm);

			// Doing this way ignores multitoken parameters :-(
			//po::store(po::command_line_parser(argc, (char **) argv).options(desc).run(), vm);
			//po::store(po::parse_command_line(argc, (char **) argv, desc), vm);

			po::notify(vm);    

			if (vm.count("help")) no_errors= false;
			else {
				// Get size values
				if (vm.count("size")) {
					if (vm["size"].as< vector<float> >().size()!= 2) 
						throw std::runtime_error("wrong number of size values");

					float width= vm["size"].as<vector<float> >()[0];
					float height= vm["size"].as<vector<float> >()[1];

					if (width< 0 || width> 1.0f || height< 0 || height> 1.0f)
						throw std::runtime_error("wrong values for size");

					SetSize (width,height);
				}

				float x= 0.5f;
				float y= 0.5f;
				// Get centre values
				if (vm.count("center")) {
					if (vm["center"].as<vector<float> >().size()!= 2) 
						throw std::runtime_error("wrong number of center values");

					x= vm["center"].as<vector<float> >()[0];
					y= vm["center"].as<vector<float> >()[1];

					if (x< 0 || x> 1.0f || y< 0 || y> 1.0f)
						throw std::runtime_error("wrong values for center");
				}
				SetCenter (x, y);
			}
		}
		catch(std::exception& e) {
			errMsg= e.what();			
			no_errors= false;
		}
		catch(...) {
			no_errors= false;
		}

		if (!no_errors) {
			// Show help
			ostringstream oss (errMsg);
			oss << desc << "\n";
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_INFO, oss.str().c_str(), "roi");
		}
	}
	catch(...) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "internal error: malformed options description", "roi");
		no_errors= false;
	}

	return no_errors;
}


//
// Working with native coordinates
//

// Find minimum child's P1 coordinates
void CTypeROIContents::FindMinChildP1 (float& x, float& y) const
{
	ROICollection::const_iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		static_cast<CTypeROI*>(*i)->FindMinChildP1Rec (x, y);
	}
}

void CTypeROIContents::FindMinChildP1Rec (float& x, float& y) const
{
	ROICollection::const_iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		static_cast<CTypeROI*>(*i)->FindMinChildP1Rec (x, y);
	}

	if (x> m_x) x= m_x;
	if (y> m_y) y= m_y;
}

// Find miaximum child's P2 coordinates
void CTypeROIContents::FindMaxChildP2 (float& x, float& y) const
{
	ROICollection::const_iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		static_cast<CTypeROI*>(*i)->FindMaxChildP2Rec (x, y);
	}
}

void CTypeROIContents::FindMaxChildP2Rec (float& x, float& y) const
{
	ROICollection::const_iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		static_cast<CTypeROI*>(*i)->FindMaxChildP2Rec (x, y);
	}

	if (x< m_x + m_width) x= m_x + m_width;
	if (y< m_y + m_height) y= m_y + m_height;
}

void CTypeROIContents::SetP1Resize (float x, float y)
{
	float min_p1x, min_p1y, max_p1x, max_p1y;
	float p2x, p2y;

	//CvPoint2D32f p2;

	p2x= m_x + m_width;
	p2y= m_y + m_height;

	if (m_pParentROI) {
		min_p1x= m_pParentROI->m_x;
		min_p1y= m_pParentROI->m_y;
	}
	else {
		 min_p1x= min_p1y= 0.0f;
	}
	max_p1x= p2x - MIN_WIDTH_HEIGHT;
	max_p1y= p2y - MIN_WIDTH_HEIGHT;
	FindMinChildP1 (max_p1x, max_p1y);
	assert (max_p1x>= 0.0f);
	assert (max_p1y>= 0.0f);

	if (x< min_p1x) m_x= min_p1x;
	else if (x> max_p1x) m_x= max_p1x;
	else m_x= x;

	if (y< min_p1y) m_y= min_p1y;
	else if (y> max_p1y) m_y= max_p1y;
	else m_y= y;

	m_width= p2x - m_x;
	m_height= p2y - m_y;
}	
		
void CTypeROIContents::SetP1Move (float x, float y)
{
	float min_p1x, min_p1y, max_p1x, max_p1y;
	float min_p2x, min_p2y;

	// Compute minimum valid p1 coordinates
	if (m_pParentROI) {
		min_p1x= m_pParentROI->m_x;
		min_p1y= m_pParentROI->m_y;
	}
	else {
		 min_p1x= min_p1y= 0.0f;
	}
	min_p2x= min_p1x + m_width;
	min_p2y= min_p1y + m_height;
	FindMaxChildP2 (min_p2x, min_p2y);
	min_p1x= min_p2x - m_width;
	min_p1y= min_p2y - m_height;
	if (min_p1x< 0.0f) min_p1x= 0.0f;
	if (min_p1y< 0.0f) min_p1y= 0.0f;

	// Compute maximum valid p1 coordinates
	if (m_pParentROI) {
		max_p1x= m_pParentROI->m_x + m_pParentROI->m_width - m_width;
		max_p1y= m_pParentROI->m_y + m_pParentROI->m_height - m_height;
	}
	else {
		max_p1x= MAX_WIDTH_HEIGHT - m_width;
		max_p1y= MAX_WIDTH_HEIGHT - m_height;
	}
	FindMinChildP1 (max_p1x, max_p1y);
	assert (max_p1x>= 0.0f);
	assert (max_p1y>= 0.0f);

	// Apply restrictions
	if (x< min_p1x) m_x= min_p1x;
	else if (x> max_p1x) m_x= max_p1x;
	else m_x= x;

	if (y< min_p1y) m_y= min_p1y;
	else if (y> max_p1y) m_y= max_p1y;
	else m_y= y;	
}

void CTypeROIContents::SetP2Resize (float x, float y)
{
	// Setting P2 always resize
	float min_p2x, min_p2y, max_p2x, max_p2y;

	min_p2x= m_x + MIN_WIDTH_HEIGHT;
	min_p2y= m_y + MIN_WIDTH_HEIGHT;
	assert (min_p2x<= MAX_WIDTH_HEIGHT);
	assert (min_p2y<= MAX_WIDTH_HEIGHT);
	FindMaxChildP2 (min_p2x, min_p2y);

	if (m_pParentROI) {
		max_p2x= m_pParentROI->m_x + m_pParentROI->m_width;
		max_p2y= m_pParentROI->m_y + m_pParentROI->m_height;
	}
	else {
		max_p2x= max_p2y= MAX_WIDTH_HEIGHT;
	}

	if (x< min_p2x) m_width= min_p2x - m_x;
	else if (x> max_p2x) m_width= max_p2x - m_x;
	else m_width= x - m_x;

	if (y< min_p2y) m_height= min_p2y - m_y;
	else if (y> max_p2y) m_height= max_p2y - m_y;
	else m_height= y - m_y;	
}

void CTypeROIContents::SetCenter (float x, float y)
{
	SetP1Move (x - (m_width / 2.0f), y - (m_height / 2.0f));
}

void CTypeROIContents::GetCenter (float& x, float& y) const
{
	x= m_x + (m_width / 2.0f);
	y= m_y + (m_height / 2.0f);
}

void CTypeROIContents::SetSize (float width, float height)
{
	SetP2Resize (width + m_x, height + m_y);
}

void CTypeROIContents::GetSize (float& width, float& height) const
{
	width= m_width;
	height= m_height;
}
#if 0
//
// Working with generic integer coordinates
//
inline void CTypeROIContents::Integer2Normalized (const CvSize& size, const int ix, const int iy, float &nx, float &ny)
{
	assert (size.width> 0);
	assert (size.height> 0);
	
	nx= (float) ix / (float) size.width;
	ny= (float) iy / (float) size.height;
}

inline void CTypeROIContents::Normalized2Integer (const CvSize& size, const float nx, const float ny, int &ix, int &iy)
{
	assert (size.width> 0);
	assert (size.height> 0);

	ix= (int) (nx * (float) size.width + 0.5f);
	iy= (int) (ny * (float) size.height + 0.5f);
}

//inline 
void CTypeROIContents::SetP1ResizeInteger (const CvSize& size, const int x, const int y)
{
	float new_x, new_y;

	Integer2Normalized (size, x, y, new_x, new_y);

	SetP1Resize (new_x, new_y);
}

//inline 
void CTypeROIContents::SetP1MoveInteger (const CvSize& size, const int x, const int y)
{
	float new_x, new_y;

	Integer2Normalized (size, x, y, new_x, new_y);

	SetP1Move (new_x, new_y);
}

//inline 
void CTypeROIContents::SetP2ResizeInteger (const CvSize& size, const int x, const int y)
{
	float new_x, new_y;

	Integer2Normalized (size, x, y, new_x, new_y);

	SetP2Resize (new_x, new_y);
}

//inline 
void CTypeROIContents::SetCenterInteger (const CvSize& size, const int x, const int y)
{
	float new_x, new_y;

	Integer2Normalized (size, x, y, new_x, new_y);

	SetCenter (new_x, new_y);
}

void CTypeROIContents::GetCenterInteger (const CvSize& size, int& x, int& y)
{
	float fx, fy;

	GetCenter (fx, fy);

	Normalized2Integer (size, fx, fy, x, y);	
}

//inline 
void CTypeROIContents::SetSizeInteger (const CvSize& size, const int width, const int height)
{
	float new_width, new_height;

	Integer2Normalized (size, width, height, new_width, new_height);

	SetSize (new_width, new_height);
}

//inline 
void CTypeROIContents::GetBoxInteger (const CvSize& size, int& x, int& y, int& width, int& height)
{
	Normalized2Integer (size, m_x, m_y, x, y);
	Normalized2Integer (size, m_width, m_height, width, height);
}

void CTypeROIContents::GetBoxInteger (const CvSize& size, CvRect& box)
{
	GetBoxInteger (size, box.x, box.y, box.width, box.height);
}

void CTypeROIContents::GetP1P2Integer (const CvSize& size, CvPoint& p1, CvPoint& p2)
{
	Normalized2Integer (size, m_x, m_y, p1.x, p1.y);
	Normalized2Integer (size, m_x + m_width, m_y + m_height, p2.x, p2.y);
}

//
// Facilities to work with CIplImage images
//
void CTypeROIContents::SetP1ResizeImg (const CIplImage *pImg, const int x, const int y)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	SetP1ResizeInteger (pImg->GetSize(), x, y);
}

void CTypeROIContents::SetP1MoveImg (const CIplImage *pImg, const int x, const int y)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	SetP1MoveInteger (pImg->GetSize(), x, y);
}

void CTypeROIContents::SetP2ResizeImg (const CIplImage *pImg, const int x, const int y)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	SetP2ResizeInteger (pImg->GetSize(), x, y);	
}

void CTypeROIContents::SetCenterImg (const CIplImage *pImg, const int x, const int y)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);
	
	SetCenterInteger (pImg->GetSize(), x, y);
}

void CTypeROIContents::GetCenterImg (const CIplImage *pImg, int& x, int& y)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	GetCenterInteger (pImg->GetSize(), x, y);	
}

void CTypeROIContents::SetSizeImg (const CIplImage *pImg, const int width, const int height)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	SetSizeInteger (pImg->GetSize(), width, height);
}

void CTypeROIContents::GetBoxImg (const CIplImage *pImg, int& x, int& y, int& width, int& height)
{
	assert (pImg);
	assert (pImg->Origin()== IPL_ORIGIN_TL);

	GetBoxInteger (pImg->GetSize(), x, y, width, height);
}

void CTypeROIContents::GetBoxImg (const CIplImage *pImg, CvRect& box)
{
	GetBoxImg (pImg, box.x, box.y, box.width, box.height);
}
#endif

//
// Registration service
//
bool CTypeROIContents::RegisterChildROI (CTypeROI* roi)
{
	assert (roi);
	assert (roi!= this);
	if (roi== this) return false;
		
	ROICollection::const_iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		if ((*i)== roi) return false;	// Already registered
	}

	// Check that no registered with other parent
	if (roi->m_pParentROI) return false;

	// Add to collection
	roi->AddRef();
	roi->m_pParentROI= static_cast<CTypeROI*>(this);
	m_childROIs.push_back (roi);	

	// Make sure child is inside parent
	if (roi->m_width> m_width) roi->m_width= m_width;
	if (roi->m_height> m_height) roi->m_height= m_height;
	roi->SetP1Move (roi->m_x, roi->m_y);	

	return true;
}

bool CTypeROIContents::UnregisterChildROI (CTypeROI* roi)
{
	assert (roi);

	if (roi== NULL) return false;

	ROICollection::iterator i;

	for(i= m_childROIs.begin(); i != m_childROIs.end(); ++i) {
		if ((*i)== roi) break;	// Found!
	}
	if (i== m_childROIs.end()) return false;	// Not found
	
	assert (static_cast<CTypeROI*>(*i)->m_pParentROI== this);
	static_cast<CTypeROI*>(*i)->m_pParentROI= NULL;
	m_childROIs.erase (i);

	roi->Release();

	return true;
}

bool CTypeROIContents::CopyTo ( CTypeAny& dst, bool recurse ) const {
	assert (dst.GetTypeID()== this->GetTypeID());
	assert (this!= &dst);

	CTypeROI* dst_cast= sptype_static_cast<CTypeROI>(&dst);
	assert (dst_cast);
	
	// copy contents
	dst_cast->m_x= m_x;
	dst_cast->m_y= m_y;
	dst_cast->m_width= m_width;
	dst_cast->m_height= m_height;
	dst_cast->m_useDirection= m_useDirection;
	dst_cast->m_direction= m_direction;
	dst_cast->m_isVisible= m_isVisible;
	dst_cast->m_isEditable= m_isEditable;
	dst_cast->m_color= m_color;
	dst_cast->m_registrationId= m_registrationId;

	if (recurse) {
		// 
		// composite copy of a composite type. update structure and contents recursively 
		//
		ROICollection::const_iterator it_src= this->m_childROIs.begin();
		ROICollection::iterator it_dst= dst_cast->m_childROIs.begin();	

		// While both have children just copy
		while (it_src!= this->m_childROIs.end() && it_dst!= dst_cast->m_childROIs.end()) {
#ifndef NDEBUG
			SmartPtr<CTypeAny> retval=
#endif
			(*it_src)->Clone (*it_dst, recurse);
#ifndef NDEBUG
			assert (retval.get());
#endif
			++it_src;
			++it_dst;		
		}

		if (it_src!= this->m_childROIs.end()) {
			// src has more children than dst, create and clone
			while (it_src!= this->m_childROIs.end()) {
				SmartPtr<CTypeROI> new_child= CTypeROI::CreateInstance();
				if (!new_child.get()) return false;
				new_child.get()->AddRef();	// Goes to non smart ptr'ed vector
				new_child->m_pParentROI= static_cast<CTypeROI*>(dst_cast);
				dst_cast->m_childROIs.push_back (new_child.get());
	#ifndef NDEBUG
				SmartPtr<CTypeAny> retval=
	#endif
				static_cast<CTypeROI*>(*it_src)->Clone (new_child.get(), recurse);
	#ifndef NDEBUG
				assert (retval.get());
	#endif
				++it_src;
			}
		}
		else {
			// dst has remaining children that need to be removed
			while (it_dst!= dst_cast->m_childROIs.end()) {
				static_cast<CTypeROI*>(*it_dst)->m_pParentROI= NULL;
				static_cast<CTypeROI*>(*it_dst)->Release();
				it_dst= dst_cast->m_childROIs.erase(it_dst);
			}
		}
	}
	else {
		// shallow copy, remove destination children if any
		ROICollection::iterator it_dst= dst_cast->m_childROIs.begin();
		while (it_dst!= dst_cast->m_childROIs.end()) {
			static_cast<CTypeROI*>(*it_dst)->m_pParentROI= NULL;
			static_cast<CTypeROI*>(*it_dst)->Release();
			it_dst= dst_cast->m_childROIs.erase(it_dst);
		}
	}	

	return true;
}

// Support composite behaviour but only allow to add ROIs
int CTypeROIContents::AddChild(SmartPtr<CTypeAny> component) {
	CTypeROI * tmp= spcore::sptype_dynamic_cast<CTypeROI>(component.get());
	if (!tmp) return -1;
	return (RegisterChildROI(tmp)? 0 : -1);
}

SmartPtr<IIterator<CTypeAny*> > CTypeROIContents::QueryChildren() const {
	return SmartPtr<IIterator<CTypeAny*> > (new CIteratorVector<CTypeAny*>(m_childROIs), false);
}

}