/*
	File                 : Segments.cpp
	Project              : LabPlot
	Description          : Contains methods to trace curve of image/plot
	--------------------------------------------------------------------
	SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
	SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "Segments.h"
#include "backend/datapicker/ImageEditor.h"
#include "backend/datapicker/Segment.h"

#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QImage>

/**
 * \class Segments
 * \brief container to open image/plot.
 *
 * this class is a container for all of the segment. a segment is a set
 * of linked lines that run along a curve in the original image. the
 * main complication is that curves in the original image cross each other
 * and other things like grid lines. we rely on the user to link
 * multiple segments together to get points along the entire curve length
 * * \ingroup datapicker
 */

Segments::Segments(DatapickerImage* image)
	: m_image(image) {
}

/*!
	segments are built when the original image is loaded. they start out hidden
	 and remain so until showSegments is called
*/
void Segments::makeSegments(QImage& imageProcessed) {
	// 	QElapsedTimer timer;
	// 	timer.start();
	clearSegments();

	const int width = imageProcessed.width();
	const int height = imageProcessed.height();

	// for each new column of pixels, loop through the runs. a run is defined as
	// one or more colored pixels that are all touching, with one uncolored pixel or the
	// image boundary at each end of the set. for each set in the current column, count
	// the number of runs it touches in the adjacent (left and right) columns. here is
	// the pseudocode:
	//   if ((L > 1) || (R > 1))
	//     "this run is at a branch point so ignore the set"
	//   else
	//     if (L == 0)
	//       "this run is the start of a new segment"
	//     else
	//       "this run is appended to the segment on the left

	bool* lastBool = new bool[height];
	bool* currBool = new bool[height];
	bool* nextBool = new bool[height];
	auto** lastSegment = new Segment*[(size_t)height];
	auto** currSegment = new Segment*[(size_t)height];
	loadSegment(lastSegment, height);

	// initialize one column of boolean flags using the pixels of the specified column
	for (int y = 0; y < height; ++y) {
		lastBool[y] = false;
		currBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, 0, y);
		nextBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, 1, y);
	}

	for (int x = 0; x < width; ++x) {
		matchRunsToSegments(x, height, lastBool, lastSegment, currBool, currSegment, nextBool);

		// get ready for next column
		for (int y = 0; y < height; ++y) {
			lastBool[y] = currBool[y];
			currBool[y] = nextBool[y];
		}

		if (x + 1 < width) {
			for (int y = 0; y < height; ++y)
				nextBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, x + 1, y);
		}
		scrollSegment(lastSegment, currSegment, height);
	}

	delete[] lastBool;
	delete[] currBool;
	delete[] nextBool;
	delete[] lastSegment;
	delete[] currSegment;
	// 	qDebug() << "Made segments in " << timer.elapsed() << "ms";
}

/*!
	scroll the segment pointers of the right column into the left column
*/
void Segments::scrollSegment(Segment** left, Segment** right, int height) {
	for (int y = 0; y < height; ++y)
		left[y] = right[y];
}

/*!
	identify the runs in a column, and connect them to segments
*/
void Segments::matchRunsToSegments(int x,
								   int height,
								   const bool* lastBool,
								   Segment** lastSegment,
								   const bool* currBool,
								   Segment** currSegment,
								   const bool* nextBool) {
	loadSegment(currSegment, height);

	int yStart = 0;
	bool inRun = false;
	for (int y = 0; y < height; ++y) {
		if (!inRun && currBool[y]) {
			inRun = true;
			yStart = y;
		}

		if ((y + 1 >= height) || !currBool[y + 1]) {
			if (inRun)
				finishRun(lastBool, nextBool, lastSegment, currSegment, x, yStart, y, height);

			inRun = false;
		}
	}

	removeUnneededLines(lastSegment, currSegment, height);
}

/*!
	remove unneeded lines belonging to segments that just finished in the previous column.
*/
void Segments::removeUnneededLines(Segment** lastSegment, Segment** currSegment, int height) {
	Segment* segLast = nullptr;
	for (int yLast = 0; yLast < height; ++yLast) {
		if (lastSegment[yLast] && (lastSegment[yLast] != segLast)) {
			segLast = lastSegment[yLast];

			bool found = false;
			for (int yCur = 0; yCur < height; ++yCur)
				if (segLast == currSegment[yCur]) {
					found = true;
					break;
				}

			if (!found) {
				if (segLast->length < m_image->minSegmentLength()) {
					// remove whole segment since it is too short
					m_image->scene()->removeItem(segLast->graphicsItem());
					segments.removeOne(segLast);
				}
			}
		}
	}
}

/*!
	initialize one column of segment pointers
*/
void Segments::loadSegment(Segment** columnSegment, int height) {
	for (int y = 0; y < height; ++y)
		columnSegment[y] = nullptr;
}

void Segments::clearSegments() {
	for (auto* seg : segments)
		m_image->scene()->removeItem(seg->graphicsItem());

	segments.clear();
}

/*!
	set segments visible
*/
void Segments::setSegmentsVisible(bool on) {
	for (auto* seg : segments)
		seg->setVisible(on);
}

void Segments::setAcceptHoverEvents(bool on) {
	for (auto* seg : segments) {
		QGraphicsItem* item = seg->graphicsItem();
		item->setAcceptHoverEvents(on);
		item->setFlag(QGraphicsItem::ItemIsSelectable, on);
	}
}

/*!
	process a run of pixels. if there are fewer than two adjacent pixel runs on
	either side, this run will be added to an existing segment, or the start of
	a new segment
*/
void Segments::finishRun(const bool* lastBool, const bool* nextBool, Segment** lastSegment, Segment** currSegment, int x, int yStart, int yStop, int height) {
	// count runs that touch on the left
	if (adjacentRuns(lastBool, yStart, yStop, height) > 1)
		return;

	// count runs that touch on the right
	if (adjacentRuns(nextBool, yStart, yStop, height) > 1)
		return;

	Segment* seg;
	int yLast = (int)((yStart + yStop) / 2);
	if (adjacentSegments(lastSegment, yStart, yStop, height) == 0) {
		seg = new Segment(m_image);
		QLine* line = new QLine(QPoint(x, yLast), QPoint(x, yLast));
		seg->path.append(line);
		seg->yLast = yLast;
		segments.append(seg);
	} else {
		// this is the continuation of an existing segment
		seg = adjacentSegment(lastSegment, yStart, yStop, height);
		QLine* line = new QLine(QPoint(x - 1, seg->yLast), QPoint(x, yLast));
		seg->length += abs(1 + (seg->yLast - yLast) * (seg->yLast - yLast));
		seg->path.append(line);
		seg->yLast = yLast;
	}

	seg->retransform();

	for (int y = yStart; y <= yStop; ++y)
		currSegment[y] = seg;
}

/*!
	return the number of runs adjacent to the pixels from yStart to yStop (inclusive)
*/
int Segments::adjacentRuns(const bool* columnBool, int yStart, int yStop, int height) {
	int runs = 0;
	bool inRun = false;
	for (int y = yStart - 1; y <= yStop + 1; ++y) {
		if ((0 <= y) && (y < height)) {
			if (!inRun && columnBool[y]) {
				inRun = true;
				++runs;
			} else if (inRun && !columnBool[y])
				inRun = false;
		}
	}

	return runs;
}

/*!
	find the single segment pointer among the adjacent pixels from yStart-1 to yStop+1
*/
Segment* Segments::adjacentSegment(Segment** lastSegment, int yStart, int yStop, int height) {
	for (int y = yStart - 1; y <= yStop + 1; ++y) {
		if ((0 <= y) && (y < height))
			if (lastSegment[y])
				return lastSegment[y];
	}

	return nullptr;
}

/*!
	return the number of segments adjacent to the pixels from yStart to yStop (inclusive)
*/
int Segments::adjacentSegments(Segment** lastSegment, int yStart, int yStop, int height) {
	int count = 0;
	bool inSegment = false;
	for (int y = yStart - 1; y <= yStop + 1; ++y) {
		if ((0 <= y) && (y < height)) {
			if (!inSegment && lastSegment[y]) {
				inSegment = true;
				++count;
			} else if (inSegment && !lastSegment[y])
				inSegment = false;
		}
	}

	return count;
}
