/*
    Copyright (C) 2011 Marcos Alba Soler (marcos dot alba at estudiants dot urv dot cat)
                       Jaume Singla Valls (jaume dot singlavalls at gmail dot com)

    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 "kernel.h"
#include "iostream"

#include <boost/shared_ptr.hpp>

#include "SDL_rotozoom.h"

using boost::shared_ptr;
using std::vector;

using namespace std;
using namespace XMLImplementation;
using namespace Pictures;

// We include this inside, it is in extern file only for more readable file. reducing the size of this file.
	
#include "delay.h"

namespace Kernel{
	
	AbstractKernel::AbstractKernel(shared_ptr<XMLImplementation::Module> module)
	: m_module(module)
	, m_old_x(0)
	, m_old_y(0)
	, m_elapsedTime(0)
	, m_imageShow(0)
	{
		m_interval=module->getLapseAnimation();
		if (m_interval>-1){
			m_backgroundList=module->getListSrcBg();
		}
	}
	
	AbstractKernel::~AbstractKernel() {	}
	
	void AbstractKernel::setWindowSize(int x, int y){
		m_old_x=x;
		m_old_y=y;
		if (m_interval>-1){

			m_backgroundResizedList.clear();
			for (vector<SmartPtr<mod_sdl::CTypeSDLSurface> >::iterator i=m_backgroundList.begin(); i!=m_backgroundList.end(); i++){
				double scalex=((float) m_old_y)/((*i)->getSurface())->h;
				SDL_Surface *tmp=zoomSurface(((*i)->getSurface()), scalex, scalex,0);
				SmartPtr<mod_sdl::CTypeSDLSurface> ctype=mod_sdl::CTypeSDLSurface::CreateInstance();
				ctype->setSurface(tmp);
				ctype->setX((x-tmp->w)/2);
				ctype->setY((y-tmp->h)/2);
				m_backgroundResizedList.push_back(ctype);
			}
		}
	}
	
	bool AbstractKernel::hasBackground(){
		return m_interval>-1 && m_old_x>0;
	}
	
	SmartPtr<mod_sdl::CTypeSDLSurface> AbstractKernel::getBackground(float time){
		if (m_interval>-1 && m_old_x>0){
			if (m_interval>0){
				// IF the time passed from last clock to now, is superior of two times the interval,
				// I consider is the firs time that we execute this for this module, and reset the options.
				if (time<m_interval*2){
					m_elapsedTime=m_elapsedTime+time;
					if (m_elapsedTime>m_interval){
						
						m_imageShow=(m_imageShow+1) % m_backgroundList.size();
						m_elapsedTime-=m_interval;
					}
				}
			}
			
			return m_backgroundResizedList[m_imageShow];
		} else return mod_sdl::CTypeSDLSurface::CreateInstance();
	}
	
	CollageKernel::CollageKernel(shared_ptr<Module> mod)
	: AbstractKernel(mod)
	, m_vanish(false)
	, m_maxOnScreen(10)
	, m_indexPictures(NULL)
	, m_count(0) 
	{
		// Count number of available pictures
		vector<shared_ptr<Picture> > lp=mod->getListPictures();
		for (vector<shared_ptr<Picture> >::iterator it=lp.begin(); it!=lp.end(); it++){
			m_count+=(*it)->getQuantity();
		}
		m_indexPictures=(int*) malloc(m_count*sizeof(int));
		int k=0;
		int j=0;
		for (vector<shared_ptr<Picture> >::iterator it=lp.begin(); it!=lp.end(); it++){
			for (int i=0; i<(*it)->getQuantity(); i++){
				m_indexPictures[k]=j;
				k++;
			}
			j++;
		}
	}
	
	CollageKernel::~CollageKernel(){
		for (unsigned int i = 0; i < m_objectsInScreen.size(); i++){
			m_objectsInScreen[i]->breakCycle();
		}
		for (unsigned int i = 0; i < m_objectsIncoming.size(); i++){
			m_objectsIncoming[i]->breakCycle();
		}
		for (unsigned int i = 0; i < m_objectsOutgoing.size(); i++){
			m_objectsOutgoing[i]->breakCycle();
		}
		free(m_indexPictures);
	}
	
	vector<shared_ptr<PictureNode> > CollageKernel::parseMotion(float increment) {

		vector<shared_ptr<PictureNode> > ret;
		
		if (m_count== 0) return ret;	// If no pictures available do nothing else

		if (increment > 0){
			// Motion detected	

			// Count number of visible pictures
			unsigned int totalOnScreen= 
				m_objectsInScreen.size() + m_objectsIncoming.size() + m_objectsOutgoing.size();

			// Can we add more pictures?
			if (totalOnScreen< m_maxOnScreen) {

				// Max not reached yet, add another picture				
				int r=rand()%m_count;
				float rx=(rand()%1001)/1000.0f;
				float ry=(rand()%1001)/1000.0f;
				float rz=(rand()%1001)/5000.0f+0.045f; //agafar atribut 'scale' de l'xml
				shared_ptr<PictureNode> tmp=m_module->getPicture(m_indexPictures[r])->getPictureNode(rx,ry,rz);
				tmp->rescale(m_old_x, m_old_y);
				m_objectsIncoming.push_back(tmp);
				++totalOnScreen;
			}
			
			// For incoming objects increase transition and tranfer them to m_ojectsInScreen when needed
			{
				std::vector<boost::shared_ptr<Pictures::PictureNode> >::iterator it= m_objectsIncoming.begin();
				while (it!= m_objectsIncoming.end()) {
					(*it)->increaseTransition(increment);
					if ((*it)->getStatusTransition()>= 1.0f) {
						// Incoming finished move to m_ojectsInScreen
						m_objectsInScreen.push_back(*it);
						it= m_objectsIncoming.erase(it);
					}
					else ++it;
				}
			}
					
			// Need to remove objects?			
			if (totalOnScreen>= m_maxOnScreen) {
				if (m_objectsInScreen.size()) {
					m_objectsOutgoing.push_back (*m_objectsInScreen.begin());
					m_objectsInScreen.erase(m_objectsInScreen.begin());
				}
			}
			
			// For outgoing objects decrease transition and remove when are gone
			{
				std::vector<boost::shared_ptr<Pictures::PictureNode> >::iterator it= m_objectsOutgoing.begin();
				while (it!= m_objectsOutgoing.end()) {
					if ((*it)->decreaseTransition(increment)<= 0) {					
						// Outgoing finished just remove
						(*it)->breakCycle();
						it= m_objectsOutgoing.erase(it);
					}
					else ++it;
				}
			}
		} 
		else if (m_vanish) {
			// Motion not detected, vanish objects
			std::vector<boost::shared_ptr<Pictures::PictureNode> >::iterator it;
			
			// Outgoing
			it= m_objectsOutgoing.begin();
			while (it!= m_objectsOutgoing.end()) {
				if ((*it)->decreaseAlpha()) {
					(*it)->breakCycle();
					it= m_objectsOutgoing.erase(it);
				}
				else ++it;
			}

			// On-screen
			it= m_objectsInScreen.begin();
			while (it!= m_objectsInScreen.end()) {
				if ((*it)->decreaseAlpha()) {
					(*it)->breakCycle();
					it= m_objectsInScreen.erase(it);
				}
				else ++it;
			}

			// Incoming
			it= m_objectsIncoming.begin();
			while (it!= m_objectsIncoming.end()) {
				if ((*it)->decreaseAlpha()) {
					(*it)->breakCycle();
					it= m_objectsIncoming.erase(it);
				}
				else ++it;
			}
		}

		// Join result
		for (unsigned int j=0; j<m_objectsOutgoing.size(); j++)
			ret.push_back(m_objectsOutgoing[j]);
		for (unsigned int j=0; j<m_objectsInScreen.size(); j++)
			ret.push_back(m_objectsInScreen[j]);
		for (unsigned int j=0; j<m_objectsIncoming.size(); j++)
			ret.push_back(m_objectsIncoming[j]);

		return ret;
	};
	
	void CollageKernel::clear(){		
		unsigned int j;
		for (j=0; j<m_objectsOutgoing.size(); j++){
			m_objectsOutgoing[j]->breakCycle();
		}
		m_objectsOutgoing.clear();
		
		for (j=0; j<m_objectsInScreen.size(); j++){
			m_objectsInScreen[j]->breakCycle();
		}
		m_objectsInScreen.clear();
		
		for (j=0; j< m_objectsIncoming.size(); j++){
			m_objectsIncoming[j]->breakCycle();	
		}
		m_objectsIncoming.clear();
	}
	
	void CollageKernel::setWindowSize(int x, int y){
		AbstractKernel::setWindowSize(x,y);
		/*for (unsigned int i = 0; i < m_objectsInScreen.size(); i++){
			m_objectsInScreen[i]->rescale(x, y);
		}*/
		unsigned int j;
		for (j=0; j<m_objectsOutgoing.size(); j++){
			//m_objectsOutgoing[j]->breakCycle();
			m_objectsOutgoing[j]->rescale(x, y);
		}
		
		for (j=0; j<m_objectsInScreen.size(); j++){
			//m_objectsInScreen[j]->breakCycle();
			m_objectsInScreen[j]->rescale(x, y);
		}
		
		for (j=0; j<m_objectsIncoming.size(); j++){
			//m_objectsIncoming[j]->breakCycle();
			m_objectsIncoming[j]->rescale(x, y);
		}
	}
	
	string CollageKernel::getName(){
		return "Collage";
	}
	
	
	CiclicKernel::CiclicKernel(shared_ptr<Module> mod): AbstractKernel(mod){
		m_delayType=mod->getDelayType();
		vector<shared_ptr<Picture> > list=mod->getListPictures();
		for (vector<shared_ptr<Picture> >::iterator it=list.begin(); it!=list.end(); it++){
			int quantity=(*it)->getQuantity();
			float percent=(*it)->getScale();
			float inici=(*it)->getPointX()-(percent*((float) quantity)/2)+percent/2;
			for (int i=0; i<quantity;i++){
				shared_ptr<PictureNode> p=(*it)->getPictureNode(inici+percent*i,(*it)->getPointY(), (*it)->getScale());
				//in this case, the sleep case is the status=0;
				p->setStatus(0);
				p->getTransitionIn()->setStatus(1);
				m_objects.push_back(p);
				//m_objWithDelays.push_back(new DelayNode(p,rand()%quantity));
			}
		}
		if (m_delayType==RAND){
			for (vector<shared_ptr<PictureNode> >::iterator it=m_objects.begin(); it!=m_objects.end(); it++){
				m_objWithDelays.push_back(shared_ptr<AbstractDelayNode>(new RandomDelayNode(*it,m_objects.size())));
			}
		} else if (m_delayType==NODELAY){
			for (vector<shared_ptr<PictureNode> >::iterator it=m_objects.begin(); it!=m_objects.end(); it++){
				m_objWithDelays.push_back(shared_ptr<AbstractDelayNode>(new NoDelayNode(*it)));
			}
		} else if (m_delayType==MOTION){
			float size=1.0f/(float)m_objects.size();
			float begin,end;
			begin=0;
			end=size;
			for (vector<shared_ptr<PictureNode> >::iterator it=m_objects.begin(); it!=m_objects.end(); it++){
				m_objWithDelays.push_back(shared_ptr<AbstractDelayNode>(new MotionDelayNode(*it,begin,end)));
				begin=end;
				end+=size;
			}
		} else {
			for (vector<shared_ptr<PictureNode> >::iterator it=m_objects.begin(); it!=m_objects.end(); it++){
				m_objWithDelays.push_back(shared_ptr<AbstractDelayNode>(new NoDelayNode(*it)));
			}
		}
	}
	
	CiclicKernel::~CiclicKernel(){}
	
	vector<shared_ptr<PictureNode> > CiclicKernel::parseMotion(float motion) {
		if (motion>0){
			for (vector<shared_ptr<AbstractDelayNode> >::iterator it=m_objWithDelays.begin(); it!=m_objWithDelays.end(); it++){
				(*it)->step(motion);
			}
		} else {
			for (vector<shared_ptr<AbstractDelayNode> >::iterator it=m_objWithDelays.begin(); it!=m_objWithDelays.end(); it++){
				(*it)->stop();
			}
		}
		return this->m_objects;
	}
	
	string CiclicKernel::getName(){return "Ciclic";};
	
	void CiclicKernel::clear(){
		for (vector<shared_ptr<AbstractDelayNode> >::iterator it=m_objWithDelays.begin(); it !=m_objWithDelays.end(); it++){
			(*it)->getPicture()->setStatus(0);
			(*it)->getPicture()->getTransitionIn()->setStatus(1);
			(*it)->getPicture()->getTransitionIn()->applyTransition();
		}
	}
	
	void CiclicKernel::setWindowSize(int x, int y){
		AbstractKernel::setWindowSize(x,y);
		for (vector<shared_ptr<PictureNode> >::iterator it=m_objects.begin(); it!=m_objects.end(); it++){
			(*it)->rescale(x,y);
		}
	}
			
	shared_ptr<AbstractKernelFactory> AbstractKernelFactory::getKernelFactory(kernelType kt){
		switch(kt){
			case COLLAGE:
				return shared_ptr<AbstractKernelFactory>(new CollageKernelFactory);
				break;
			case CICLIC:
				return shared_ptr<AbstractKernelFactory>(new CiclicKernelFactory);
				break;
			default:
				// Should never happen, just in case
				assert (false);
				return shared_ptr<AbstractKernelFactory>();
				break;
		}		
	}
	
	shared_ptr<AbstractKernel> CollageKernelFactory::getKernel(shared_ptr<Module> mod) {
		return shared_ptr<AbstractKernel>(new CollageKernel(mod));
	}
	
	shared_ptr<AbstractKernel> CiclicKernelFactory::getKernel(shared_ptr<Module> mod) {
		return shared_ptr<AbstractKernel>(new CiclicKernel(mod));
	}
}