/**
 * Mandelbulber v2, a 3D fractal generator       ,=#MKNmMMKmmßMNWy,
 *                                             ,B" ]L,,p%%%,,,§;, "K
 * Copyright (C) 2014-20 Mandelbulber Team     §R-==%w["'~5]m%=L.=~5N
 *                                        ,=mm=§M ]=4 yJKA"/-Nsaj  "Bw,==,,
 * This file is part of Mandelbulber.    §R.r= jw",M  Km .mM  FW ",§=ß., ,TN
 *                                     ,4R =%["w[N=7]J '"5=],""]]M,w,-; T=]M
 * Mandelbulber is free software:     §R.ß~-Q/M=,=5"v"]=Qf,'§"M= =,M.§ Rz]M"Kw
 * you can redistribute it and/or     §w "xDY.J ' -"m=====WeC=\ ""%""y=%"]"" §
 * modify it under the terms of the    "§M=M =D=4"N #"%==A%p M§ M6  R' #"=~.4M
 * GNU General Public License as        §W =, ][T"]C  §  § '§ e===~ U  !§[Z ]N
 * published by the                    4M",,Jm=,"=e~  §  §  j]]""N  BmM"py=ßM
 * Free Software Foundation,          ]§ T,M=& 'YmMMpM9MMM%=w=,,=MT]M m§;'§,
 * either version 3 of the License,    TWw [.j"5=~N[=§%=%W,T ]R,"=="Y[LFT ]N
 * or (at your option)                   TW=,-#"%=;[  =Q:["V""  ],,M.m == ]N
 * any later version.                      J§"mr"] ,=,," =="""J]= M"M"]==ß"
 *                                          §= "=C=4 §"eM "=B:m|4"]#F,§~
 * Mandelbulber is distributed in            "9w=,,]w em%wJ '"~" ,=,,ß"
 * the hope that it will be useful,                 . "K=  ,=RMMMßM"""
 * 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 Mandelbulber. If not, see <http://www.gnu.org/licenses/>.
 *
 * ###########################################################################
 *
 * Authors: Krzysztof Marczak (buddhi1980@gmail.com)
 *
 * cScheduler class - class to schedule rendering job between CPU cores
 *
 * The image to render is divided into [height] horizontal lines of size [width] x 1.
 * Each line will be managed by the scheduler and given to the asking threads,
 * while the image renders.
 */

#include "scheduler.hpp"

#include <QDebug>

#include "system_data.hpp"

#define LINE_DONE_BY_SERVER 9999

cScheduler::cScheduler(cRegion<int> screenRegion, int progressive)
{
	startLine = screenRegion.y1;
	endLine = screenRegion.y2;
	numberOfLines = screenRegion.height;
	linePendingThreadId.resize(endLine);
	lineDone.resize(endLine);
	lastLinesDone.resize(endLine);
	stopRequest = false;
	progressiveStep = progressive;
	progressivePass = 1;
	progressiveEnabled = progressive > 1;
	Reset();
}

cScheduler::~cScheduler() {}

void cScheduler::Reset()
{
	std::fill(linePendingThreadId.begin(), linePendingThreadId.end(), 0);
	std::fill(lineDone.begin(), lineDone.end(), false);
	std::fill(lastLinesDone.begin(), lastLinesDone.end(), false);
}

bool cScheduler::ThereIsStillSomethingToDo(int threadId) const
{
	bool result = false;
	for (int i = startLine; i < endLine; i++)
	{
		// qDebug() << "ThereIsStillSomethingToDo:" << i << lineDone[i];
		if (!lineDone[i] && (linePendingThreadId[i] == threadId || linePendingThreadId[i] == 0))
		{
			result = true;
			break;
		}
	}

	if (stopRequest) result = false;
	if (systemData.globalStopRequest) result = false;

	return result;
}

bool cScheduler::AllLinesDone() const
{
	bool result = true;
	for (int i = startLine; i < endLine; i++)
	{
		// qDebug() << "AllLinesDone:" << i << lineDone[i];
		if (!lineDone[i])
		{
			result = false;
			break;
		}
	}

	if (stopRequest) result = true;

	return result;
}

bool cScheduler::ShouldIBreak(int threadId, int actualLine) const
{
	if (actualLine >= 0)
	{
		return threadId != linePendingThreadId[actualLine] || stopRequest;
	}
	else
	{
		qCritical()
			<< "cScheduler::ShouldIBreak(int threadId, int actualLine): actualLine lower than zero";
		return true;
	}
}

int cScheduler::NextLine(int threadId, int actualLine, bool lastLineWasBroken)
{
	mutex.lock();
	// qDebug() << "threadID:" << threadId << " Actual line:" << actualLine;

	int nextLine;

	if (!lastLineWasBroken)
	{
		for (int i = 0; i < progressiveStep; i++)
		{
			if (actualLine + i < endLine)
			{
				lineDone[actualLine + i] = true;
				lastLinesDone[actualLine + i] = true;
			}
		}
	}
	else
	{
		// qDebug() << "threadID:" << threadId << " lastLineWasBroken, line:" << actualLine;
	}

	// next line is not occupied by any thread
	if (actualLine < endLine - progressiveStep
			&& linePendingThreadId[actualLine + progressiveStep] == 0 && !lastLineWasBroken)
	{
		nextLine = actualLine + progressiveStep;
		// qDebug() << "threadID:" << threadId << " one after:" << nextLine;
	}
	else
	// next line is occupied or it's last line. There is needed to find new optimal line for rendering
	{
		nextLine = FindBiggestGap();
		// qDebug() << "threadID:" << threadId << " gap:" << nextLine;
	}

	if (nextLine >= 0)
	{
		if (linePendingThreadId[nextLine] == 0)
		{
			for (int i = 0; i < progressiveStep; i++)
			{
				if (nextLine + i < endLine)
				{
					linePendingThreadId[nextLine + i] = threadId;
				}
			}
		}
	}
	mutex.unlock();

	if (nextLine < 0)
	{
		// qCritical() << "cScheduler::NextLine(int threadId, int actualLine): not possible to find new
		// line";
		return -1;
	}

	// qDebug() << "threadID:" << threadId << " Next line:" << nextLine;
	return nextLine;
}

int cScheduler::FindBiggestGap() const
{
	bool firstFreeFound = false;
	int firstFree = -1;
	int lastFree;
	int maxHole = 0;
	int theBest = -1;

	for (int i = startLine; i < endLine; i++)
	{
		if (!firstFreeFound && linePendingThreadId[i] == 0)
		{
			firstFreeFound = true;
			firstFree = i;
			if (i == endLine - 1)
			{
				theBest = i;
				theBest /= progressiveStep;
				theBest *= progressiveStep;
				break;
			}
			continue;
		}

		if (firstFreeFound && (linePendingThreadId[i] > 0 || i == endLine - 1))
		{
			lastFree = i;
			int holeSize = lastFree - firstFree;
			firstFreeFound = false;

			if (holeSize > maxHole)
			{
				maxHole = holeSize;
				// next line should be in the middle of the biggest gap
				theBest = (lastFree + firstFree) / 2;
				theBest /= progressiveStep;
				theBest *= progressiveStep;
				// out << "Jump Id: " << threadId  << " first: " << firstFree << " last: " << lastFree <<
				// endl;
			}
		}
	}
	return theBest;
}

void cScheduler::InitFirstLine(int threadId, int firstLine)
{
	linePendingThreadId[firstLine] = threadId;
}

QList<int> cScheduler::GetLastRenderedLines()
{
	QList<int> list;
	for (int i = startLine; i < endLine; i++)
	{
		if (lastLinesDone[i])
		{
			list.append(i);
			lastLinesDone[i] = false;
		}
	}
	return list;
}

double cScheduler::PercentDone() const
{
	int count = 0;
	for (int i = startLine; i < endLine; i++)
	{
		if (lineDone[i]) count++;
	}

	double progressiveDone, percent_done;
	if (progressivePass == 1)
		progressiveDone = 0;
	else
		progressiveDone = 0.25 / (progressiveStep * progressiveStep);

	if (progressiveEnabled)
	{
		percent_done =
			double(count) / numberOfLines * 0.75 / (progressiveStep * progressiveStep) + progressiveDone;
	}
	else
	{
		percent_done = double(count) / numberOfLines;
	}

	return percent_done;
}

bool cScheduler::ProgressiveNextStep()
{
	progressiveStep /= 2;
	progressivePass++;

	if (progressiveStep == 0)
	{
		return false;
	}
	else
	{
		std::fill(linePendingThreadId.begin(), linePendingThreadId.end(), 0);
		std::fill(lineDone.begin(), lineDone.end(), false);
		return true;
	}
}

void cScheduler::MarkReceivedLines(const QList<int> &lineNumbers)
{
	for (int line : lineNumbers)
	{
		lineDone[line] = true;
		lastLinesDone[line] = true;
		linePendingThreadId[line] = LINE_DONE_BY_SERVER;
	}
}

QList<int> cScheduler::CreateDoneList() const
{
	QList<int> list;
	for (int i = startLine; i < endLine; i++)
	{
		if (lineDone[i])
		{
			list.append(i);
		}
	}

	return list;
}

void cScheduler::UpdateDoneLines(const QList<int> &done)
{
	mutex.lock();
	for (int line : done)
	{
		lastLinesDone[line] = false;
		lineDone[line] = true;
		linePendingThreadId[line] = LINE_DONE_BY_SERVER;
	}
	mutex.unlock();
}

bool cScheduler::IsLineDoneByServer(int line) const
{
	if (line >= startLine && line < endLine)
	{
		return linePendingThreadId[line] == LINE_DONE_BY_SERVER;
	}
	return false;
}
