/*******************************************************************************************************
 DkPageSegmentation.h

 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances

 Copyright (C) 2011-2015 Markus Diem <markus@nomacs.org>

 This file is part of nomacs.

 nomacs 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.

 nomacs 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/>.

 *******************************************************************************************************/

#pragma once

#include "DkMath.h"

#pragma warning(push, 0)	// no warnings from includes - begin
#include <opencv2/core/core.hpp>
#include <QString>
#pragma warning(pop)		// no warnings from includes - end

namespace nmp {

/**
* Box class DK_CORE_API, defines a non-skewed rectangle e.g. Bounding Box
**/
class DkBox {

public:

	/**
	* Default constructor.
	* All values are initialized with zero.
	**/
	DkBox() : uc(), lc() {};

	/**
	* Constructor.
	* @param uc the upper left corner of the box.
	* @param size the size of the box.
	**/
	DkBox(nmc::DkVector uc, nmc::DkVector size) {

		this->uc = uc;
		this->lc = uc+size;

		//if (size.width < 0 || size.height < 0)
			//std::cout << "the size is < 0: " << size << std::endl;
	};
	/**
	* Constructor.
	* @param x value of the upper left corner.
	* @param y value of the upper left corner.
	* @param width of the box.
	* @param height of the box.
	**/
	DkBox(float x, float y, float width, float height) {

		nmc::DkVector size = nmc::DkVector(width, height);

		uc = nmc::DkVector(x,y);
		lc = uc+size;

		//if (size.width < 0 || size.height < 0)
			//std::cout << "the size is < 0: " << size << std::endl;

	};
	/**
	* Constructor.
	* @param r box as rect with upperleft corner and width and height.
	**/
	DkBox(cv::Rect r) {

		nmc::DkVector size((float)r.width, (float)r.height);

		uc.x = (float)r.x;
		uc.y = (float)r.y;

		lc = uc+size;

		//if (size.width < 0 || size.height < 0)
		//	std::cout << "the size is < 0: " << size << std::endl;

	};

	/**
	* Constructor.
	* @param b box as DkBox.
	**/
	DkBox(const DkBox &b) {

		this->uc = b.uc;
		this->lc = b.uc + b.size();

		//if (size().width < 0 || size().height < 0)
		//	std::cout << "the size is < 0: " << size() << std::endl;
	}
	/**
	* Default destructor.
	**/
	~DkBox() {};

	void getStorageBuffer(char** buffer, size_t& length) const {


		size_t newBufferLength = length + 4*sizeof(float);
		char* newStream = new char[newBufferLength];

		if (*buffer) {

			// copy old stream & clean it
			memcpy(newStream, *buffer, length);
			delete *buffer;
		}

		float* newFStream = (float*)newStream;

		int pos = 0;
		newFStream[pos] = uc.x; pos++;
		newFStream[pos] = uc.y; pos++;
		newFStream[pos] = lc.x; pos++;
		newFStream[pos] = lc.y; pos++;

		*buffer = newStream;
		length = newBufferLength;
	}

	const char* setSorageBuffer(const char* buffer) {

		const float* fBuffer = (const float*)buffer;
		int pos = 0;
		uc.x = fBuffer[pos]; pos++;
		uc.y = fBuffer[pos]; pos++;
		lc.x = fBuffer[pos]; pos++;
		lc.y = fBuffer[pos]; pos++;

		return buffer+sizeof(float)*pos;	// update buffer position
	}

	//friend std::ostream& operator<<(std::ostream& s, DkBox& b) - original
	friend std::ostream& operator<<(std::ostream& s, DkBox b) {

		// this makes the operator<< virtual (stroustrup)
		return s << b.toString().toStdString();
	};

	void moveBy(const nmc::DkVector& dxy) {

		uc += dxy;
		lc += dxy;
	};

	bool isEmpty() const {

		return uc.isEmpty() && lc.isEmpty();
	};

	/**
	* Returns the box as opencv Rect.
	* @return a box as opencv Rect.
	**/
	cv::Rect getCvRect() const {

		return cv::Rect(cvRound(uc.x), cvRound(uc.y), cvRound(size().width), cvRound(size().height));
	}

	static DkBox contour2BBox(const std::vector<std::vector<cv::Point> >& pts) {

		if (pts.empty())
			return DkBox();

		// TODO: write this in dk style
		int ux = INT_MAX, uy = INT_MAX;
		int lx = 0, ly = 0;

		for (int cIdx = 0; cIdx < (int)pts.size(); cIdx++) {

			const std::vector<cv::Point>& cont = pts[cIdx];

			for (int idx = 0; idx < (int)cont.size(); idx++) {

				cv::Point p = cont[idx];

				if (p.x < ux)
					ux = p.x;
				if (p.x > lx)
					lx = p.x;
				if (p.y < uy)
					uy = p.y;
				if (p.y > ly)
					ly = p.y;
			}
		}
		DkBox rect((float)ux, (float)uy, (float)lx-ux, (float)ly-uy);

		return rect;
	}

	/**
	* Enlarges the box by the given offset, and the upperleft corner is recalculated.
	* @param offset by which the box is expanded.
	**/
	void expand(float offset) {

		uc -= (offset*0.5f);
	}

	/**
	* Clips the box according the vector s (the box is only clipped but not expanded).
	* @param s the clip vector.
	**/
	void clip(nmc::DkVector s) {

		uc.round();
		lc.round();

		uc.clipTo(s);
		lc.clipTo(s);

		//if (lc.x > s.x || lc.y > s.y)
		//	mout << "I did not clip..." << dkendl;
	};

	bool within(const nmc::DkVector& p) const {

		return (p.x >= uc.x && p.x < lc.x && 
			p.y >= uc.y && p.y < lc.y);
	};

	nmc::DkVector center() const {
		return uc + size() * 0.5f;
	};

	void scaleAboutCenter(float s) {

		nmc::DkVector c = center();

		uc = nmc::DkVector(uc-c)*s+c;
		lc = nmc::DkVector(lc-c)*s+c;
	};

	/**
	* Returns the x value of the upper left corner.
	* @return x value in pixel of the upperleft corner.
	**/
	int getX() const {
		return cvRound(uc.x);
	};
	/**
	* Returns the y value of the upper left corner.
	* @return y value in pixel of the upperleft corner.
	**/
	int getY() const {
		return cvRound(uc.y);
	};
	/**
	* Returns the width of the box.
	* @return the width in pixel of the box.
	**/
	int getWidth() const {
		return cvRound(lc.x-uc.x);
	};
	/**
	* Returns the width of the box.
	* @return float the width in pixel fo the box.
	**/ 
	float getWidthF() const {
		return lc.x-uc.x;
	};
	/**
	* Returns the height of the box.
	* @return the height in pixel of the box.
	**/
	int getHeight() const {
		return cvRound(lc.y-uc.y);
	};
	/**
	* Returns the height of the box as float
	* @return float height in pixel of the box.
	**/ 
	float getHeightF() const {
		return lc.y-uc.y;
	};
	/**
	* Returns the size of the box.
	* @return size of the box as opencv Size.
	**/
	cv::Size getSize() const {
		return cv::Size(getWidth(), getHeight());
	};

	nmc::DkVector size() const {

		return lc-uc;
	};

	void setSize(nmc::DkVector size) {

		lc = uc+size;
	};

	float area() const {

		nmc::DkVector s = size();
		return s.width*s.height;
	};

	float intersectArea(const DkBox& box) const {

		nmc::DkVector tmp1 = lc.maxVec(box.lc);
		nmc::DkVector tmp2 = uc.maxVec(box.uc);

		// no intersection?
		if (lc.x < uc.x || lc.y < lc.y)
			return 0;

		tmp1 = tmp2-tmp1;

		return tmp1.width*tmp1.height;
	};

	QString toString() const {

		//QString msg =	"\n upper corner: " + uc.toString();
		//msg +=				"\n size:         " + size().toString();

		return QString();
	};

	nmc::DkVector uc;		/**< upper left corner of the box **/
	nmc::DkVector lc;		/**< lower right corner of the box **/
};

/**
* A simple point class DK_CORE_API.
* This class DK_CORE_API is needed for a fast computation
* of the polygon overlap.
**/
class DkIPoint {

public:
	int x;
	int y;

	DkIPoint() : x(0), y(0) {};

	DkIPoint(int x, int y) {
		this->x = x;
		this->y = y;
	};
};


/**
* A simple vertex class DK_CORE_API.
* This class DK_CORE_API is needed for a fast computation
* of the polygon overlap.
**/
class DkVertex {

public:
	DkIPoint ip;
	DkIPoint rx;
	DkIPoint ry;
	int in;

	DkVertex() {};

	DkVertex (DkIPoint ip, DkIPoint rx, DkIPoint ry) {
		this->ip = ip;
		this->rx = rx;
		this->ry = ry;
		in = 0;
	};
};



class DkIntersectPoly {

	// this class DK_CORE_API is based on a method proposed by norman hardy
	// see: http://www.cap-lore.com/MathPhys/IP/aip.c

public:

	DkIntersectPoly();
	DkIntersectPoly(std::vector<nmc::DkVector> vecA, std::vector<nmc::DkVector> vecB);

	double compute();

private:

	std::vector<nmc::DkVector> vecA;
	std::vector<nmc::DkVector> vecB;
	int64 interArea;
	nmc::DkVector maxRange;
	nmc::DkVector minRange;
	nmc::DkVector scale;
	float gamut;

	void inness(std::vector<DkVertex> ipA, std::vector<DkVertex> ipB);
	void cross(DkVertex a, DkVertex b, DkVertex c, DkVertex d, double a1, double a2, double a3, double a4);
	void cntrib(int fx, int fy, int tx, int ty, int w);
	int64 area(DkIPoint a, DkIPoint p, DkIPoint q);
	bool ovl(DkIPoint p, DkIPoint q);
	void getVertices(const std::vector<nmc::DkVector>& vec, std::vector<DkVertex> *ip, int noise);
	void computeBoundingBox(std::vector<nmc::DkVector> vec, nmc::DkVector *minRange, nmc::DkVector *maxRange);
};

// data class
class DkPolyRect {

public:
	//DkPolyRect(nmc::DkVector p1, nmc::DkVector p2, nmc::DkVector p3, nmc::DkVector p4);
	DkPolyRect(const std::vector<cv::Point>& pts = std::vector<cv::Point>());
	DkPolyRect(const std::vector<nmc::DkVector>& pts);

	bool empty() const;
	double getMaxCosine() const { return maxCosine; };
	void draw(cv::Mat& img, const cv::Scalar& col = cv::Scalar(0, 100, 255)) const;
	std::vector<cv::Point> toCvPoints() const;
	QPolygonF toPolygon() const;
	std::vector<nmc::DkVector> getCorners() const;
	DkBox getBBox() const;
	double intersectArea(const DkPolyRect& pr) const;
	double getArea();
	double getAreaConst() const;
	void scale(float s);
	void scaleCenter(float s);
	bool inside(const nmc::DkVector& vec) const;
	float maxSide() const;
	nmc::DkVector center() const;
	static bool compArea(const DkPolyRect& pl, const DkPolyRect& pr);
	nmc::DkRotatingRect toRotatingRect() const;

protected:
	std::vector<nmc::DkVector> pts;
	double maxCosine;
	double area;

	void toDkVectors(const std::vector<cv::Point>& pts, std::vector<nmc::DkVector>& dkPts) const;
	void computeMaxCosine();
};

class PageExtractor {
	
public:
	PageExtractor() {}
	
	void findPage(cv::Mat img, float scale, std::vector<DkPolyRect>& rects);
	
protected:
	const int maxLinesHough = 30;
	const float houghPeakThresholdRel = 0.3f; // minimum accumulator value of hough lines, relative to smaller image dimension
	const double t_theta = CV_PI / 9; // angle tolerance for parallel lines
	const float t_l = 0.5f;
	const float maxGapLengthRel = 0.3f; // maximum gap size in findLineSegments, relative to smaller image dimension
	const int minLineSegmentLength = 10;
	const float minRelSideLength = 0.3f; // minimum length of final rectangle sides relative to smaller image dimension
	const double orthoTol = CV_PI / 9; // orthogonality tolerance
	const float cornerGapTol = 3.0f; // tolerance for line segments that almost form a corner
	const int numFinalRects = 3; // number of rectangles to return
	
	struct HoughLine {
		int acc;
		float rho;
		float angle;
	};
	
	struct LineSegment {
		cv::Point2f p1;
		cv::Point2f p2;
		float length;
	};
	
	struct ExtendedPeak {
		ExtendedPeak(const HoughLine& line1, const LineSegment& ls1, const HoughLine& line2, const LineSegment& ls2);
		//~ExtendedPeak() {}
		
		HoughLine line1;
		HoughLine line2;
		std::vector<LineSegment> spatialLines;
		std::pair<bool, cv::Point2f> intersectionPoint;
		double theta_k;
		double A_k;
	};
	
	struct IntermediatePeak {
		IntermediatePeak(const ExtendedPeak& ep1, const ExtendedPeak& ep2) : ep1(ep1), ep2(ep2) {}
		
		ExtendedPeak ep1;
		ExtendedPeak ep2;
	};
	
	struct Rectangle {
		Rectangle(const IntermediatePeak& ip, const std::vector<cv::Point2f>& corners) : ip(ip), corners(corners) {}
		
		IntermediatePeak ip;
		std::vector<cv::Point2f> corners;
	};
	
	enum class LineFindingMode {Horizontal, Vertical};
	
	static double angleDiff(double a, double b);
	static std::pair<bool, cv::Point2f> findLineIntersection(const LineSegment& ls1, const LineSegment& ls2);
	static float pointToLineDistance(LineSegment ls, cv::Point2f p);
	static cv::Mat removeText(cv::Mat gray, float sigma, int selemSize, int threshold = 2);
	std::vector<HoughLine> houghTransform(cv::Mat bwImg, float rho, float theta, int threshold, int linesMax) const;
	std::vector<LineSegment> findLineSegments(cv::Mat bwImg, const std::vector<HoughLine>& houghLines, int minLength, int maxGap) const;
};

};
