/*
	Scan Tailor - Interactive post-processing tool for scanned pages.
	Copyright (C) 2015  Joseph Artsimovich <joseph.artsimovich@gmail.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/>.
*/

#ifndef IMAGEPROC_FIND_PEAKS_H_
#define IMAGEPROC_FIND_PEAKS_H_

#include "imageproc_config.h"
#include "BinaryImage.h"
#include "Connectivity.h"
#include "SeedFillGeneric.h"
#include "LocalMinMaxGeneric.h"
#include <QSize>
#include <QPoint>
#include <QRect>
#include <QDebug>
#include <vector>
#include <limits>
#include <algorithm>
#include <stdint.h>

namespace imageproc
{

namespace detail
{

namespace find_peaks
{

template<typename T, typename MostSignificantSelector,
		 typename LeastSignificantSelector, typename IncreaseSignificance>
void raiseAllButPeaks(
	MostSignificantSelector most_significant,
	LeastSignificantSelector least_significant,
	IncreaseSignificance increase_significance,
	QSize peak_neighborhood, T outside_values,
	T const* input, int input_stride, QSize size,
	T* to_be_raised, int to_be_raised_stride)
{
	if (peak_neighborhood.isEmpty()) {
		peak_neighborhood.setWidth(1);
		peak_neighborhood.setHeight(1);
	}

	// Dilate the peaks and write results to seed.
	QRect neighborhood(QPoint(0, 0), peak_neighborhood);
	neighborhood.moveCenter(QPoint(0, 0));
	localMinMaxGeneric(
		most_significant, neighborhood, outside_values,
		input, input_stride, size, to_be_raised, to_be_raised_stride
	);

	std::vector<T> mask(to_be_raised, to_be_raised + to_be_raised_stride * size.height());
	int const mask_stride = to_be_raised_stride;

	// Slightly raise the mask relative to to_be_raised.
	std::transform(mask.begin(), mask.end(), mask.begin(), increase_significance);

	seedFillGenericInPlace(
		most_significant, least_significant, CONN8,
		&to_be_raised[0], to_be_raised_stride, size, &mask[0], mask_stride
	);
}

} // namespace find_peaks

} // namespace detail

/**
 * \brief Finds positive or negative peaks on a 2D grid.
 *
 * A peak is defined as a cell or an 8-connected group of cells that is more
 * significant than any of its neighbor cells.  More significant means either
 * greater or less, depending on the kind of peaks we want to locate.
 * In addition, we provide functionality to suppress peaks that are
 * in a specified neighborhood of a more significant peak.
 *
 * \param most_significant A functor or a pointer to a free function that
 *        can be called with two arguments of type T and return the bigger
 *        or the smaller of the two.
 * \param least_significant Same as most_significant, but the oposite operation.
 * \param increase_significance A functor or a pointer to a free function that
 *        takes one argument and returns the next most significant value next
 *        to it.  Hint: for floating point data, use the nextafter() family of
 *        functions.  Their generic versions are available in Boost.
 * \param peak_mutator A functor or a pointer to a free function that will
 *        transform a peak value.  Two typical cases would be returning
 *        the value as is and returning a fixed value.
 * \param non_peak_mutator Same as peak_mutator, but for non-peak values.
 * \param neighborhood The area around a peak (centered at width/2, height/2)
 *        in which less significant peaks will be suppressed.  Passing an empty
 *        neighborhood is equivalent of passing a 1x1 neighborhood.
 * \param outside_values Values that are assumed to be outside of the grid bounds.
 *        This will affect peak detection at the edges of grid.
 * \param[in,out] data Pointer to the data buffer.
 * \param stride The size of a row in the data buffer, in terms of the number of T objects.
 * \param size Grid dimensions.
 */
template<typename T, typename MostSignificantSelector,
		 typename LeastSignificantSelector, typename IncreaseSignificance,
		 typename PeakMutator, typename NonPeakMutator>
void findPeaksInPlaceGeneric(
	MostSignificantSelector most_significant,
	LeastSignificantSelector least_significant,
	IncreaseSignificance increase_significance,
	PeakMutator peak_mutator,
	NonPeakMutator non_peak_mutator,
	QSize peak_neighborhood, T outside_values,
	T* data, int stride, QSize size)
{
	if (size.isEmpty()) {
		return;
	}

	std::vector<T> raised(size.width() * size.height());
	int const raised_stride = size.width();

	detail::find_peaks::raiseAllButPeaks(
		most_significant, least_significant, increase_significance,
		peak_neighborhood, outside_values, data, stride, size,
		&raised[0], raised_stride
	);

	T* data_line = data;
	T* raised_line = &raised[0];
	int const w = size.width();
	int const h = size.height();

	for (int y = 0; y < h; ++y) {
		for (int x = 0; x < w; ++x) {
			if (data_line[x] == raised_line[x]) {
				data_line[x] = peak_mutator(data_line[x]);
			} else {
				data_line[x] = non_peak_mutator(data_line[x]);
			}
		}
		raised_line += raised_stride;
		data_line += stride;
	}
}

/**
 * \brief Same as findPeaksInPlaceGeneric(), but returning a binary image
 *        rather than mutating the input data.
 */
template<typename T, typename MostSignificantSelector,
		 typename LeastSignificantSelector, typename IncreaseSignificance>
BinaryImage findPeaksGeneric(
	MostSignificantSelector most_significant,
	LeastSignificantSelector least_significant,
	IncreaseSignificance increase_significance,
	QSize peak_neighborhood, T outside_values,
	T const* data, int stride, QSize size)
{
	if (size.isEmpty()) {
		return BinaryImage();
	}

	std::vector<T> raised(size.width() * size.height());
	int const raised_stride = size.width();

	detail::find_peaks::raiseAllButPeaks(
		most_significant, least_significant, increase_significance,
		peak_neighborhood, outside_values, data, stride, size,
		&raised[0], raised_stride
	);

	BinaryImage peaks(size, WHITE);
	uint32_t* peaks_line = peaks.data();
	int const peaks_stride = peaks.wordsPerLine();
	T const* data_line = data;
	T const* raised_line = &raised[0];
	int const w = size.width();
	int const h = size.height();
	uint32_t const msb = uint32_t(1) << 31;

	for (int y = 0; y < h; ++y) {
		for (int x = 0; x < w; ++x) {
			if (data_line[x] == raised_line[x]) {
				peaks_line[x >> 5] |= msb >> (x & 31);
			}
		}
		peaks_line += peaks_stride;
		raised_line += raised_stride;
		data_line += stride;
	}

	return peaks;
}

} // namespace imageproc

#endif
