/////////////////////////////////////////////////////////////////////////////
// File:        mod_collage.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// 		Marcos Alba Soler (marcos dot alba at estudiants dot urv dot cat)
//		Jaume Singla Valls (jaume dot singlavalls at gmail 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 "spcore/basictypes.h"
#include "spcore/pin.h"
#include "spcore/component.h"
#include "spcore/libimpexp.h"

// Do not include <SDL.h> directly. The following
// include provides the SDL library headers
#include "mod_sdl/sdlsurfacetype.h"

#include "XMLImplementation/InterfaceXML.h"
#include "XMLImplementation/Implementation.h"

#include "boost/date_time/posix_time/posix_time_types.hpp"

#include "Pictures/Pictures.h"

#include "Poco/Exception.h"
#include <time.h>

//#include "config_gui.h"

#include <math.h>
#include <stdexcept>

using boost::shared_ptr;

using std::vector;
using std::string;

using namespace spcore;
using namespace mod_sdl;
using namespace XMLImplementation;
using namespace Pictures;
using namespace Kernel;

using namespace Poco;

using namespace boost::posix_time;

namespace mod_collage {

/*
	CollageGraphics component

	Implementation of the graphical part of the Collage activity for SITPLUS

	At the very minimum this component must provide one input integer pin for
	motion activity and one output pin of type CTypeSDLSurface. Additional input
	pins should be provided in order to:
	1) support GUI
	2) support external read/write of properties
*/
class CollageGraphics : public CComponentAdapter {
public:
	CollageGraphics(const char * name, int argc, const char * argv[]) 
	: CComponentAdapter(name, argc, argv)
	, m_filechanged(false)
	, m_deadZone(0)
	, m_responsiveness(1.0f)
	, m_maxOnScreen(10)
	, m_multipliciTimeAnimation(1.0f)
	, m_old_x(0)
	, m_old_y(0)
	, m_indexKernel(0)
	{
		// Create output pin
		
		m_oPinResult= mod_sdl::CTypeSDLSurface::CreateOutputPin("result");
		if (m_oPinResult.get()== NULL)
			// Output pin creation failed. Shouldn't happen but...

			// Note that this exception is handled and properly reported as an error code
			// by the component factory. Remember that exceptions are not allowed to
			// travel across dynamic library boundaries.

			// Also note that, when throwing an exception inside a constructor, the
			// object's destructor is not run.
			throw std::runtime_error("collage_graphics. output pin creation failed.");
		RegisterOutputPin (*m_oPinResult);

		// Create and register input pin
		RegisterInputPin (*SmartPtr<InputPinMotion>(new InputPinMotion("motion", *this), false));
		RegisterInputPin (*SmartPtr<InputPinFile>(new InputPinFile("file", *this), false));
		RegisterInputPin (*SmartPtr<InputPinDeadZone>(new InputPinDeadZone("deadZone", *this), false));
		RegisterInputPin (*SmartPtr<InputPinResponsiveness>(new InputPinResponsiveness("Responsiveness", *this), false));
		RegisterInputPin (*SmartPtr<InputPinMaximum>(new InputPinMaximum("maximum", *this), false));
		RegisterInputPin (*SmartPtr<InputPinNextScene>(new InputPinNextScene("NextScene", *this), false));
		RegisterInputPin (*SmartPtr<InputPinSpeedAnimation>(new InputPinSpeedAnimation("SpeedAnimation", *this), false));
		RegisterInputPin (*SmartPtr<IInputPin>(new InputPinVanish("vanish", *this), false));

		m_xmlfile="";
		m_lastClock= microsec_clock::local_time();
		
		srand ( time(NULL) );

		m_DBImages = DBImages::create();

		m_vanish= CTypeBool::CreateInstance();
	}
	static const char* getTypeName() { return "collage_graphics"; };
	virtual const char* GetTypeName() const { return CollageGraphics::getTypeName(); };
	
	virtual int DoInitialize() { 
		if (m_xmlfile!="") this->loadFile();
		return 0; 
	}

private:
	//
	// Attributes
	//
	bool m_filechanged;
	
	SmartPtr<IOutputPin> m_oPinResult;
	shared_ptr<Activity> m_activity;
	shared_ptr<DBImages> m_DBImages;

	float m_deadZone, m_responsiveness;
	unsigned int m_maxOnScreen; 
	string m_xmlfile;
	ptime m_lastClock;

	float m_multipliciTimeAnimation;

	int m_old_x,m_old_y;

	string m_File, m_Path;

	
	// Smart pointer to the current surface.
	
	vector<shared_ptr<AbstractKernel> > m_listKernel;
	shared_ptr<AbstractKernel> m_kernel;

	//int m_numKernel;
	int m_indexKernel;

	SmartPtr<CTypeBool> m_vanish;

	//
	// Private methods
	//
	~CollageGraphics() {}

	// Initializes/updates the surface we use internally to draw
	int UpdateInternalSurface (const SDL_VideoInfo* vi){
		if (m_old_x!=vi->current_w || m_old_y!=vi->current_h){
			m_old_x=vi->current_w;
			m_old_y=vi->current_h;
			if (m_listKernel.size()>0){
				for (unsigned int i=0;i<m_listKernel.size(); i++){
					m_listKernel[i]->setWindowSize(m_old_x, m_old_y);
				}
			}
		}
		return 0;
	}

	int DoGraphicalStuff (float motion)
	{
		// get video info to know the viewport size
		// note: that viewport size may change during runtime
		const SDL_VideoInfo* vi= SDL_GetVideoInfo();
		if (!vi) {
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "SDL_GetVideoInfo call failed", "mod_collage");
			return -1;
		}

		// update drawing surface when needed
		if (UpdateInternalSurface (vi)!= 0) return -1;

		// Temporary vector of surfaces for show
		vector<shared_ptr<PictureNode> > tmp;
		if (m_kernel!=NULL){ //If we have a valid kernel loaded.
			
			motion= fabs(motion);
			if (motion>m_deadZone){  // If motion is greater as deadZone. (by default configured as 0.01 as the mínimum)
				float increment=(motion-m_deadZone)*m_responsiveness; // calculate the motion that i have to pass.
				tmp=m_kernel->parseMotion(increment);
			} else{ // or pass only a 0
				tmp=m_kernel->parseMotion(0);
			}
			
			// Obtain the time.
			ptime load=microsec_clock::local_time();
			
			// calculate the time transcurred.
			float calculated=((double)(load-m_lastClock).total_milliseconds())/1000.0f;
			
			// save the time as the old time
			m_lastClock=load;
			
			//IF we have background, obtain the image we need as background, passing the time, and it returns the image, in case that we don't have animation it was everytime the same
			if (m_kernel->hasBackground()){
				// We multiply the time, with the variable m_multiplicityTimeAnimation for control the velocity of the animation.
				SmartPtr<CTypeSDLSurface> Background=m_kernel->getBackground(calculated*m_multipliciTimeAnimation);
				m_oPinResult->Send(Background); // If we have a background picture, we send it as the first, for show it.
			}

		}

		// Send the other imatges saved in the temporal vector. 
		for (vector<shared_ptr<PictureNode> >::iterator i=tmp.begin(); i!=tmp.end(); ++i){
			m_oPinResult->Send((*i)->getcType());
		}

		return 0;
	}
	
	int loadFile(){
		if (m_filechanged){
			m_filechanged=false;
			size_t found;
			// break the string of the file path, for obtain the folder and the file name
			found=m_xmlfile.find_last_of("/\\");
			m_File=m_xmlfile.substr(found+1);
			m_Path=m_xmlfile.substr(0,found);
			try {
				LoadXML xml(m_Path,m_File,m_DBImages);
				shared_ptr<LogError> errs = xml.getErrors();
				if(!errs->hasFatalErrors()){ // If we don't have fatal errors. (errors can be recuperables)
					// obtain errors and show it as Errors or warning in the spcore log console.
					vector<shared_ptr<Error> > errors = errs->getErrors();
					if(errors.size()>0){
						shared_ptr<Error> e;
						for(unsigned int i=0;i<errors.size();i++){
							e = errors[i];
							string errs = e->toString();
							getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, errs.c_str(), "mod_collage");
						}
					}
					// obtain the tree configuration of XML
					m_activity = xml.getConfiguration();
					vector<boost::shared_ptr<Module> > list=m_activity->getListModules(); // load modules.
					if (list.size()>0){
						m_listKernel.clear();
						for (unsigned int i=0;i<list.size();i++){ // create kernel for every module
							m_kernel=list[i]->getKernel(list[i]);
							m_listKernel.push_back(m_kernel);
							m_kernel->setWindowSize(m_old_x, m_old_y);
							m_kernel->SetMaximumOnScreen(m_maxOnScreen);
							m_kernel->SetVanish(m_vanish->getValue());
						}
						// set the initial kernel. (every time is the kernel 0) and initialy this.
						m_kernel=m_listKernel[0];						
						m_indexKernel=0;
					}
				}
				else
					getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, errs->getErrors()[0]->toString().c_str(), "mod_collage");
			} catch (Exception e){
				getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(), "mod_collage");
				return -1;
			}
			return 0;
		} else
			return 0;
		
	}

	int setFileName(string file){
		m_xmlfile=file;
		m_filechanged=true;
		if (IsInitialized())
			return this->loadFile();
		else
			return 0;

	}
	string getFileName() const{
#ifdef _WIN32
		return m_Path+"\\"+m_File;
#else
		return m_Path+"/"+m_File;
#endif
	};

	int setResponsiveness(float val){
		m_responsiveness=val;
		return 0;
	}
	float getResponsiveness() const{
		return this->m_responsiveness;
	}

	int setDeadZone(float val){
		m_deadZone=val;
		return 0;
	}
	float getDeadZone() const{
		return this->m_deadZone;
	}

	int setMaximum(unsigned int val){
		m_maxOnScreen=val;
		if (m_kernel.get())	m_kernel->SetMaximumOnScreen(val);
		return 0;
	}
	unsigned int getMaximum() const{
		return this->m_maxOnScreen;
	}

	int nextScene(bool p){
		if (m_listKernel.size()>1){
			// IF boolean is true, we go to the next, if is false, go to the previous kernel.
			// Calculate the new index of the kernel.
			if (p)
				m_indexKernel=(m_indexKernel+1) % m_listKernel.size();
			else
				m_indexKernel=(m_indexKernel==0? m_listKernel.size()-1: m_indexKernel-1);
			// clear the old kernel, and release all memory that it can release. (in mod_collage, it release all images)
			m_kernel->clear();
			// set the new kernel, and initialyze.
			m_kernel=m_listKernel[m_indexKernel];
			m_kernel->SetMaximumOnScreen(m_maxOnScreen);
			m_kernel->SetVanish(m_vanish->getValue());
		}
		return 0;
	}

	int setSpeedAnimation(float val){
		m_multipliciTimeAnimation=val;
		return 0;
	}
	float getSpeedAnimation() const{
		return this->m_multipliciTimeAnimation;
	}

	int setVanish(bool v) {
		m_vanish->setValue(v);
		if (m_kernel.get()) m_kernel->SetVanish(v);
		return 0;
	}

	// Input pin definition for motion
	class InputPinMotion : public CInputPinWriteOnly<CTypeFloat, CollageGraphics> {
	public:
		InputPinMotion (const char * name, CollageGraphics & component) : CInputPinWriteOnly<CTypeFloat, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeFloat & message) {
			return m_component->DoGraphicalStuff(message.getValue());
		}
	};

	class InputPinFile : public CInputPinReadWrite<CTypeString, CollageGraphics> {
	public:
		InputPinFile (const char * name, CollageGraphics & component) : CInputPinReadWrite<CTypeString, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeString & message) {
			return m_component->setFileName(message.get());
		}

		virtual SmartPtr<CTypeString> DoRead() const {
			SmartPtr<CTypeString> ret=CTypeString::CreateInstance();
			ret->set(m_component->getFileName().c_str());
			return ret;
		}
	};

	class InputPinResponsiveness : public CInputPinReadWrite<CTypeFloat, CollageGraphics> {
	public:
		InputPinResponsiveness (const char * name, CollageGraphics & component) : CInputPinReadWrite<CTypeFloat, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeFloat & message) {
			return m_component->setResponsiveness(message.getValue());
		}

		virtual SmartPtr<CTypeFloat> DoRead() const {
			SmartPtr<CTypeFloat> ret=CTypeFloat::CreateInstance();
			ret->setValue(m_component->getResponsiveness());
			return ret;;
		}
	};

	class InputPinDeadZone : public CInputPinReadWrite<CTypeFloat, CollageGraphics> {
	public:
		InputPinDeadZone (const char * name, CollageGraphics & component) : CInputPinReadWrite<CTypeFloat, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeFloat & message) {
			return m_component->setDeadZone(message.getValue());
		}

		virtual SmartPtr<CTypeFloat> DoRead() const {
			SmartPtr<CTypeFloat> ret=CTypeFloat::CreateInstance();
			ret->setValue(m_component->getDeadZone());
			return ret;
		}
	};

	class InputPinMaximum : public CInputPinReadWrite<CTypeInt, CollageGraphics> {
	public:
		InputPinMaximum (const char * name, CollageGraphics & component) 
		: CInputPinReadWrite<CTypeInt, CollageGraphics>(name, component) {}

		virtual int DoSend(const CTypeInt & message) {
			return m_component->setMaximum(message.getValue());
		}

		virtual SmartPtr<CTypeInt> DoRead() const {			
			SmartPtr<CTypeInt> ret= CTypeInt::CreateInstance();
			ret->setValue(m_component->getMaximum());
			return ret;
		}
	};

	class InputPinNextScene : public CInputPinWriteOnly<CTypeBool, CollageGraphics> {
	public:
		InputPinNextScene (const char * name, CollageGraphics & component) : CInputPinWriteOnly<CTypeBool, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeBool & message) {
			return m_component->nextScene(message.getValue());
		}	
	};

	class InputPinSpeedAnimation : public CInputPinReadWrite<CTypeFloat, CollageGraphics> {
	public:
		InputPinSpeedAnimation (const char * name, CollageGraphics & component) : CInputPinReadWrite<CTypeFloat, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeFloat & message) {
			return m_component->setSpeedAnimation(message.getValue());
		}

		virtual SmartPtr<CTypeFloat> DoRead() const {			
			SmartPtr<CTypeFloat> ret=CTypeFloat::CreateInstance();
			ret->setValue(m_component->getSpeedAnimation());
			return ret;
		}
	};


	class InputPinVanish : public CInputPinReadWrite<CTypeBool, CollageGraphics> {
	public:
		InputPinVanish (const char * name, CollageGraphics & component) 
		: CInputPinReadWrite<CTypeBool, CollageGraphics>(name, component) {}
		virtual int DoSend(const CTypeBool & message) {
			return m_component->setVanish(message.getValue());
		}

		virtual SmartPtr<CTypeBool> DoRead() const {			
			return m_component->m_vanish;
		}
	};
};

/*
	Component CollageGraphics factory class
*/
typedef spcore::ComponentFactory<CollageGraphics> CollageGraphicsFactory;


/**
	CollageModule module
*/
class CollageModule : public spcore::CModuleAdapter {
public:
	CollageModule() {
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new CollageGraphicsFactory(), false));
	}
	virtual const char * GetName() const { return "mod_collage"; }
};

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

}

