#include "render.h"
#include <cmath>
#include <QList>
#include <algorithm>
#include <QByteArray>
#include <QPixmap>
#include <QApplication>
#include <QImage>

#include <typeinfo>

#include "comic_db.h"
#include "yacreader_global.h"

template<class T>
inline const T& kClamp( const T& x, const T& low, const T& high )
{
	if ( x < low )       return low;
	else if ( high < x ) return high;
	else                 return x;
}

inline
int changeBrightness( int value, int brightness )
	{
	return kClamp( value + brightness * 255 / 100, 0, 255 );
	}

inline
int changeContrast( int value, int contrast )
	{
	return kClamp((( value - 127 ) * contrast / 100 ) + 127, 0, 255 );
	}

inline
int changeGamma( int value, int gamma )
	{
	return kClamp( int( pow( value / 255.0, 100.0 / gamma ) * 255 ), 0, 255 );
	}

inline
int changeUsingTable( int value, const int table[] )
	{
	return table[ value ];
	}

template< int operation( int, int ) >
static
QImage changeImage( const QImage& image, int value )
	{
	QImage im = image;
	im.detach();
	if( im.colorCount() == 0 ) /* truecolor */
		{
		if( im.format() != QImage::Format_RGB32 ) /* just in case */
			im = im.convertToFormat( QImage::Format_RGB32 );
		int table[ 256 ];
		for( int i = 0;
			 i < 256;
			 ++i )
			table[ i ] = operation( i, value );
		if( im.hasAlphaChannel() )
			{
			for( int y = 0;
				 y < im.height();
				 ++y )
				{
				QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y ));
				for( int x = 0;
					 x < im.width();
					 ++x )
					line[ x ] = qRgba( changeUsingTable( qRed( line[ x ] ), table ),
						changeUsingTable( qGreen( line[ x ] ), table ),
						changeUsingTable( qBlue( line[ x ] ), table ),
						changeUsingTable( qAlpha( line[ x ] ), table ));
				}
			}
		else
			{
			for( int y = 0;
				 y < im.height();
				 ++y )
				{
				QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y ));
				for( int x = 0;
					 x < im.width();
					 ++x )
					line[ x ] = qRgb( changeUsingTable( qRed( line[ x ] ), table ),
						changeUsingTable( qGreen( line[ x ] ), table ),
						changeUsingTable( qBlue( line[ x ] ), table ));
				}
			}
		}
	else
		{
		QVector<QRgb> colors = im.colorTable();
		for( int i = 0;
			 i < im.colorCount();
			 ++i )
			colors[ i ] = qRgb( operation( qRed( colors[ i ] ), value ),
				operation( qGreen( colors[ i ] ), value ),
				operation( qBlue( colors[ i ] ), value ));
		}
	return im;
	}


// brightness is multiplied by 100 in order to avoid floating point numbers
QImage changeBrightness( const QImage& image, int brightness )
	{
	if( brightness == 0 ) // no change
		return image;
	return changeImage< changeBrightness >( image, brightness );
	}


// contrast is multiplied by 100 in order to avoid floating point numbers
QImage changeContrast( const QImage& image, int contrast )
	{
	if( contrast == 100 ) // no change
		return image;
	return changeImage< changeContrast >( image, contrast );
	}

// gamma is multiplied by 100 in order to avoid floating point numbers
QImage changeGamma( const QImage& image, int gamma )
	{
	if( gamma == 100 ) // no change
		return image;
	return changeImage< changeGamma >( image, gamma );
	}



//-----------------------------------------------------------------------------
// MeanNoiseReductionFilter
//-----------------------------------------------------------------------------

MeanNoiseReductionFilter::MeanNoiseReductionFilter(enum NeighborghoodSize ns)
:neighborghoodSize(ns)
{

}

QImage MeanNoiseReductionFilter::setFilter(const QImage & image)
{
	int width = image.width();
	int height = image.height();
	QImage result(width,height,image.format());
	int filterSize = sqrt((float)neighborghoodSize);
	int bound = filterSize/2;
	QRgb pix;
	int r,g,b;
	for(int j=bound;j<height-bound;j++){
		for(int i=bound;i<width-bound;i++){
			r=g=b=0;
			for(int y=j-bound;y<=j+bound;y++)
			{
				for(int x=i-bound;x<=i+bound;x++)
				{
					pix = image.pixel(x,y);
					r += qRed(pix);
					g += qGreen(pix);
					b += qBlue(pix);
				}
			}
			result.setPixel(i,j,QColor(r/neighborghoodSize,g/neighborghoodSize,b/neighborghoodSize).rgb());
			//qDebug((QString::number(redChannel.at(4))+" "+QString::number(greenChannel.at(4))+" "+QString::number(blueChannel.at(4))).toAscii());
			//qDebug((QString::number(redChannel.size())+" "+QString::number(greenChannel.size())+" "+QString::number(blueChannel.size())).toAscii());
		}
	}
	return result;
}

//-----------------------------------------------------------------------------
// MedianNoiseReductionFilter
//-----------------------------------------------------------------------------

MedianNoiseReductionFilter::MedianNoiseReductionFilter(enum NeighborghoodSize ns)
:neighborghoodSize(ns)
{

}

QImage MedianNoiseReductionFilter::setFilter(const QImage & image)
{
	int width = image.width();
	int height = image.height();
	QImage result(width,height,image.format());
	int filterSize = sqrt((float)neighborghoodSize);
	int bound = filterSize/2;
	QRgb pix;
	QList<int> redChannel;
	QList<int> greenChannel;
	QList<int> blueChannel;
	for(int j=bound;j<height-bound;j++){
		for(int i=bound;i<width-bound;i++){
			redChannel.clear();
			greenChannel.clear();
			blueChannel.clear();
			for(int y=j-bound;y<=j+bound;y++)
			{
				for(int x=i-bound;x<=i+bound;x++)
				{
					pix = image.pixel(x,y);
					redChannel.push_back(qRed(pix));
					greenChannel.push_back(qGreen(pix));
					blueChannel.push_back(qBlue(pix));
				}
			}

			std::sort(redChannel.begin(),redChannel.end());
			std::sort(greenChannel.begin(),greenChannel.end());
			std::sort(blueChannel.begin(),blueChannel.end());
			result.setPixel(i,j,QColor(redChannel.at(4),greenChannel.at(4),blueChannel.at(4)).rgb());
		}
	}
	return result;
}


//-----------------------------------------------------------------------------
// BrightnessFilter
//-----------------------------------------------------------------------------
BrightnessFilter::BrightnessFilter(int l)
	:ImageFilter()
{
	level = l;
}

QImage BrightnessFilter::setFilter(const QImage & image)
{
	/*int width = image.width();
	int height = image.height();
	QImage result(width,height,image.format());

	for(int j=0;j<height;j++){
		for(int i=0;i<width;i++){
			result.setPixel(i,j,QColor(image.pixel(i,j)).light(level).rgb());
		}
	}
	return result;*/
	if(level ==-1)
	{
		QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
		return changeBrightness(image,settings.value(BRIGHTNESS,0).toInt());
	}
	else
	{
		return changeBrightness(image,level);
	}
}

//-----------------------------------------------------------------------------
// ContrastFilter
//-----------------------------------------------------------------------------
ContrastFilter::ContrastFilter(int l)
	:ImageFilter()
{
	level = l;
}

QImage ContrastFilter::setFilter(const QImage & image)
{
	/*int width = image.width();
	int height = image.height();
	QImage result(width,height,image.format());

	int min,max,v;
	min = 0;
	max = 255;
	int sum = 0;

	QVector<int> hist(256,0);

	for(int j=0;j<height;j++){
		for(int i=0;i<width;i++){
			hist[QColor(image.pixel(i,j)).lightness()]++;
			sum++;
		}
	}

	long double count = sum;
	long double new_count = 0.0;
	long double percentage,next_percentage;

	for (int i = 0; i < 254; i++)
	{
		new_count += hist[i];
		percentage = new_count/count;
		next_percentage = (new_count+hist[i+1])/count;
		if(fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
		{
			min = i+1;
			break;
		}
	}

	new_count=0.0;
	for (int i = 255; i > 1; i--)
	{
		new_count += hist[i];
		percentage = new_count/count;
		next_percentage = (new_count+hist[i-1])/count;
		if(fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
		{
			max = i-1;
			break;
		}
	}
	QColor c;
	int range = max - min;
	for(int j=0;j<height;j++){
		for(int i=0;i<width;i++){
			c = QColor(image.pixel(i,j));
			result.setPixel(i,j,c.light(((c.lightness()-min)/range*1.0)*255).rgb());
		}
	}

	return result;*/
	if(level ==-1)
	{
		QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
		return changeContrast(image,settings.value(CONTRAST,100).toInt());
	}
	else
	{
		return changeContrast(image,level);
	}
}
//-----------------------------------------------------------------------------
// ContrastFilter
//-----------------------------------------------------------------------------
GammaFilter::GammaFilter(int l)
	:ImageFilter()
{
	level = l;
}

QImage GammaFilter::setFilter(const QImage & image)
{
	if(level ==-1)
	{
		QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
		return changeGamma(image,settings.value(GAMMA,100).toInt());
	}
	else
	{
		return changeGamma(image,level);
	}
}

//-----------------------------------------------------------------------------
// PageRender
//-----------------------------------------------------------------------------
PageRender::PageRender()
:QThread()
{

}
PageRender::PageRender(Render * r,int np, const QByteArray & rd, QImage * p,unsigned int d, QVector<ImageFilter *> f)
:QThread(),
render(r),
numPage(np),
data(rd),
page(p),
degrees(d),
filters(f)
{
}

void PageRender::run()
{
	QMutexLocker locker(&(render->mutex));
	
	QImage img;
	img.loadFromData(data);
	if(degrees > 0)
	{
		QMatrix m;
		m.rotate(degrees);
		img = img.transformed(m,Qt::SmoothTransformation);
	}
	for(int i=0;i<filters.size();i++)
	{
		img = filters[i]->setFilter(img);
	}

	
	*page = img;

	emit pageReady(numPage);
}

//-----------------------------------------------------------------------------
// DoublePageRender
//-----------------------------------------------------------------------------

DoublePageRender::DoublePageRender(Render * r, int np, const QByteArray & rd, const QByteArray & rd2, QImage * p,unsigned int d, QVector<ImageFilter *> f)
:PageRender(),
render(r),
numPage(np),
data(rd),
data2(rd2),
page(p),
degrees(d),
filters(f)
{

}

void DoublePageRender::run()
{
	//QImage result;
	QMutexLocker locker(&(render->mutex));
	QImage img, img2;
	if(!data.isEmpty())
		img.loadFromData(data);
	if(!data2.isEmpty())
		img2.loadFromData(data2);
	/*if(img.isNull())
	img = QPixmap(img2.width(),img2.height());
	if(img2.isNull())
	img2 = QPixmap(img.width(),img.height());*/

	if(img.isNull() && !img2.isNull())
	{
		img = img2;
		img2 = QImage();
	}
		

	int totalWidth,totalHeight;
	//x = img.width()+img2.width();
	totalHeight = qMax(img.height(),img2.height());

	//widths fiting the normalized height
	int width1, width2;

	//altura normalizada
	if(!img2.isNull())
	{
		if(img.height()!=totalHeight)
			totalWidth = (width1 = ((img.width() * totalHeight) / img.height())) +  (width2 = img2.width());
		else
			totalWidth = (width1 = img.width()) + (width2 = ((img2.width() * totalHeight) / img2.height()));
	}
	else
		totalWidth = width1 = img.width();


	
	
	QImage auxImg(totalWidth,totalHeight,QImage::Format_RGB32);
	QPainter painter(&auxImg);
	painter.drawImage(QRect(0,0,width1,totalHeight),img);
	if(!img2.isNull())
		painter.drawImage(QRect(width1,0,width2,totalHeight),img2);
	painter.end();

	if(degrees > 0)
	{
		QMatrix m;
		m.rotate(degrees);
		auxImg = auxImg.transformed(m,Qt::SmoothTransformation);
	}
	for(int i=0;i<filters.size();i++)
	{
		auxImg = filters[i]->setFilter(auxImg);
	}
	
	*page = auxImg;

	emit pageReady(numPage);
}

//-----------------------------------------------------------------------------
// Render
//-----------------------------------------------------------------------------

Render::Render()
:currentIndex(0),doublePage(false),comic(0),loadedComic(false),imageRotation(0),numLeftPages(2),numRightPages(2)
{
	int size = numLeftPages+numRightPages+1;
	currentPageBufferedIndex = numLeftPages;
	for(int i = 0; i<size; i++)
	{
		buffer.push_back(new QImage());
		pageRenders.push_back(0);
	}

	filters.push_back(new BrightnessFilter());
	filters.push_back(new ContrastFilter());
	filters.push_back(new GammaFilter());
}

Render::~Render()
{
	if(comic!=0)
	{
		comic->moveToThread(QApplication::instance()->thread());
		comic->deleteLater();
	}

	foreach(ImageFilter * filter, filters)
		delete filter;

	foreach(PageRender * pr,pageRenders)
		if(pr !=0)
		{
			if(pr->wait())
				delete pr;
		}
}
//Este m�todo se encarga de forzar el renderizado de las p�ginas.
//Actualiza el buffer seg�n es necesario.
//si la pagina actual no est� renderizada, se lanza un hilo que la renderize (double or single page mode) y se emite una se�al que indica que se est� renderizando.
void Render::render()
{
	updateBuffer();
	if(buffer[currentPageBufferedIndex]->isNull())
	{
		if(pagesReady.size()>0)
		{
			if(doublePage)
			{
				if(pagesReady[currentIndex] && pagesReady[qMin(currentIndex+1,(int)comic->numPages()-1)])
					if(currentIndex+1 > (int)comic->numPages()-1)
						pageRenders[currentPageBufferedIndex] = new DoublePageRender(this,currentIndex,comic->getRawData()->at(currentIndex),QByteArray(),buffer[currentPageBufferedIndex],imageRotation,filters);
					else
						pageRenders[currentPageBufferedIndex] = new DoublePageRender(this,currentIndex,comic->getRawData()->at(currentIndex),comic->getRawData()->at(currentIndex+1),buffer[currentPageBufferedIndex],imageRotation,filters);
				else
					//las p�ginas no est�n listas, y se est�n cargando en el c�mic
					emit processingPage(); //para evitar confusiones esta se�al deber�a llamarse de otra forma
			}
			else
				if(pagesReady[currentIndex])
					pageRenders[currentPageBufferedIndex] = new PageRender(this,currentIndex,comic->getRawData()->at(currentIndex),buffer[currentPageBufferedIndex],imageRotation,filters);
				else
					//las p�ginas no est�n listas, y se est�n cargando en el c�mic
					emit processingPage(); //para evitar confusiones esta se�al deber�a llamarse de otra forma

			//si se ha creado un hilo para renderizar la p�gina actual, se arranca
			if(pageRenders[currentPageBufferedIndex]!=0)
			{
				//se conecta la se�al pageReady del hilo, con el SLOT prepareAvailablePage
				connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
				//se emite la se�al de procesando, debido a que los hilos se arrancan aqu�
				if(doublePage || filters.size()>0)
					emit processingPage();
				pageRenders[currentPageBufferedIndex]->start();
				pageRenders[currentPageBufferedIndex]->setPriority(QThread::TimeCriticalPriority);
			}
			else
				//en qu� caso ser�a necesario hacer esto??? //TODO: IMPORTANTE, puede que no sea necesario.
				emit processingPage();
		}
		else
			//no hay ninguna p�gina lista para ser renderizada, es necesario esperar.
			emit processingPage();
	}
	else
		// la p�gina actual est� lista
		emit currentPageReady();

	//se renderizan las p�ginas restantes para llenar el buffer.
	if(doublePage)
		fillBufferDoublePage();
	else
		fillBuffer();
}

QPixmap * Render::getCurrentPage()
{
	QPixmap * page = new QPixmap();
	*page = page->fromImage(*buffer[currentPageBufferedIndex]);
	return page;
}

void Render::setRotation(int degrees)
{
	Q_UNUSED(degrees)
}

void Render::setComic(Comic * c)
{
	if(comic !=0)
	{
		comic->moveToThread(QApplication::instance()->thread());
		comic->disconnect();
		comic->deleteLater();
	}
	comic = c;
}

void Render::prepareAvailablePage(int page)
{
	if(currentIndex == page)
		emit currentPageReady();
}

void Render::update()
{
	render();
}
//-----------------------------------------------------------------------------
// Comic interface
//-----------------------------------------------------------------------------
void Render::load(const QString & path, int atPage)
{
	createComic(path);
	loadComic(path,atPage);
	startLoad();
}

//-----------------------------------------------------------------------------
void Render::load(const QString & path, const ComicDB & comicDB)
{
   //TODO prepare filters
   for(int i = 0; i < filters.count(); i++)
   {
	   if(typeid(*filters[i]) == typeid(BrightnessFilter))
		   if(comicDB.info.brightness == -1)
			   filters[i]->setLevel(0);
		   else
			filters[i]->setLevel(comicDB.info.brightness);
	   if(typeid(*filters[i]) == typeid(ContrastFilter))
		   if(comicDB.info.contrast == -1)
			   filters[i]->setLevel(100);
		   else
			   filters[i]->setLevel(comicDB.info.contrast);
	   if(typeid(*filters[i]) == typeid(GammaFilter))
		   if(comicDB.info.gamma == -1)
			   filters[i]->setLevel(100);
		   else
			   filters[i]->setLevel(comicDB.info.gamma);
   }
   createComic(path);
   loadComic(path,comicDB);
   startLoad();
}

void Render::createComic(const QString & path)
{
	if(comic!=0)
	{
		//comic->moveToThread(QApplication::instance()->thread());
		comic->disconnect();
		comic->deleteLater();
	}
		//comic->moveToThread(QApplication::instance()->thread());
	comic = FactoryComic::newComic(path);

	
	if(comic == NULL)//archivo no encontrado o no v�lido
	{
		emit errorOpening();
		reset();
		return;
	}

	previousIndex = currentIndex = 0;

	connect(comic,SIGNAL(errorOpening()),this,SIGNAL(errorOpening()));
	connect(comic,SIGNAL(errorOpening(QString)),this,SIGNAL(errorOpening(QString)));
	connect(comic,SIGNAL(crcErrorFound(QString)),this,SIGNAL(crcError(QString)));
	connect(comic,SIGNAL(errorOpening()),this,SLOT(reset()));
	connect(comic,SIGNAL(imageLoaded(int)),this,SIGNAL(imageLoaded(int)));
	connect(comic,SIGNAL(imageLoaded(int)),this,SLOT(pageRawDataReady(int)));
	connect(comic,SIGNAL(openAt(int)),this,SLOT(renderAt(int)));
	connect(comic,SIGNAL(numPages(unsigned int)),this,SIGNAL(numPages(unsigned int)));
	connect(comic,SIGNAL(numPages(unsigned int)),this,SLOT(setNumPages(unsigned int)));
	connect(comic,SIGNAL(imageLoaded(int,QByteArray)),this,SIGNAL(imageLoaded(int,QByteArray)));
	connect(comic,SIGNAL(isBookmark(bool)),this,SIGNAL(currentPageIsBookmark(bool)));
	connect(comic,SIGNAL(isBookmark(bool)),this,SLOT(pageIsBookmark(bool)));

	connect(comic,SIGNAL(bookmarksUpdated()),this,SIGNAL(bookmarksUpdated()));

	//connect(comic,SIGNAL(isLast()),this,SIGNAL(isLast()));
	//connect(comic,SIGNAL(isCover()),this,SIGNAL(isCover()));

	pagesReady.clear();
}
void Render::loadComic(const QString & path,const ComicDB & comicDB)
{
	comic->load(path,comicDB);
}
void Render::loadComic(const QString & path, int atPage)
{
	comic->load(path,atPage);
}

void Render::startLoad()
{
	QThread * thread = NULL;

	thread = new QThread();

	comic->moveToThread(thread);

	connect(thread, SIGNAL(started()), comic, SLOT(process()));
	connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

	if(thread != NULL)
		thread->start();

	invalidate();
	loadedComic = true;
	update();
}

void Render::renderAt(int page)
{
	previousIndex = currentIndex = page;
	emit pageChanged(page);
}

void Render::reset()
{
	loadedComic = false;
	invalidate();
}
//si se solicita la siguiente p�gina, se calcula cu�l debe ser en funci�n de si se lee en modo a doble p�gina o no.
//la p�gina s�lo se renderiza, si realmente ha cambiado.
void Render::nextPage()
{
	int nextPage; //indica cu�l ser� la pr�xima p�gina
	if(doublePage)
	{
		nextPage = currentIndex;
		if(currentIndex+2<(int)comic->numPages())
		{
			nextPage = currentIndex+2;
			if(currentIndex != nextPage)
				comic->setIndex(nextPage);
		}
	}
	else
	{
		nextPage = comic->nextPage();
	}

	//se fuerza renderizado si la p�gina ha cambiado
	if(currentIndex != nextPage)
	{
		previousIndex = currentIndex;
		currentIndex = nextPage;
		update();
		emit pageChanged(currentIndex);
	}
	else
		emit isLast();
}
//si se solicita la p�gina anterior, se calcula cu�l debe ser en funci�n de si se lee en modo a doble p�gina o no.
//la p�gina s�lo se renderiza, si realmente ha cambiado.
void Render::previousPage()
{
	int previousPage; //indica cu�l ser� la pr�xima p�gina
	if(doublePage)
	{
		if(currentIndex == 1)
			invalidate();
		previousPage = qMax(currentIndex-2,0);
		if(currentIndex != previousPage)
		{
			comic->setIndex(previousPage);
		}
	}
	else
	{
		previousPage = comic->previousPage();
	}
	
	//se fuerza renderizado si la p�gina ha cambiado
	if(currentIndex != previousPage)
	{
		previousIndex = currentIndex;
		currentIndex = previousPage;
		update();
		emit pageChanged(currentIndex);
	}
	else
		emit isCover();
}
unsigned int Render::getIndex()
{
	return comic->getIndex();
}
unsigned int Render::numPages()
{
	return comic->numPages();
}

bool Render::hasLoadedComic()
{
	if(comic!=0)
		return comic->loaded();
	return false;
}

void Render::setNumPages(unsigned int numPages)
{
	pagesReady.fill(false,numPages);
}

void Render::pageRawDataReady(int page)
{
	pagesEmited.push_back(page);
	if(pageRenders.size()>0)
	{
		for(int i=0;i<pagesEmited.size();i++)
		{
			pagesReady[pagesEmited.at(i)] = true;
			if(pagesEmited.at(i) == currentIndex)
				update();

			if(doublePage)
			{
				if(pagesEmited.at(i)==currentIndex+1)
					update();

				if ( ((pagesEmited.at(i) < currentIndex) && (pagesEmited.at(i) > currentIndex-2*numLeftPages)) ||
					((pagesEmited.at(i) > currentIndex+1) && (pagesEmited.at(i) < currentIndex+1+2*numRightPages)) )
				{
					fillBufferDoublePage();
				}
			}
			else
			{
				if ( ((pagesEmited.at(i) < currentIndex) && (pagesEmited.at(i) > currentIndex-numLeftPages)) ||
					((pagesEmited.at(i) > currentIndex) && (pagesEmited.at(i) < currentIndex+numRightPages)) )
				{
					fillBuffer();
				}
			}
		}
		pagesEmited.clear();
	}
}

//s�lo se renderiza la p�gina, si ha habido un cambio de p�gina
void Render::goTo(int index)
{

	if(currentIndex != index)
	{
		comic->setIndex(index);
		previousIndex = currentIndex;
		currentIndex = index;

		//si cambia la paridad de las p�gina en modo a doble p�gina, se rellena el buffer.
		//esto solo deber�a orcurrir al llegar al principio o al final
		if(doublePage && ((previousIndex - index) % 2)!=0) 
			invalidate();

		update();
		emit pageChanged(currentIndex);
	}
}

void Render::rotateRight()
{
	imageRotation = (imageRotation+90) % 360;
	reload();
}
void Render::rotateLeft()
{
	if(imageRotation == 0)
		imageRotation = 270;
	else
		imageRotation = imageRotation - 90;
	reload();
}

//Actualiza el buffer, a�adiendo las im�genes (vac�as) necesarias para su posterior renderizado y
//eliminado aquellas que ya no sean necesarias. Tambi�n libera los hilos (no estoy seguro de que sea responsabilidad suya)
//Calcula el n�mero de nuevas p�ginas que hay que buferear y si debe hacerlo por la izquierda o la derecha (seg�n sea el sentido de la lectura)
void Render::updateBuffer()
{
	QMutexLocker locker(&mutex);
	int windowSize = currentIndex - previousIndex;
	if(doublePage)
	{	
		windowSize = windowSize/2;
		if(currentIndex == 0 && windowSize == 0 && previousIndex == 1)
			windowSize = -1;

	}
	if(windowSize > 0)//add pages to right pages and remove on the left
	{
		windowSize = qMin(windowSize,buffer.size());
		for(int i = 0; i < windowSize; i++)
		{
			//renders
			PageRender * pr = pageRenders.front();
			pageRenders.pop_front();
			if(pr !=0)
			{
				if(pr->wait())
					delete pr;
			}
			pageRenders.push_back(0);

			//images
			
			if(buffer.front()!=0)
				delete buffer.front();
			buffer.pop_front();
			buffer.push_back(new QImage());
		}
	}
	else //add pages to left pages and remove on the right
		if(windowSize<0)
		{
			windowSize = -windowSize;
			windowSize = qMin(windowSize,buffer.size());
			for(int i = 0; i < windowSize; i++)
			{
				//renders
				PageRender * pr = pageRenders.back();
				pageRenders.pop_back();
				if(pr !=0)
				{
					if(pr->wait())
						delete pr;
				}
				pageRenders.push_front(0);

				//images
				buffer.push_front(new QImage());
				QImage * p = buffer.back();
				if(p!=0)
					delete p;
				buffer.pop_back();
			}
		}
		previousIndex = currentIndex;
}

void Render::fillBuffer()
{
	for(int i = 1; i <= qMax(numLeftPages,numRightPages); i++)
	{
		if ((currentIndex+i < (int)comic->numPages()) && 
			buffer[currentPageBufferedIndex+i]->isNull() && 
			i <= numRightPages &&
			pageRenders[currentPageBufferedIndex+i]==0 &&
			pagesReady[currentIndex+1]) //preload next pages
		{
			pageRenders[currentPageBufferedIndex+i] = new PageRender(this,currentIndex+i,comic->getRawData()->at(currentIndex+i),buffer[currentPageBufferedIndex+i],imageRotation,filters);
			connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
			pageRenders[currentPageBufferedIndex+i]->start();
		}

		if ((currentIndex-i > 0) && 
			buffer[currentPageBufferedIndex-i]->isNull() && 
			i <= numLeftPages &&
			pageRenders[currentPageBufferedIndex-i]==0 &&
			pagesReady[currentIndex-1]) //preload previous pages
		{
			pageRenders[currentPageBufferedIndex-i] = new PageRender(this,currentIndex-i,comic->getRawData()->at(currentIndex-i),buffer[currentPageBufferedIndex-i],imageRotation,filters);
			connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
			pageRenders[currentPageBufferedIndex-i]->start();
		}
	}
}

void Render::fillBufferDoublePage()
{
	for(int i = 1; i <= qMax(numLeftPages,numRightPages); i++)
	{
		if ((currentIndex+2*i < (int)comic->numPages()) && 
			buffer[currentPageBufferedIndex+i]->isNull() && 
			i <= numRightPages &&
			pageRenders[currentPageBufferedIndex+i]==0 &&
			(pagesReady[currentIndex+2*i] && pagesReady[qMin(currentIndex+(2*i)+1,(int)comic->numPages()-1)])) //preload next pages
		{
			if(currentIndex+(2*i)+1 > (int)comic->numPages()-1)
				pageRenders[currentPageBufferedIndex+i] = new DoublePageRender(this,currentIndex+2*i,comic->getRawData()->at(currentIndex+(2*i)),QByteArray(),buffer[currentPageBufferedIndex+i],imageRotation,filters);
			else
				pageRenders[currentPageBufferedIndex+i] = new DoublePageRender(this,currentIndex+2*i,comic->getRawData()->at(currentIndex+(2*i)),comic->getRawData()->at(currentIndex+(2*i)+1),buffer[currentPageBufferedIndex+i],imageRotation,filters);
			connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
			pageRenders[currentPageBufferedIndex+i]->start();
		}

		if ((currentIndex-2*i >= -1) && 
			buffer[currentPageBufferedIndex-i]->isNull() && 
			i <= numLeftPages &&
			pageRenders[currentPageBufferedIndex-i]==0 &&
			(pagesReady[qMax(currentIndex-2*i,0)] && pagesReady[qMin(currentIndex-(2*i)+1,(int)comic->numPages()-1)])) //preload previous pages
		{
			if(currentIndex-2*i == -1)
				pageRenders[currentPageBufferedIndex-i] = new DoublePageRender(this,0,QByteArray(),comic->getRawData()->at(0),buffer[currentPageBufferedIndex-i],imageRotation,filters);
			else
				pageRenders[currentPageBufferedIndex-i] = new DoublePageRender(this,currentIndex-2*i,comic->getRawData()->at(currentIndex-(2*i)),comic->getRawData()->at(currentIndex-(2*i)+1),buffer[currentPageBufferedIndex-i],imageRotation,filters);
			connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
			pageRenders[currentPageBufferedIndex-i]->start();
		}
	}
}

//M�todo que debe ser llamado cada vez que la estructura del buffer se vuelve inconsistente con el modo de lectura actual.
//se terminan todos los hilos en ejecuci�n y se libera la memoria (de hilos e im�genes)
void Render::invalidate()
{
	for(int i=0;i<pageRenders.size();i++)
	{
		if(pageRenders[i]!=0)
		{
			pageRenders[i]->wait();
			delete pageRenders[i];
			pageRenders[i] = 0;
		}
	}

	for(int i=0;i<buffer.size();i++)
	{
		delete buffer[i];
		buffer[i] = new QImage();
	}
}

void Render::doublePageSwitch()
{
	doublePage = !doublePage;
	if(comic)
	{
		invalidate();
		update();
	}
}

QString Render::getCurrentPagesInformation()
{
	QString s = QString::number(currentIndex+1);
	if (doublePage && (currentIndex+1 < (int)comic->numPages()))
		s += "-"+QString::number(currentIndex+2);
	s += "/"+QString::number(comic->numPages());
	return s;
}

void Render::setBookmark()
{
	comic->setBookmark();
}

void Render::removeBookmark()
{
	comic->removeBookmark();
}

void Render::save()
{
	comic->saveBookmarks();
}

Bookmarks * Render::getBookmarks()
{
	return comic->bm;
}

void Render::reload()
{
	if(comic)
	{
		invalidate();
		update();
	}
}

void Render::updateFilters(int brightness, int contrast, int gamma)
{
   for(int i = 0; i < filters.count(); i++)
   {
	   if(typeid(*filters[i]) == typeid(BrightnessFilter))
		   filters[i]->setLevel(brightness);
	   if(typeid(*filters[i]) == typeid(ContrastFilter))
		   filters[i]->setLevel(contrast);
	   if(typeid(*filters[i]) == typeid(GammaFilter))
		   filters[i]->setLevel(gamma);
   }

   reload();
}