/////////////////////////////////////////////////////////////////////////////
// File:        mod_camera.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// 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.h"
#include "wcameraconfiguration.h"
#include "wcamerapanel.h"
#include "mod_camera/roitype.h"
#include "wxroicontrol.h"
#include "spcore/libimpexp.h"

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/recursive_mutex.hpp>

using namespace spcore;

namespace mod_camera {


wxWindow * CameraConfig::GetGUI(wxWindow * parent) {
	return new CCameraConfiguration(parent);
}

typedef SingletonComponentFactory<CameraConfig> CameraConfigFactory;

/* ******************************************************************************
	CameraConfig factory (singleton)
*/
/*
class CameraConfigFactory : public IComponentFactory {
public:
	CameraConfigFactory() {
		m_cameraConfig= SmartPtr<IComponent>(new CameraConfig ("camera_config", 0, NULL), false);
	}

	virtual const char* GetName() const { return CameraConfig::getTypeName(); }
	virtual SmartPtr<IComponent> CreateInstance(const char *, const char *) {
		return m_cameraConfig;
	}
private:
	SmartPtr<IComponent> m_cameraConfig;
};
*/

/*
	camera_grabber component
*/
class CameraGrabber : public spcore::CComponentAdapter, public CameraCaptureListener
{
public:
	static const char * getTypeName() { return "camera_grabber"; }
	virtual const char * GetTypeName() const { return CameraGrabber::getTypeName(); }

	CameraGrabber(const char * name, int argc, const char * argv[])
	: CComponentAdapter(name, argc, argv)
	{
		// Create output pin
		m_oPinResult= CTypeIplImage::CreateOutputPin("image");
		if (m_oPinResult.get()== NULL)
			// Output pin creation failed. Shouldn't happen but...
			throw std::runtime_error("camera_grabber. output pin creation failed.");
		RegisterOutputPin (*m_oPinResult);

		m_cameraConfig= smartptr_dynamic_cast<CameraConfig,IComponent>(
			getSpCoreRuntime()->CreateComponent("camera_config", "camera_config", 0, NULL));

		if (!m_cameraConfig.get()) {
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_FATAL, "cannot create camera_config module", "mod_camera");
			throw std::runtime_error("cannot create camera_config module");
		}
	}
	
private:
	SmartPtr<spcore::IOutputPin> m_oPinResult;	// output pin
	SmartPtr<mod_camera::CameraConfig> m_cameraConfig;

	virtual ~CameraGrabber() {
		Stop();
	}
	virtual void CameraCaptureCallback (SmartPtr<const CTypeIplImage> img) {
		// TODO: send image as the MAIN THREAD
		m_oPinResult->Send(img);
	}

	virtual bool ProvidesExecThread() const { return true; }

	// Starts the component
    // \return 0 if successfully started, -1 otherwise
	virtual int DoStart() {
		m_cameraConfig->RegisterListener (*this);
		return 0;
	}

    //Stops the component.
	virtual void DoStop() {
		m_cameraConfig->UnregisterListener (*this);
	}
};

// camera_grabber component factory
typedef ComponentFactory<CameraGrabber> CameraGrabberFactory;

/*
	camera_viewer component
*/

class CameraViewer : public spcore::CComponentAdapter
{
public:
	CameraViewer(const char * name, int argc, const char * argv[])
	: CComponentAdapter(name, argc, argv)
	, m_viewerPanel(NULL) {
		m_oPinResult= CTypeROI::CreateOutputPin("roi");
		if (m_oPinResult.get()== NULL)
			// Output pin creation failed. Shouldn't happen but...
			throw std::runtime_error("camera_viewer. output pin creation failed.");
		RegisterOutputPin (*m_oPinResult);

		m_roiControls=  boost::shared_ptr<WXRoiControls>(
			new WXRoiControls(boost::bind(&CameraViewer::NotifyROIModification, this, _1)));
		if (m_roiControls.get()== NULL) {
			// Output pin creation failed. Shouldn't happen but...
			throw std::runtime_error("camera_viewer. WXRoiControls creation failed.");
		}

		RegisterInputPin (*SmartPtr<InputPinImage>(new InputPinImage(*this), false));
		RegisterInputPin (*SmartPtr<InputPinROI>(new InputPinROI(*this), false));
	}

	static const char * getTypeName() { return "camera_viewer"; }
	virtual const char * GetTypeName() const { return CameraViewer::getTypeName(); }

	virtual wxWindow* GetGUI(wxWindow * parent) {
		assert (wxIsMainThread());
		boost::recursive_mutex::scoped_lock lock(m_mutex);
		if (m_viewerPanel) {
			// Already created change parent ????
			assert (false);
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "camera panel alredy open", "mod_camera");
			return NULL;
		}
		else {
			m_viewerPanel= new CameraPanel(
				CameraPanel::CleanupFunctor(boost::bind (&CameraViewer::OnPanelDestroyed, this)),
				m_roiControls.get()
			);
			//m_viewerPanel->SetReziseParent ( true );
			m_viewerPanel->Create (parent);
		}

		return m_viewerPanel;
	}
private:
	boost::shared_ptr<WXRoiControls> m_roiControls;
	CameraPanel *m_viewerPanel;
	SmartPtr<spcore::IOutputPin> m_oPinResult;
	boost::recursive_mutex m_mutex;

	void NotifyROIModification (SmartPtr<const CTypeROI> roi)
	{
		m_oPinResult->Send(roi);
	}

	virtual ~CameraViewer() {
		assert (wxIsMainThread());
		boost::recursive_mutex::scoped_lock lock(m_mutex);
		if (m_viewerPanel) {
			m_viewerPanel->RemoveCleanupFunctor();
			m_viewerPanel->Close();
			m_viewerPanel= NULL;
		}
	}

	void OnPanelDestroyed() {
		assert (wxIsMainThread());
		boost::recursive_mutex::scoped_lock lock(m_mutex);
		m_viewerPanel= NULL;
	}
	void OnImage(const CTypeIplImage & img) {
		if (wxIsMainThread()) {
			if (m_viewerPanel) m_viewerPanel->DrawCam(img.getImage());
		}
		else {
			boost::recursive_mutex::scoped_lock lock(m_mutex);
			if (m_viewerPanel) m_viewerPanel->DrawCam(img.getImage());
		}
	}

	// Write-only pin to send the image
	class InputPinImage : public spcore::CInputPinWriteOnly<CTypeIplImage, CameraViewer> {
	public:
		InputPinImage (CameraViewer & component)
		: CInputPinWriteOnly<CTypeIplImage, CameraViewer>("image", component) { }

		virtual int DoSend(const CTypeIplImage & img) {
			m_component->OnImage(img);
			return 0;
		}
	};

	// Write-only pin to send a ROI
	class InputPinROI : public spcore::CInputPinWriteOnly<CTypeROI, CameraViewer> {
	public:
		InputPinROI (CameraViewer & component)
		: CInputPinWriteOnly<CTypeROI, CameraViewer>("roi", component) { }

		virtual int DoSend(const CTypeROI & roi) {
			m_component->m_roiControls->UpdateRootROI (roi);
			return 0;
		}
	};
};

// camera_viewer component factory
typedef ComponentFactory<CameraViewer> CameraViewerFactory;

/* ******************************************************************************
	roi_storage component

	Input pins:
		roi (roi): internally stores a copy of the ROI object and sends the
			result through the output pin

		roi_same_id:  internally stores a copy of the ROI object if has the same ID 
			and sends the result through the output pin

		centre (compsite of 2 floats):
			centres the roi around the given point and sends the
			result through the output pin
						
	Output pins:
		roi (roi): resulting roi

	Command line flags:
		-s x y		roi size. two floats between 0 and 1. (default 1 1)
		-c x y		roi centre. two floats between 0 and 1 (default 0,5 0,5)
		-v [0|1]	is visible (default 1)
		-e [0|1]	is editable (default 1)
		-d [0|1]	use direction arrow (default 0)
		-col int	24bit integer with the color code (default black)
*/

class RoiStorage : public CComponentAdapter
{
public:
	static const char * getTypeName() { return "roi_storage"; }
	virtual const char * GetTypeName() const { return RoiStorage::getTypeName(); }

	RoiStorage(const char * name, int argc, const char * argv[])
	: CComponentAdapter(name, argc, argv)
	{
		m_oPinResult= CTypeROI::CreateOutputPin("roi");
		if (m_oPinResult.get()== NULL)
			// Output pin creation failed. Shouldn't happen but...
			throw std::runtime_error("roi_storage. output pin creation failed.");
		RegisterOutputPin (*m_oPinResult);		
		RegisterInputPin (*SmartPtr<InputPinROI>(new InputPinROI(*this), false));
		RegisterInputPin (*SmartPtr<InputPinROISameID>(new InputPinROISameID(*this), false));
		RegisterInputPin (*SmartPtr<InputPinCentre>(new InputPinCentre(*this), false));

		m_ROI= CTypeROI::CreateInstance();
		if (m_ROI.get()== NULL)
			throw std::runtime_error("roi_storage. cannot create internal instance.");

		
		// We create an ID from the pointer, just to obtain a unique value
		m_Id= (unsigned int) ((unsigned long) m_ROI.get());
		m_ROI->SetRegistrationId (m_Id);

		// parse command line
		if (!m_ROI->ParseCommandline (argc, argv))
			throw std::runtime_error("error parsing options");
	}

	virtual int DoInitialize() {
		return m_oPinResult->Send (m_ROI);
	}

private:
	// TODO: take into account concurrency
	SmartPtr<CTypeROI> m_ROI;
	SmartPtr<spcore::IOutputPin> m_oPinResult;
	unsigned int m_Id;

	// Read/Write pin to send/read the ROI
	class InputPinROI : public CInputPinReadWrite<CTypeROI, RoiStorage> {
	public:
		InputPinROI (RoiStorage & component) : CInputPinReadWrite<CTypeROI, RoiStorage>("roi", component) {}
		virtual SmartPtr<CTypeROI> DoRead() const {
			SmartPtr<CTypeROI> result= CTypeROI::CreateInstance();
			m_component->m_ROI->Clone(NULL, true);
			return result;
		}
		virtual int DoSend(const CTypeROI &src) {
			src.Clone(m_component->m_ROI.get(), true);
			// restore ID
			m_component->m_ROI->SetRegistrationId (m_component->m_Id);
			return m_component->m_oPinResult->Send (m_component->m_ROI);
		}
	};

	// Write pin to send ROIs. Only these with the same ID are taken into account
	class InputPinROISameID : public CInputPinWriteOnly<CTypeROI, RoiStorage> {
	public:
		InputPinROISameID (RoiStorage & component) : CInputPinWriteOnly<CTypeROI, RoiStorage>("roi_same_id", component) {}		
		virtual int DoSend(const CTypeROI &src) {
			if (m_component->m_Id== src.GetRegistrationId()) {
				src.Clone(m_component->m_ROI.get(), true);
				return m_component->m_oPinResult->Send (m_component->m_ROI);
			}
			return 0;
		}
	};

	// Allows to centre the ROI around a given point. Expects a composite type with 2 normalized float values (x,y)
	class InputPinCentre : public CInputPinWriteOnly<CTypeAny, RoiStorage> {
	public:
		InputPinCentre (RoiStorage & component) : CInputPinWriteOnly<CTypeAny, RoiStorage>("centre", component) {}
		virtual int DoSend(const CTypeAny &centre) {
			SmartPtr<IIterator<CTypeAny *> > it= centre.QueryChildren();

			SmartPtr<CTypeFloat> x=
				spcore::sptype_dynamic_cast<spcore::CTypeFloat>(SmartPtr<CTypeAny>(it->CurrentItem()));
			if (!x.get()) goto malformed_msg;
			it->Next();
			if (it->IsDone()) goto malformed_msg;

			{
				SmartPtr<CTypeFloat> y=
					spcore::sptype_dynamic_cast<spcore::CTypeFloat>(SmartPtr<CTypeAny>(it->CurrentItem()));
				if (!y.get()) goto malformed_msg;

				// Sanity check
				if (x->getValue()< 0.0f || x->getValue()> 1.0f ||
					y->getValue()< 0.0f || y->getValue()> 1.0f) goto wrong_values;

				m_component->m_ROI->SetCenter(x->getValue(), y->getValue());
			}
			return m_component->m_oPinResult->Send (m_component->m_ROI);
		malformed_msg:
			spcore::getSpCoreRuntime()->LogMessage
					(spcore::ICoreRuntime::LOG_WARNING, "setting ROI centre. request ignored. invalid message", "mod_camera");
			return -1;
		wrong_values:
			spcore::getSpCoreRuntime()->LogMessage
					(spcore::ICoreRuntime::LOG_WARNING, "setting ROI centre. request ignored. invalid value", "mod_camera");
			return -1;
		}
	};
};

// camera_viewer component factory
typedef ComponentFactory<RoiStorage> RoiStorageFactory;


/* ******************************************************************************
	TODO: roi_decomposer (can be generalized for composite types)

	Inputs:
	- "roi", CTypeROI

	Outputs:
	- "parent", CTypeROI
	- "1st_son", CTypeROI
	- "right_brother", CTypeROI
****************************************************************************** */

/* ******************************************************************************
	TODO: roi_composer (can be generalized for composite types)

	Inputs:
	- "parent", CTypeROI -> stores parent internally
	- "child", CTypeROI  -> adds son and sends result

	Outputs:
	- "roi", CTypeROI
****************************************************************************** */



/* ******************************************************************************
	Type CTypeIplImage factory class
*/
typedef spcore::SimpleTypeFactory<CTypeIplImage> CTypeIplImageFactory;

/* ******************************************************************************
	Type CTypeROI factory class
*/
typedef spcore::SimpleTypeFactory<CTypeROI> CTypeROIFactory;


/* ******************************************************************************
	sdl_base module
*/
class CCameraModule : public spcore::CModuleAdapter {
public:
	CCameraModule() {
		RegisterTypeFactory(SmartPtr<spcore::ITypeFactory>(new CTypeIplImageFactory(), false));
		RegisterTypeFactory(SmartPtr<spcore::ITypeFactory>(new CTypeROIFactory(), false));
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new CameraConfigFactory(), false));
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new CameraGrabberFactory(), false));
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new CameraViewerFactory(), false));
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new RoiStorageFactory(), false));
	}
	virtual const char * GetName() const { return "mod_camera"; }
};

static spcore::IModule* g_module= NULL;
SPEXPORT_FUNCTION spcore::IModule* module_create_instance()
{
	if (g_module== NULL) g_module= new CCameraModule();
	return g_module;
}

};
