
#include "Webcam.h"
#include "log/log.h"
#include "picture/pict_io.h"
#include "platform.h"
#include "ui/ui_sprite.h"

#include "ntypes.h"
#include "opencv2\opencv.hpp"
#include <iostream>
#include <fstream>

using namespace GS;
using namespace GS::Script;

using namespace cv;
static const double webcam_fps = 25.0;

Webcam::Webcam() {
}

Webcam::~Webcam() {
	Close();
	if (InputVideo && InputVideo->isOpened())
		InputVideo->release();
	InputVideo = nullptr;
	outputVideo = nullptr;
}

ProcessWebcamThread::ProcessWebcamThread(cv::VideoCapture *_InputVideo, cv::VideoWriter *_outputVideo, int width, int height,std::string _outputVideoName,int _videoIndex) : InputVideo(_InputVideo), outputVideo(_outputVideo), not_finish(true), pause(true), record_frame(false),outputVideoName(_outputVideoName),videoIndex(_videoIndex) {
	im = new Mat(width, height, CV_8UC3);
}

void ProcessWebcamThread::Execute() {

	bool temp_not_finish = true;
	bool temp_pause = true;
	bool temp_record_frame = false;

	//construction LUT pour synchro avec la clock squirrel
	std::ofstream lutFile(outputVideoName+"_lut.bin", std::ios::binary);
	__LOG_CAM__ << "NEW RECORDING THREAD "<<videoIndex<<" LAUNCHED \n";
	while (temp_not_finish) {

		mutex_not_finish.Lock();
		temp_not_finish = not_finish;
		mutex_not_finish.Unlock();

		mutex_pause.Lock();
		temp_pause = pause;
		mutex_pause.Unlock();

		mutex_record_frame.Lock();
		temp_record_frame = record_frame;
		mutex_record_frame.Unlock();

		if (temp_pause || !temp_record_frame)
			Platform::Get().Sleep(10);
		else{
			InputVideo->read(*im);
			outputVideo->write(*im);

			lutFile.write(reinterpret_cast<const char*>(&current_frame_clock),sizeof(float));
		}
	}
	__LOG_CAM__ << "THREAD STOPPED "<<videoIndex<<"\n";
	outputVideo->release();
	im->release();
	lutFile.close();

	delete im;
}
//*********************************************************

#include <windows.h>
#include <dshow.h>

#pragma comment(lib, "strmiids")

HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
	// Create the System Device Enumerator.
	ICreateDevEnum *pDevEnum;
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
		CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));

	if (SUCCEEDED(hr))
	{
		// Create an enumerator for the category.
		hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);
		if (hr == S_FALSE)
		{
			hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
		}
		pDevEnum->Release();
	}
	return hr;
}

int DisplayDeviceInformation(IEnumMoniker *pEnum)
{
	IMoniker *pMoniker = NULL;
	int return_id_device = -1;
	int counter_device = 0;

	while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
	{
		IPropertyBag *pPropBag;
		HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
		if (FAILED(hr))
		{
			pMoniker->Release();
			continue;
		}

		VARIANT var;
		VariantInit(&var);

		// Get description or friendly name.
		hr = pPropBag->Read(L"Description", &var, 0);
		if (FAILED(hr))
		{
			hr = pPropBag->Read(L"FriendlyName", &var, 0);
		}
		if (SUCCEEDED(hr))
		{
			printf("%S\n", var.bstrVal);

			if (wcscmp(var.bstrVal, L"MMP SDK") != 0 && wcscmp(var.bstrVal, L"Oculus Rift") != 0 && wcscmp(var.bstrVal, L"HTC Vive") != 0)
				return_id_device = counter_device;

			VariantClear(&var);
		}

		hr = pPropBag->Write(L"FriendlyName", &var);

		// WaveInID applies only to audio capture devices.
		hr = pPropBag->Read(L"WaveInID", &var, 0);
		if (SUCCEEDED(hr))
		{
			printf("WaveIn ID: %d\n", var.lVal);
			VariantClear(&var);
		}

		hr = pPropBag->Read(L"DevicePath", &var, 0);
		if (SUCCEEDED(hr))
		{
			// The device path is not intended for display.
			printf("Device path: %S\n", var.bstrVal);
			VariantClear(&var);
		}

		pPropBag->Release();
		pMoniker->Release();
		++counter_device;
	}
	return return_id_device;
}


bool Webcam::Open(int width_, int height_) {
	__LOG_CAM__ << "Open\n";
	ReplayInputVideo = NULL;

	index_save_video = -1;
	index_replay_video = -1;
	thread_process = nullptr;
	InputVideo = nullptr;
	outputVideo = nullptr;

	width = width_;
	height = height_;


	int id_device = -1;
	HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (SUCCEEDED(hr))
	{
		IEnumMoniker *pEnum;

		hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
		if (SUCCEEDED(hr))
		{
			id_device = DisplayDeviceInformation(pEnum);
			pEnum->Release();
		}

		CoUninitialize();
	}
	if (id_device == -1)
		return false;

	InputVideo = new VideoCapture(0);
	InputVideo->set(CV_CAP_PROP_FRAME_WIDTH, width);
	InputVideo->set(CV_CAP_PROP_FRAME_HEIGHT, height);
	InputVideo->set(CAP_PROP_FPS, webcam_fps);

	if (!InputVideo->isOpened())
		return false;

	outputVideo = new VideoWriter;
	ReplayInputVideo = new VideoCapture();

	ReplayIm = new Mat(width, height, CV_8UC4);
	ReplayIm->setTo(cv::Scalar(255, 0, 0, 255));
	ReplayImframe = new Mat;

	return true;
}

void Webcam::Reset(bool _reset_video) {
	__LOG_CAM__ << "RESET\n";
	Close();
	video_times.clear();
	index_save_video = -1;
	index_replay_video = -1;

	if(_reset_video)
		ResetVideo();
}

void Webcam::RecordFrame(float _clock) {
    thread_process->mutex_record_frame.Lock();
    thread_process->record_frame = true;
	thread_process->current_frame_clock = _clock;
    thread_process->mutex_record_frame.Unlock();
}

void Webcam::ResetVideo() {
	// chargement du flux video
	std::stringstream video_name;
	video_name << "./webcam_record_" << index_save_video << ".avi";
	// set the codec fourcc
	unsigned int ex = outputVideo->fourcc('X', 'V', 'I', 'D');
	outputVideo->set(VIDEOWRITER_PROP_QUALITY, 70);
	outputVideo->open(video_name.str(), ex, webcam_fps, Size(width, height), true);

	if (!outputVideo->isOpened())
		__LOG_CAM__ << "Could not open the output video for write.\n";

	//Grab a frame
	thread_process = new ProcessWebcamThread(InputVideo, outputVideo, width, height,"webcam_record_"+std::to_string(index_save_video),index_save_video);
	thread_process->Start();

	index_save_video++;

	thread_process->mutex_pause.Lock();
	thread_process->pause = true;
	thread_process->mutex_pause.Unlock();
}

void Webcam::setPause(bool pause) {

	__LOG_CAM__ <<"setPause "<<pause<<"\n";
	if (!thread_process) {
		if (!pause) {
			__LOG_CAM__ <<"A\n";
			Close();
			ResetVideo();
			thread_process->mutex_pause.Lock();
			thread_process->pause = pause;
			thread_process->mutex_pause.Unlock();
		}
		return;
	}

	thread_process->mutex_pause.Lock();
	bool thread_pause = thread_process->pause;
	thread_process->mutex_pause.Unlock();


	if (!thread_pause && pause) {
		__LOG_CAM__ <<"B\n";
		Close();
		__LOG_CAM__ << "REPLAY INPUT RELEASE ./webcam_record_" << index_replay_video << ".avi\n ";
		ReplayInputVideo->release();
		index_replay_video = index_save_video - 1;
		std::stringstream video_name;
		video_name << "./webcam_record_" << index_replay_video << ".avi";

		__LOG_CAM__ << "REPLAY INPUT IS HOLDING ./webcam_record_" << index_replay_video << ".avi\n";

		ReplayInputVideo->open(video_name.str());
		return;

	} else if (thread_pause && !pause) {
		__LOG_CAM__ <<"C\n";
		Close();
		ResetVideo();
	}

	thread_process->mutex_pause.Lock();
	thread_process->pause = pause;
	thread_process->mutex_pause.Unlock();
}

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

bool Webcam::IsThreadRunning() {
    return thread_process != nullptr;
}

void Webcam::Close() {
	__LOG_CAM__ << "Close\n";

	if (thread_process) {
		thread_process->mutex_not_finish.Lock();
		thread_process->not_finish = false;
		thread_process->mutex_not_finish.Unlock();
		thread_process->Join();
		delete thread_process;
		thread_process = nullptr;
	}

	ReplayInputVideo->release();
}

uchar *Webcam::SetFrame(float clock, GS::String record_name , int record_index) {
	//create buffer from lut
	if(video_times.size() == 0){
		//charger lut dans video_times
		__LOG_CAM__ << "Chargement LUT: "<<record_name<<"_"<<record_index<<"_lut.bin\n";
		std::stringstream lut_path;
		lut_path << record_name <<"_"<<record_index<< "_lut.bin";
		std::ifstream lutFile(lut_path.str(), std::ios::binary);

        if (!lutFile.is_open())
        {
            __LOG_CAM__ << "Cannot open LUT file: " << record_name<<"_lut.bin\n";
            return NULL;
        }
		video_times.clear();

		float end_time;
		lutFile.seekg(0, std::ios::end);
		std::streampos size = lutFile.tellg();
		__LOG_CAM__ <<"size: "<<(int)size<<"\n";
		if(size < 4 ) //il n'y a meme pas un seul float ecrit
			 return NULL;

		lutFile.seekg(size - static_cast<std::streamoff>(sizeof(float)));
		lutFile.read(reinterpret_cast<char*>(&end_time), sizeof(float));
		__LOG_CAM__ <<"end: "<<(float)end_time<<" asked clock: "<<clock<<"\n";
		//read la derniere et premiere clock
		lutFile.seekg(0);
		float start_time;
		lutFile.read(reinterpret_cast<char*>(&start_time), sizeof(float));


		if(clock < start_time){
			__LOG_CAM__ << "clock < start_time\n";
			lutFile.close();
			return NULL;
		}
		else if(clock > end_time){
			__LOG_CAM__ << "clock > end_time\n";
			lutFile.close();
			return SetFrame(clock,record_name,record_index+1); //recursive search forward
		}

		if (record_index != index_replay_video) {
			__LOG_H__ << "Changing video\n";
			index_replay_video = record_index;
			std::stringstream video_name;
			video_name << record_name << "_" << index_replay_video << ".avi";
			__LOG_H__ << record_name << "_" << index_replay_video << ".avi";
			ReplayInputVideo->release();
			ReplayInputVideo->open(video_name.str());
		}


        float t;
		lutFile.seekg(0);
        while (lutFile.read(reinterpret_cast<char*>(&t), sizeof(float)))
        {
            video_times.push_back(t);
        }
        lutFile.close();
	}

	//upper_bound -1 clock dans video_times pour recuperer index frame ou clock <=
	if (video_times.empty())
        return NULL;

	//force reset if clock not in range
	if(clock < video_times[0] || clock > video_times[video_times.size() -1]){
		video_times.clear();
		return SetFrame(clock, record_name , 0);
	}

	auto it = std::upper_bound(video_times.begin(),video_times.end(),clock);

    if (it == video_times.begin())
    {
        // clock before first frame
		__LOG_CAM__ << "clock before first frame\n";
		//SetFrame(clock,record_name-1)
        return nullptr;
    }

	size_t index = std::distance(video_times.begin(), it) - 1;

	if (!ReplayInputVideo || !ReplayInputVideo->isOpened())
    {
        __LOG_CAM__ << "ReplayInputVideo not opened\n";
        return NULL;
    }
	__LOG_CAM__ << "Index found "<<index<<"for clock "<<clock<<"\n";
	ReplayInputVideo->set(cv::CAP_PROP_POS_FRAMES, index);

	if (!ReplayInputVideo->read(*ReplayImframe))
    {
        __LOG_CAM__ << "Failed to read frame at index " << index << "\n";
        return NULL;
    }

	cv::cvtColor(*ReplayImframe, *ReplayIm, CV_BGR2RGBA, 4);

	return ReplayIm->data;
}

