#include <Windows.h>
#include <msclr\marshal_cppstd.h>
#include "CardDetection.h"
#include "ArucoUtils.h"
#include "bitmap_image.hpp"
#include "zbar.h"

using namespace msclr::interop;
using namespace cv;
using namespace std;
using namespace System::Drawing;
using namespace Tango::TCC::CardDetector;
using namespace zbar;



CardDetection::CardDetection()
{

}

CardDetection::~CardDetection()
{

}

CardDetectionResult ^ CardDetection::Detect(cli::array<Byte>^ bitmap, CardDetectionConfig ^ config)
{
	CardDetectionResult^ s = gcnew CardDetectionResult();

	
	bool has_template = config->TemplateBitmap != nullptr;

	Mat img = convertBitmapToMat(bitmap);

	Mat timg;

	if (has_template)
	{
		timg = convertBitmapToMat(config->TemplateBitmap);
	}

	ArucoUtils utils;
	vector<cv::Point> vertices = utils.getArcusVertices(img);

	if (vertices.size() == 4)
	{
		Mat detectedMat = utils.applyHomography(img, vertices, cv::Size(config->DesiredBitmapWidth, config->DesiredBitmapHeight));

		bool passed_double_checking = true;

		if (config->EnableDoubleChecking)
		{
			vertices = utils.getArcusVertices(detectedMat);
			if (vertices.size() == 4)
			{
				passed_double_checking = true;
				System::Diagnostics::Debug::WriteLine("DOUBLE CHECKING: PASSED");
			}
			else
			{
				passed_double_checking = false;
				System::Diagnostics::Debug::WriteLine("DOUBLE CHECKING: FAILED");
			}
		}

		double similarity = 100;

		if (has_template && config->SimilarityTolerance > 0 && config->HistogramMethod < 4)
		{
			try
			{
				similarity = getSimilarity(timg, detectedMat, config->HistogramMethod);
				System::Diagnostics::Debug::WriteLine("SIMILARITY: " + similarity.ToString());
			}
			catch (const std::exception&)
			{
				similarity = 0;
				System::Diagnostics::Debug::WriteLine("SIMILARITY: ERROR!");
			}
		}

		if (similarity >= config->SimilarityTolerance && passed_double_checking)
		{
			s->IsDetected = true;

			//Perform CLAHE
			if (config->PerformCLAHE)
			{
				detectedMat = performCLAHE(detectedMat);
			}

			//Encode the opencv mat to bitmap.
			vector<uchar> buf;
			imencode(".bmp", detectedMat, buf, std::vector<int>());

			//copy the encoded bitmap to a managed byte array.
			cli::array<Byte>^ detectedBitmapBytes = gcnew cli::array<Byte>(buf.size());

			for (size_t i = 0; i < buf.size(); i++)
			{
				detectedBitmapBytes[i] = buf[i];
			}

			s->DetectedBitmap = detectedBitmapBytes;

			//Get bar-code
			s->Barcode = getBarcode(img);
			
			if (config->EnforceBarcodeDetection && s->Barcode == System::String::Empty)
			{
				s->IsDetected = false;
			}
		}
		else
		{
			System::Diagnostics::Debug::WriteLine("IMAGE DROPPED - SIMILARITY: " + similarity.ToString());
		}

		s->Similarity = similarity;
	}

	free(img.data);
	img.release();

	if (has_template)
	{
		free(timg.data);
		timg.release();
	}

	return s;
}

double CardDetection::getSimilarity(Mat base, Mat img, int histogram_method)
{
	Mat hsv_base;
	Mat hsv_img;

	Mat resized;

	resize(img, resized, base.size());

	cvtColor(base, hsv_base, COLOR_RGB2HSV);
	cvtColor(resized, hsv_img, COLOR_RGB2HSV);

	// Using 50 bins for hue and 60 for saturation
	int h_bins = 50; int s_bins = 60;
	int histSize[] = { h_bins, s_bins };

	// hue varies from 0 to 179, saturation from 0 to 255
	float h_ranges[] = { 0, 180 };
	float s_ranges[] = { 0, 256 };

	const float* ranges[] = { h_ranges, s_ranges };

	// Use the o-th and 1-st channels
	int channels[] = { 0, 1 };

	// Histograms
	MatND hist_base;
	MatND hist_test1;

	// Calculate the histograms for the HSV images
	calcHist(&hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false);
	normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());

	calcHist(&hsv_img, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false);
	normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());

	double similarity = compareHist(hist_base, hist_test1, histogram_method);

	return similarity * 100;
}

Mat CardDetection::convertBitmapToMat(cli::array<Byte>^ bitmap)
{
	pin_ptr<Byte> p = &bitmap[0];
	unsigned char* pby = p;
	bitmap_image bmp(pby, bitmap->Length);

	size_t bmp_size = sizeof(unsigned char) * bmp.pixel_count() * bmp.bytes_per_pixel();
	unsigned char* bitmapBuf = (unsigned char*)malloc(bmp_size);
	memcpy(bitmapBuf, bmp.data(), bmp_size);

	Mat img = Mat(bmp.height(), bmp.width(), CV_8UC3, bitmapBuf);

	return img;
}

System::String^ CardDetection::getBarcode(Mat img)
{
	//Get bar-code
	ImageScanner scanner;
	scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);

	Mat imGray;
	cvtColor(img, imGray, CV_RGB2GRAY);

	int width = imGray.cols;
	int height = imGray.rows;
	uchar *raw = (uchar *)imGray.data;
	zbar::Image image(width, height, "Y800", raw, width * height);
	int n = scanner.scan(image);

	for (zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol)
	{
		System::String^ barcode = marshal_as<System::String^>(symbol->get_data());
		System::Diagnostics::Debug::WriteLine("BARCODE: " + barcode);

		image.set_data(NULL, 0);
		return barcode;
	}

	return "";
}

Mat Tango::TCC::CardDetector::CardDetection::performCLAHE(Mat source)
{
	// READ RGB color image and convert it to Lab
	cv::Mat bgr_image = source;
	cv::Mat lab_image;
	cv::cvtColor(bgr_image, lab_image, CV_BGR2Lab);

	// Extract the L channel
	std::vector<cv::Mat> lab_planes(3);
	cv::split(lab_image, lab_planes);  // now we have the L image in lab_planes[0]

	// apply the CLAHE algorithm to the L channel
	cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
	clahe->setClipLimit(4);
	cv::Mat dst;
	clahe->apply(lab_planes[0], dst);

	// Merge the the color planes back into an Lab image
	dst.copyTo(lab_planes[0]);
	cv::merge(lab_planes, lab_image);

	// convert back to RGB
	cv::Mat image_clahe;
	cv::cvtColor(lab_image, image_clahe, CV_Lab2BGR);

	return image_clahe;
}
