/*UVOS*/

/* This file is part of MAClient copyright © 2021 Carl Philipp Klemm.
 *
 * MAClient is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPL) version 
 * 3 as published by the Free Software Foundation.
 *
 * MAClient 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 MAClient.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "cameras.h"
#include <QRandomGenerator>
#include <string>
#include <functional>
#include <QDebug>
#include <unistd.h>
#include <QTimer>

Cameras::Cameras(uvosled* led, bool quirks, bool serial):
	led_(led), quirks_(quirks), serial_(serial)
{
	ledTimer.setSingleShot(true);
	cameraFailureTimer.setSingleShot(true);
	connect(&ledTimer, &QTimer::timeout, this, &Cameras::lightOff);
	connect(&cameraFailureTimer, &QTimer::timeout, this, &Cameras::reloadCameras);
}

Cameras::~Cameras()
{
	lightOff();
}

bool Cameras::setCameras(const std::vector<cam::Camera::Description>& descriptions)
{
	clear();
	bool ret = true;
	for(auto& camera : descriptions)
	{
		if(!addCamera(camera))
			ret = false;
	}
	return ret;
}

void Cameras::reloadCameras()
{
	setCameras(getCameras());
}


void Cameras::clear()
{
	free_ = false;
	for(auto& camera : cameras_)
		cameraRemoved(camera);
	cameras_.clear();
	if(led_)
		uvosled_set_current(led_, 0xFF, 0);
}

std::vector<cam::Camera::Description> Cameras::getCameras()
{
	std::vector<cam::Camera::Description> desc;
	desc.reserve(cameras_.size());
	for(std::shared_ptr<Camera> camera : cameras_)
		desc.push_back(camera->cam()->getDescription());
	return desc;
}

std::shared_ptr<Camera> Cameras::getCamera(size_t id)
{
	std::vector<cam::Camera::Description> desc = getCameras();
	for(size_t i = 0; i < desc.size(); ++i)
	{
		if(desc[i].getId() == id)
			return cameras_[i];
	}
	return std::shared_ptr<Camera>(nullptr);
}

bool Cameras::addCamera(const cam::Camera::Description& desc)
{
	cameras_.push_back(std::shared_ptr<Camera>(new Camera(desc)));

	if(!cameras_.back()->cam()->isOpen())
	{
		cameras_.pop_back();
		return false;
	}

	enableCapture(false);
	blockCaptureId_ = cameras_.back()->id();

	cameras_.back()->cam()->setExposureTime(exposrueTime_);

	if(quirks_ && desc.getVendor().find("Photonfocus") != std::string::npos)
	{
		qDebug()<<"Mitiagting broken PhotonFocus single shot mode";
		std::shared_ptr<Camera> camera = cameras_.back();
		camera->cam()->setTriggerMode(cam::Camera::TRIGGER_FREE);
		camera->cam()->setAcquisitionMode(cam::Camera::MODE_FREE);
		camera->cam()->setFrameRate(10);
		camera->cam()->startAcquisition();
		std::this_thread::sleep_for (std::chrono::milliseconds(200));
		camera->cam()->stopAcquisition();
		cameras_.pop_back();
		camera = nullptr;
		std::this_thread::sleep_for (std::chrono::milliseconds(200));
		cameras_.push_back(std::shared_ptr<Camera>(new Camera(desc)));
		if(!cameras_.back()->cam()->isOpen())
		{
			cameras_.pop_back();
			return false;
		}
		camera = cameras_.back();
		cameraAdded(camera);
		camera->cam()->setTriggerMode(cam::Camera::TRIGGER_FREE);
		camera->cam()->setAcquisitionMode(cam::Camera::MODE_FREE);
		camera->cam()->setFrameRate(10);
		camera->cam()->startAcquisition();
		if(!serial_)
			QTimer::singleShot(quirkTime, this, [this, camera](){finishAddCamera(camera);});
		else
		{
			std::this_thread::sleep_for(std::chrono::microseconds(static_cast<long>(quirkTime*1000)));
			finishAddCamera(camera);
		}
	}
	else
	{
		finishAddCamera(cameras_.back());
	}

	return true;
}

void Cameras::finishAddCamera(std::shared_ptr<Camera> camera)
{
	camera->cam()->setTriggerMode(cam::Camera::TRIGGER_SOFTWARE);
	setFree(free_);
	connect(camera.get(), &Camera::newImage, this, &Cameras::imageRecived);
	if(blockCaptureId_ == camera->id())
		enableCapture(true);
	qDebug()<<"Using camera"<<camera->id();
}

bool Cameras::lightFor(const LightingSetup& lighting, double time)
{
	int ret = 0;
	if(led_)
	{
		ret = uvosled_set_current(led_, lighting.mask, lighting.brightness);
		ledTimer.start(time*1000);
	}
	return ret == 0;
}

void Cameras::lightOff()
{
	if(led_)
		uvosled_set_current(led_, 0xff, 0);
	ledTimer.stop();
}

void Cameras::trigger()
{
	if(serial_)
	{
		if(captureingCamera == 0)
		{
			lightFor(lighting_, 10000);
			qDebug()<<__func__<<"started";
			std::this_thread::sleep_for(std::chrono::microseconds(static_cast<long>(exposrueTime_)));
		}
		cameras_[captureingCamera]->cam()->trigger();
		++captureingCamera;
		if(captureingCamera < cameras_.size())
			QTimer::singleShot(exposrueTime_*1000, this, &Cameras::trigger);
		else
		{
			captureingCamera = 0;
			qDebug()<<__func__<<"finished";
		}
	}
	else
	{
		lightFor(lighting_, exposrueTime_*1.5);
		for(auto& camera : cameras_)
			camera->cam()->trigger();
	}
}

bool Cameras::start()
{
	bool ret = true;
	for(auto& camera : cameras_)
	{
		if(!camera->cam()->startAcquisition())
			ret = false;
	}

	if(free_)
		uvosled_set_current(led_, lighting_.mask, lighting_.brightness);

	return ret;
}

bool Cameras::stop()
{
	bool ret = true;
	for(auto& camera : cameras_)
	{
		if(!camera->cam()->stopAcquisition())
			ret = false;
	}

	lightOff();
	return ret;
}

void Cameras::disable(bool disable)
{
	disable_ = disable;
}

bool Cameras::setFree(bool free)
{
	stop();
	free_ = free;
	bool ret = true;

	for(auto& camera : cameras_)
	{
		if(!camera->cam()->setAcquisitionMode(free ? cam::Camera::MODE_FREE : cam::Camera::MODE_SINGLE))
		{
			qDebug()<<"failed to set acquisition mode on camera"<<camera->id();
			ret = false;
		}
		if(ret)
			camera->cam()->setTriggerMode(free ? cam::Camera::TRIGGER_FREE : cam::Camera::TRIGGER_SOFTWARE);
	}
	return ret;
}

bool Cameras::setExposureTime(double exposureTime)
{
	stop();
	exposrueTime_ = exposureTime;
	bool ret = true;
	for(auto& camera : cameras_)
	{
		if(!camera->cam()->setExposureTime(exposrueTime_*1000000))
		{
			qDebug()<<"failed to set exposure on camera"<<camera->id();
			ret = false;
		}
	}
	return ret;
}

void Cameras::imageRecived(Camera::Image img)
{
	if(!disable_)
	{
		qDebug()<<"free"<<free_;
		bool allreadyUpdated = false;
		for(auto& image : images_)
		{
			if(image.cameraId == img.cameraId)
			{
				allreadyUpdated = true;
				break;
			}
		}

		if(!allreadyUpdated)
			images_.push_back(img);

		qDebug()<<"Recived"<<images_.size()<<"of"<<cameras_.size()<<"images";

		if(images_.size() == 1)
		{
			if(!serial_)
				cameraFailureTimer.start(exposrueTime_*1000+1000);
			else
				cameraFailureTimer.start(exposrueTime_*1000*(cameras_.size()+1)+2000);
		}

		if(images_.size() == cameras_.size())
		{
			lightOff();
			cameraFailureTimer.stop();
			newImages(images_);
			images_.clear();
		}

	}
}

double Cameras::getMeanTemp()
{
	double mean = 0;
	bool failure = false;
	for(auto& camera : cameras_)
	{
		double temp;
		if(camera->cam()->getTemperature(temp))
			mean+=temp;
		else
			failure = true;
	}
	if(!failure)
		return mean/cameras_.size();
	else
		return NAN;
}

void Cameras::setSetup(const std::vector<CameraSetup>& setups)
{
	for(auto& camera : cameras_)
	{
		for(auto& setup : setups)
		{
			if(camera->id() == setup.id)
			{
				camera->cam()->setBayerMode(setup.bayerMode);
				camera->cam()->setGain(setup.gain);
			}
		}
	}
}

void Cameras::store(QSettings &settings)
{
	std::vector<cam::Camera::Description> available = cam::Camera::getAvailableCameras();

	settings.beginWriteArray(GROUP, cameras_.size());
	for(size_t i = 0; i < cameras_.size(); ++i)
	{
		settings.setArrayIndex(i);
		settings.setValue("id", static_cast<unsigned long long>(cameras_[i]->id()));
	}
	settings.endArray();
}

void Cameras::load(QSettings &settings)
{
	std::vector<cam::Camera::Description> available = cam::Camera::getAvailableCameras();

	int size = settings.beginReadArray(GROUP);
	for(int i = 0; i < size; ++i)
	{
		settings.setArrayIndex(i);
		size_t hash = settings.value("id", 0).toULongLong();
		for(auto& camera : available)
		{
			if(camera.getId() == hash)
				addCamera(camera);
		}
	}
	settings.endArray();
}
