#include <ftl/codecs/opencv_encoder.hpp>

#include <opencv2/opencv.hpp>

#include <loguru.hpp>
#include <vector>

using ftl::codecs::definition_t;
using ftl::codecs::codec_t;
using ftl::codecs::OpenCVEncoder;
using std::vector;

OpenCVEncoder::OpenCVEncoder(ftl::codecs::definition_t maxdef,
			ftl::codecs::definition_t mindef) : Encoder(maxdef, mindef, ftl::codecs::device_t::Software) {
	jobs_ = 0;
}

OpenCVEncoder::~OpenCVEncoder() {
	
}

bool OpenCVEncoder::supports(ftl::codecs::codec_t codec) {
	switch (codec) {
	case codec_t::JPG:
	case codec_t::PNG: return true;
	default: return false;
	}
}

bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, ftl::codecs::Packet &pkt) {
	bool is_colour = !(pkt.flags & ftl::codecs::kFlagFloat);

	if (is_colour && in.type() != CV_8UC4 && in.type() != CV_8UC1) return false;
	if (!is_colour && in.type() == CV_8UC4) {
		LOG(ERROR) << "OpenCV Encoder doesn't support lossy depth";
		return false;
	}

	auto [tx,ty] = ftl::codecs::chooseTileConfig(pkt.frame_count);
	pkt.definition = (pkt.definition == definition_t::Any) ? ftl::codecs::findDefinition(in.cols/tx, in.rows/ty) : pkt.definition;
	if (pkt.definition == definition_t::Invalid || pkt.definition == definition_t::Any) {
		LOG(ERROR) << "Could not find appropriate definition";
		return false;
	}

	/*pkt.definition = (pkt.definition == definition_t::Any) ? ftl::codecs::findDefinition(in.cols, in.rows) : pkt.definition;

	if (pkt.definition == definition_t::Invalid || pkt.definition == definition_t::Any) {
		LOG(ERROR) << "Invalid definition";
		return false;
	}*/

	// Ensure definition does not exceed max
	current_definition_ = pkt.definition; //((int)pkt.definition < (int)max_definition) ? max_definition : pkt.definition;

	in.download(tmp_);
	//CHECK(cv::Size(ftl::codecs::getWidth(definition), ftl::codecs::getHeight(definition)) == in.size()); 

	//if (!is_colour) {
		//tmp_.convertTo(tmp_, CV_16U, 1000.0f);
	//}

	int width = ftl::codecs::getWidth(current_definition_);
	int height = ftl::codecs::getHeight(current_definition_);

	// Scale down image to match requested definition...
	/*if (ftl::codecs::getHeight(current_definition_) < in.rows) {
		cv::resize(tmp_, tmp_, cv::Size(ftl::codecs::getWidth(current_definition_), ftl::codecs::getHeight(current_definition_)), 0, 0, (is_colour) ? 1 : cv::INTER_NEAREST);
	} else {
		
	}*/
	if (tx*width != in.cols || ty*height != in.rows) {
		LOG(ERROR) << "Input does not match requested definition";
		return false;
	}

	if (pkt.codec == codec_t::Any) pkt.codec = (is_colour && in.type() != CV_8UC1) ? codec_t::JPG : codec_t::PNG;

	//for (int i=0; i<chunk_count_; ++i) {
		// Add chunk job to thread pool
		//ftl::pool.push([this,i,cb,is_colour,bitrate](int id) {
			//ftl::codecs::Packet pkt;
			//pkt.bitrate = 0;
			//pkt.frame_count = 1;
			//pkt.definition = current_definition_;
			//pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG;

			try {
				_encodeBlock(tmp_, pkt);
			} catch(...) {
				LOG(ERROR) << "OpenCV encode block exception: ";
			}

			//std::unique_lock<std::mutex> lk(job_mtx_);
			//--jobs_;
			//if (jobs_ == 0) job_cv_.notify_one();
		//});
	//}

	//std::unique_lock<std::mutex> lk(job_mtx_);
	//job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
	//if (jobs_ != 0) {
	//	LOG(FATAL) << "Deadlock detected (" << jobs_ << ")";
	//}

	return true;
}

bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt) {
	//int chunk_width = in.cols / 1;
	//int chunk_height = in.rows / 1;

	// Build chunk heads
	//int cx = 0; //(pkt.block_number % chunk_dim_) * chunk_width;
	//int cy = 0; //(pkt.block_number / chunk_dim_) * chunk_height;
	//cv::Rect roi(cx,cy,chunk_width,chunk_height);

	cv::Mat chunkHead = in;

	if (pkt.codec == codec_t::PNG) {
		vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 1};
		if (!cv::imencode(".png", chunkHead, pkt.data, params)) {
			LOG(ERROR) << "PNG Encoding error";
			return false;
		}
		return true;
	} else if (pkt.codec == codec_t::JPG) {
		int q = int((95.0f - 50.0f) * (float(pkt.bitrate)/255.0f) + 50.0f);

		vector<int> params = {cv::IMWRITE_JPEG_QUALITY, q};
		cv::imencode(".jpg", chunkHead, pkt.data, params);
		return true;
	} else {
		LOG(ERROR) << "Bad channel configuration: imagetype=" << in.type(); 
	}

	return false;
}