#include "camera.hpp"
#include "pose_window.hpp"
#include "screen.hpp"
#include <nanogui/glutil.h>

#include <ftl/profiler.hpp>

#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>

#include <ftl/operators/antialiasing.hpp>
#include <ftl/cuda/normals.hpp>
#include <ftl/render/colouriser.hpp>
#include <ftl/cuda/transform.hpp>

#include <ftl/render/overlay.hpp>
#include "statsimage.hpp"

#define LOGURU_REPLACE_GLOG 1
#include <loguru.hpp>

#include <fstream>

#ifdef HAVE_OPENVR
#include "vr.hpp"
#endif

using ftl::rgbd::isValidDepth;
using ftl::gui::GLTexture;
using ftl::gui::PoseWindow;
using ftl::codecs::Channel;
using ftl::codecs::Channels;
using cv::cuda::GpuMat;


static int vcamcount = 0;

static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
	Eigen::Affine3d rx =
		Eigen::Affine3d(Eigen::AngleAxisd(ax, Eigen::Vector3d(1, 0, 0)));
	Eigen::Affine3d ry =
		Eigen::Affine3d(Eigen::AngleAxisd(ay, Eigen::Vector3d(0, 1, 0)));
	Eigen::Affine3d rz =
		Eigen::Affine3d(Eigen::AngleAxisd(az, Eigen::Vector3d(0, 0, 1)));
	return rz * rx * ry;
}

ftl::gui::Camera::Camera(ftl::gui::Screen *screen, int fsmask, int fid, ftl::codecs::Channel c)
		: screen_(screen), fsmask_(fsmask), fid_(fid), texture1_(GLTexture::Type::BGRA), texture2_(GLTexture::Type::BGRA), depth1_(GLTexture::Type::Float), channel_(c),channels_(0u) {
	
	eye_ = Eigen::Vector3d::Zero();
	neye_ = Eigen::Vector4d::Zero();
	rotmat_.setIdentity();
	
	//up_ = Eigen::Vector3f(0,1.0f,0);
	lerpSpeed_ = 0.999f;
	sdepth_ = false;
	ftime_ = (float)glfwGetTime();
	pause_ = false;

#ifdef HAVE_OPENVR
	vr_mode_ = false;
#endif

	//channel_ = Channel::Left;

	channels_ += c;
	//channels_ += Channel::Depth;
	width_ = 0;
	height_ = 0;

	// Create pose window...
	//posewin_ = new PoseWindow(screen, src_->getURI());
	//posewin_->setTheme(screen->windowtheme);
	//posewin_->setVisible(false);
	posewin_ = nullptr;
	renderer_ = nullptr;
	renderer2_ = nullptr;
	post_pipe_ = nullptr;
	record_stream_ = nullptr;
	transform_ix_ = -1;
	stereo_ = false;
	rx_ = 0;
	ry_ = 0;
	framesets_ = nullptr;

	colouriser_ = ftl::create<ftl::render::Colouriser>(screen->root(), "colouriser");
	overlayer_ = ftl::create<ftl::overlay::Overlay>(screen->root(), "overlay");

	// Is virtual camera?
	if (fid == 255) {
		renderer_ = ftl::create<ftl::render::CUDARender>(screen_->root(), std::string("vcam")+std::to_string(vcamcount++));
		// Allow mask to be changed
		fsmask_ = renderer_->value("fsmask", fsmask_);
		renderer_->on("fsmask", [this](const ftl::config::Event &e) {
			fsmask_ = renderer_->value("fsmask", fsmask_);
		});

		intrinsics_ = ftl::create<ftl::Configurable>(renderer_, "intrinsics");
	
		state_.getLeft() = ftl::rgbd::Camera::from(intrinsics_);
		state_.getRight() = state_.getLeft();

		{
			Eigen::Matrix4d pose;
			pose.setIdentity();
			state_.setPose(pose);

			for (auto &t : transforms_) {
				t.setIdentity();
			}
		}
		{
			double camera_initial_x = intrinsics_->value("camera_x", 0.0);
			double camera_initial_y = intrinsics_->value("camera_y", -1.75);
			double camera_initial_z = intrinsics_->value("camera_z", 0.0);

			double lookat_initial_x = intrinsics_->value("lookat_x", 1.0);
			double lookat_initial_y = intrinsics_->value("lookat_y", 0.0);
			double lookat_initial_z = intrinsics_->value("lookat_z", 0.0);

			Eigen::Vector3f head(camera_initial_x, camera_initial_y, camera_initial_z);
			Eigen::Vector3f lookat(lookat_initial_x, lookat_initial_y, lookat_initial_z);
			// TODO up vector
			Eigen::Matrix4f pose = nanogui::lookAt(head, head+lookat, Eigen::Vector3f(0.0f, 1.0f, 0.0f));

			eye_ = Eigen::Vector3d(camera_initial_x, camera_initial_y, camera_initial_z);
			neye_ = Eigen::Vector4d(eye_(0), eye_(1), eye_(2), 0.0);
			rotmat_ = pose.cast<double>();
			rotmat_.block(0, 3, 3, 1).setZero();
		}
	}
}

ftl::gui::Camera::~Camera() {
	//delete writer_;
	//delete fileout_;
}

void ftl::gui::Camera::drawUpdated(std::vector<ftl::rgbd::FrameSet*> &fss) {
	// Only draw if frameset updated.
	if (!stale_frame_.test_and_set()) {
		draw(fss);
	}
}

void ftl::gui::Camera::draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
	if (fid_ != 255) {
		for (auto *fs : fss) {
			if (!usesFrameset(fs->id)) continue;
			UNIQUE_LOCK(fs->mtx, lk);

			ftl::rgbd::Frame *frame = nullptr;

			if ((size_t)fid_ >= fs->frames.size()) return;
			if (!fs->hasFrame(fid_)) return;

			frame = &fs->frames[fid_];
			if (!frame->hasChannel(channel_)) return;

			auto &buf = colouriser_->colourise(*frame, channel_, 0);

			// For non-virtual cameras, copy the CUDA texture into the opengl
			// texture device-to-device.
			texture1_.make(buf.width(), buf.height());
			auto dst1 = texture1_.map(0);
			cudaMemcpy2D(dst1.data, dst1.step1(), buf.devicePtr(), buf.pitch(), buf.width()*4, buf.height(), cudaMemcpyDeviceToDevice);
			ftl::cuda::flip<uchar4>(dst1, 0);
			texture1_.unmap(0);

			depth1_.make(buf.width(), buf.height());
			dst1 = depth1_.map(0);
			dst1.setTo(cv::Scalar(0.5f));
			depth1_.unmap(0);

			width_ = texture1_.width();
			height_ = texture1_.height();
			return;
		}
	}
	//if (fsid_ >= fss.size()) return;

	//auto &fs = *fss[fsid_];

	_applyPoseEffects(fss);

	UNIQUE_LOCK(mutex_, lk2);
	//state_.getLeft().fx = intrinsics_->value("focal", 700.0f);
	//state_.getLeft().fy = state_.getLeft().fx;
	_draw(fss);
}

std::pair<const ftl::rgbd::Frame *, const ftl::codecs::Face *> ftl::gui::Camera::_selectFace(std::vector<ftl::rgbd::FrameSet*> &fss) {
	for (auto *fset : fss) {
		for (const auto &f : fset->frames) {
			if (f.hasChannel(Channel::Faces)) {
				std::vector<ftl::codecs::Face> data;
				f.get(Channel::Faces, data);

				if (data.size() > 0) {
					return {&f,&(*data.rbegin())};
				}
			}
		}
	}
	return {nullptr, nullptr};
}

void ftl::gui::Camera::_generateWindow(const ftl::rgbd::Frame &f, const ftl::codecs::Face &face, Eigen::Matrix4d &pose_adjust, ftl::render::ViewPort &vp) {
	auto cam = ftl::rgbd::Camera::from(intrinsics_);
	auto d = face;

	float screenWidth = intrinsics_->value("screen_size", 0.6f);  // In meters
	float screenHeight = (9.0f/16.0f) * screenWidth;

	float screenDistance = (d.depth > cam.minDepth && d.depth < cam.maxDepth) ? d.depth : intrinsics_->value("screen_dist_default", 0.5f);  // Face distance from screen in meters

	auto pos = f.getLeft().screenToCam(float(d.box.x+(d.box.width/2)), float(d.box.y+(d.box.height/2)), screenDistance);
	Eigen::Vector3f eye;
	eye[0] = -pos.x;
	eye[1] = pos.y;
	eye[2] = -pos.z;
	//eye[3] = 0;

	Eigen::Translation3f trans(eye);
	Eigen::Affine3f t(trans);
	Eigen::Matrix4f viewPose = t.matrix();

	// Calculate where the screen is within current camera space
	Eigen::Vector4f p1 = viewPose.cast<float>() * (Eigen::Vector4f(screenWidth/2.0, screenHeight/2.0, 0, 1));
	Eigen::Vector4f p2 = viewPose.cast<float>() * (Eigen::Vector4f(screenWidth/2.0, -screenHeight/2.0, 0, 1));
	Eigen::Vector4f p3 = viewPose.cast<float>() * (Eigen::Vector4f(-screenWidth/2.0, screenHeight/2.0, 0, 1));
	Eigen::Vector4f p4 = viewPose.cast<float>() * (Eigen::Vector4f(-screenWidth/2.0, -screenHeight/2.0, 0, 1));
	p1 = p1 / p1[3];
	p2 = p2 / p2[3];
	p3 = p3 / p3[3];
	p4 = p4 / p4[3];
	float2 p1screen = cam.camToScreen<float2>(make_float3(p1[0],p1[1],p1[2]));
	float2 p2screen = cam.camToScreen<float2>(make_float3(p2[0],p2[1],p2[2]));
	float2 p3screen = cam.camToScreen<float2>(make_float3(p3[0],p3[1],p3[2]));
	float2 p4screen = cam.camToScreen<float2>(make_float3(p4[0],p4[1],p4[2]));

	std::vector<cv::Point2f> quad_pts;
	std::vector<cv::Point2f> squre_pts;
	quad_pts.push_back(cv::Point2f(p1screen.x,p1screen.y));
	quad_pts.push_back(cv::Point2f(p2screen.x,p2screen.y));
	quad_pts.push_back(cv::Point2f(p3screen.x,p3screen.y));
	quad_pts.push_back(cv::Point2f(p4screen.x,p4screen.y));
	squre_pts.push_back(cv::Point2f(0,0));
	squre_pts.push_back(cv::Point2f(0,cam.height));
	squre_pts.push_back(cv::Point2f(cam.width,0));
	squre_pts.push_back(cv::Point2f(cam.width,cam.height));

	cv::Mat transmtx = cv::getPerspectiveTransform(quad_pts,squre_pts);
	//cv::Mat transformed = cv::Mat::zeros(overlay_.rows, overlay_.cols, CV_8UC4);
	//cv::warpPerspective(im1_, im1_, transmtx, im1_.size());

	// TODO: Use the transmtx above for perspective distortion..

	//ftl::render::ViewPort vp;
	vp.x = std::min(p4screen.x, std::min(p3screen.x, std::min(p1screen.x,p2screen.x)));
	vp.y = std::min(p4screen.y, std::min(p3screen.y, std::min(p1screen.y,p2screen.y)));
	vp.width = std::max(p4screen.x, std::max(p3screen.x, std::max(p1screen.x,p2screen.x))) - vp.x;
	vp.height = std::max(p4screen.y, std::max(p3screen.y, std::max(p1screen.y,p2screen.y))) - vp.y;
	/*vp.warpMatrix.entries[0] = transmtx.at<float>(0,0);
	vp.warpMatrix.entries[1] = transmtx.at<float>(1,0);
	vp.warpMatrix.entries[2] = transmtx.at<float>(2,0);
	vp.warpMatrix.entries[3] = transmtx.at<float>(0,1);
	vp.warpMatrix.entries[4] = transmtx.at<float>(1,1);
	vp.warpMatrix.entries[5] = transmtx.at<float>(2,1);
	vp.warpMatrix.entries[6] = transmtx.at<float>(0,2);
	vp.warpMatrix.entries[7] = transmtx.at<float>(1,2);
	vp.warpMatrix.entries[8] = transmtx.at<float>(2,2);
	vp.warpMatrix = vp.warpMatrix.getInverse(); //.getInverse();*/
	//renderer_->setViewPort(ftl::render::ViewPortMode::Warping, vp);

	pose_adjust = viewPose.cast<double>();
}

void ftl::gui::Camera::_applyPoseEffects(std::vector<ftl::rgbd::FrameSet*> &fss) {
	if (renderer_->value("window_effect", false)) {
		auto [frame,face] = _selectFace(fss);
		if (face) {
			Eigen::Matrix4d windowPose;
			ftl::render::ViewPort windowViewPort;
			_generateWindow(*frame, *face, windowPose, windowViewPort);

			// Apply the window effect
			renderer_->setViewPort(ftl::render::ViewPortMode::Stretch, windowViewPort);
			state_.getPose() = windowPose * state_.getPose();
		}
	}
}

void ftl::gui::Camera::setStereo(bool v) {
	UNIQUE_LOCK(mutex_, lk);

	if (isVirtual()) {
		stereo_ = v;
	} else if (v && availableChannels().has(Channel::Right)) {
		stereo_ = true;
	} else {
		stereo_ = false;
	}
}

static ftl::codecs::Channel mapToSecondChannel(ftl::codecs::Channel c) {
	switch (c) {
		case Channel::Depth		: return Channel::Depth2;
		case Channel::Normals	: return Channel::Normals2;
		default: return c;
	}
}

void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
	frame_.reset();
	frame_.setOrigin(&state_);

	// Make sure an OpenGL pixel buffer exists
	texture1_.make(state_.getLeft().width, state_.getLeft().height);
	depth1_.make(state_.getLeft().width, state_.getLeft().height);
	if (isStereo()) texture2_.make(state_.getRight().width, state_.getRight().height);

	// Map the GL pixel buffer to a GpuMat
	frame_.create<cv::cuda::GpuMat>(Channel::Colour) = texture1_.map(renderer_->getCUDAStream());
	frame_.create<cv::cuda::GpuMat>(Channel::Depth) = depth1_.map(renderer_->getCUDAStream());
	frame_.createTexture<float>(Channel::Depth);
	if (isStereo()) frame_.create<cv::cuda::GpuMat>(Channel::Colour2) = texture2_.map((renderer2_) ? renderer2_->getCUDAStream() : 0);

	// TODO: Remove;
	overlay_.create(state_.getLeft().height, state_.getLeft().width, CV_8UC4);
	//frame_.create<cv::Mat>(Channel::Overlay) = overlay_;

	//overlay_.setTo(cv::Scalar(0,0,0,0));
	//bool enable_overlay = overlayer_->value("enabled", false);

	{
		FTL_Profile("Render",0.034);
		renderer_->begin(frame_, Channel::Colour);
		if (isStereo()) {
			if (!renderer2_) {
				renderer2_ = ftl::create<ftl::render::CUDARender>(screen_->root(), std::string("vcam")+std::to_string(vcamcount++));
			}
			renderer2_->begin(frame_, Channel::Colour2);
		}

		try {
			for (auto *fs : fss) {
				if (!usesFrameset(fs->id)) continue;

				fs->mtx.lock();
				renderer_->submit(fs, ftl::codecs::Channels<0>(Channel::Colour), transforms_[fs->id]);
				if (isStereo()) renderer2_->submit(fs, ftl::codecs::Channels<0>(Channel::Colour), transforms_[fs->id]);

				//if (enable_overlay) {
					// Generate and upload an overlay image.
				//	overlayer_->apply(*fs, overlay_, state_);
				//	frame_.upload(Channel::Overlay, renderer_->getCUDAStream());
				//}
			}

			if (channel_ != Channel::Left && channel_ != Channel::Right && channel_ != Channel::None) {
				renderer_->blend(channel_);
				if (isStereo()) {
					renderer2_->blend(mapToSecondChannel(channel_));
				}
			}

			//if (enable_overlay) {
			//	renderer_->blend(Channel::Overlay);
			//}

			renderer_->end();
			if (isStereo()) renderer2_->end();
		} catch(std::exception &e) {
			LOG(ERROR) << "Exception in render: " << e.what();
		}

		for (auto *fs : fss) {
			if (!usesFrameset(fs->id)) continue;
			fs->mtx.unlock();
		}
	}

	if (!post_pipe_) {
		post_pipe_ = ftl::config::create<ftl::operators::Graph>(screen_->root(), "post_filters");
		post_pipe_->append<ftl::operators::FXAA>("fxaa");
	}

	post_pipe_->apply(frame_, frame_, 0);

	channels_ = frame_.getChannels();

	frame_.get<cv::cuda::GpuMat>(Channel::Depth).download(im_depth_);
	cv::flip(im_depth_, im_depth_, 0);

	//frame_.get<cv::cuda::GpuMat>(Channel::Normals).download(im_normals_);
	//im_normals_.createMatHeader().convertTo(im_normals_f_, CV_32FC4);
	//cv::flip(im_normals_f_, im_normals_f_, 0);

	// Normalize depth map
	frame_.get<cv::cuda::GpuMat>(Channel::Depth).convertTo(frame_.get<cv::cuda::GpuMat>(Channel::Depth), CV_32F, 1.0/8.0);

	// Unmap GL buffer from CUDA and finish updating GL texture
	texture1_.unmap(renderer_->getCUDAStream());
	depth1_.unmap(renderer_->getCUDAStream());
	if (isStereo()) texture2_.unmap(renderer2_->getCUDAStream());

	width_ = texture1_.width();
	height_ = texture1_.height();

	if (record_stream_ && record_stream_->active()) {
		// TODO: Allow custom channel selection
		ftl::rgbd::FrameSet fs2;
		auto &f = fs2.frames.emplace_back();
		fs2.count = 1;
		fs2.mask = 1;
		//fs2.stale = false;
		fs2.set(ftl::data::FSFlag::STALE);
		frame_.swapTo(Channels<0>(Channel::Colour), f);  // Channel::Colour + Channel::Depth
		fs2.timestamp = ftl::timer::get_time();
		fs2.id = 0;
		record_sender_->post(fs2);
		record_stream_->select(0, Channels<0>(Channel::Colour));
		f.swapTo(Channels<0>(Channel::Colour), frame_);
	}
}

void ftl::gui::Camera::update(int fsid, const ftl::codecs::Channels<0> &c) {
	if (!isVirtual() && ((1 << fsid) & fsmask_)) {
		channels_ = c;
		if (c.has(Channel::Depth)) {
			//channels_ += Channel::ColourNormals;
		}
	}
}

void ftl::gui::Camera::update(std::vector<ftl::rgbd::FrameSet*> &fss) {
	UNIQUE_LOCK(mutex_, lk);

	framesets_ = &fss;
	stale_frame_.clear();

	if (screen_->activeCamera() == this) {
		for (auto *fs : fss) {
			if (!usesFrameset(fs->id)) continue;

			for (auto &f : fs->frames) {
				//if (f.hasChanged(Channel::Pose)) {
					f.patchPose(T_);
				//}
			}
		}
	}

	//if (fss.size() <= fsid_) return;
	if (fid_ == 255) {
		name_ = "Virtual Camera";
	} else {
		for (auto *fs : fss) {
			if (!usesFrameset(fs->id)) continue;

			ftl::rgbd::Frame *frame = nullptr;

			if ((size_t)fid_ >= fs->frames.size()) return;
			frame = &fs->frames[fid_];

			auto n = frame->get<std::string>("name");
			if (n) {
				name_ = *n;
			} else {
				name_ = "No name";
			}
			state_.getLeft() = frame->getLeftCamera();
			return;
		}
	}
}

void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) {
	eye_[0] = p(0,3);
	eye_[1] = p(1,3);
	eye_[2] = p(2,3);

	double sx = Eigen::Vector3d(p(0,0), p(1,0), p(2,0)).norm();
	double sy = Eigen::Vector3d(p(0,1), p(1,1), p(2,1)).norm();
	double sz = Eigen::Vector3d(p(0,2), p(1,2), p(2,2)).norm();

	Eigen::Matrix4d rot = p;
	rot(0,3) = 0.0;
	rot(1,3) = 0.0;
	rot(2,3) = 0.0;
	rot(0,0) = rot(0,0) / sx;
	rot(1,0) = rot(1,0) / sx;
	rot(2,0) = rot(2,0) / sx;
	rot(0,1) = rot(0,1) / sy;
	rot(1,1) = rot(1,1) / sy;
	rot(2,1) = rot(2,1) / sy;
	rot(0,2) = rot(0,2) / sz;
	rot(1,2) = rot(1,2) / sz;
	rot(2,2) = rot(2,2) / sz;
	rotmat_ = rot;
}

void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) {
	//if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
	if (fid_ < 255) return;
	if (button == 1) {
		rx_ += rx;
		ry_ += ry;

		/*float rrx = ((float)ry * 0.2f * delta_);
		//orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_);
		float rry = (float)rx * 0.2f * delta_;
		float rrz = 0.0;


		Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
		rotmat_ = rotmat_ * r.matrix();*/
	}
}

void ftl::gui::Camera::keyMovement(int key, int modifiers) {
	//if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
	if (fid_ < 255) return;
	if (key == 263 || key == 262) {
		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
		float scalar = (key == 263) ? -mag : mag;
		neye_ += rotmat_*Eigen::Vector4d(scalar,0.0,0.0,1.0);
		return;
	} else if (key == 264 || key == 265) {
		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
		float scalar = (key == 264) ? -mag : mag;
		neye_ += rotmat_*Eigen::Vector4d(0.0,0.0,scalar,1.0);
		return;
	} else if (key == 266 || key == 267) {
		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
		float scalar = (key == 266) ? -mag : mag;
		neye_ += rotmat_*Eigen::Vector4d(0.0,scalar,0.0,1.0);
		return;
	} else if (key >= '0' && key <= '5' && modifiers == 2) {  // Ctrl+NUMBER
		int ix = key - (int)('0');
		transform_ix_ = ix-1;
		return;
	}
}

void ftl::gui::Camera::showPoseWindow() {
	posewin_->setVisible(true);
}

void ftl::gui::Camera::showSettings() {

}

#ifdef HAVE_OPENVR
bool ftl::gui::Camera::setVR(bool on) {
	if (on == vr_mode_) {
		LOG(WARNING) << "VR mode already enabled";
		return on;
	}
	vr_mode_ = on;

	if (on) {
		setStereo(true);

		UNIQUE_LOCK(mutex_, lk);
		//src_->set("baseline", baseline_);
		state_.getLeft().baseline = baseline_;

		Eigen::Matrix3d intrinsic;

		unsigned int size_x, size_y;
		screen_->getVR()->GetRecommendedRenderTargetSize(&size_x, &size_y);
		state_.getLeft().width = size_x;
		state_.getLeft().height = size_y;
		state_.getRight().width = size_x;
		state_.getRight().height = size_y;
		
		intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Left);
		CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0);
		state_.getLeft().fx = intrinsic(0,0);
		state_.getLeft().fy = intrinsic(0,0);
		state_.getLeft().cx = intrinsic(0,2);
		state_.getLeft().cy = intrinsic(1,2);
		
		intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Right);
		CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0);
		state_.getRight().fx = intrinsic(0,0);
		state_.getRight().fy = intrinsic(0,0);
		state_.getRight().cx = intrinsic(0,2);
		state_.getRight().cy = intrinsic(1,2);

		vr_mode_ = true;
	}
	else {
		vr_mode_ = false;
		setStereo(false);

		UNIQUE_LOCK(mutex_, lk);
		state_.getLeft() = ftl::rgbd::Camera::from(intrinsics_);
		state_.getRight() = state_.getLeft();
	}

	return vr_mode_;
}
#endif

void ftl::gui::Camera::setChannel(Channel c) {
	UNIQUE_LOCK(mutex_, lk);
	channel_ = c;
}

/*static void drawEdges(	const cv::Mat &in, cv::Mat &out,
						const int ksize = 3, double weight = -1.0, const int threshold = 32,
						const int threshold_type = cv::THRESH_TOZERO)
{
	cv::Mat edges;
	cv::Laplacian(in, edges, 8, ksize);
	cv::threshold(edges, edges, threshold, 255, threshold_type);

	cv::Mat edges_color(in.size(), CV_8UC4);
	cv::addWeighted(edges, weight, out, 1.0, 0.0, out, CV_8UC4);
}*/


void ftl::gui::Camera::active(bool a) {
	if (a) {

	} else {
		neye_[0] = eye_[0];
		neye_[1] = eye_[1];
		neye_[2] = eye_[2];
	}
}

void ftl::gui::Camera::drawOverlay(const Eigen::Vector2f &s) {
	if (!framesets_) return;
	//UNIQUE_LOCK(mutex_,lk);
	for (auto *fs : *framesets_) {
		if (!usesFrameset(fs->id)) continue;

		// Generate and upload an overlay image.
		overlayer_->draw(*fs, state_, s);
	}
}

const void ftl::gui::Camera::captureFrame() {
	float now = (float)glfwGetTime();
	if (!screen_->isVR() && (now - ftime_) < 0.04f) return;

	delta_ = now - ftime_;
	ftime_ = now;

	//LOG(INFO) << "Frame delta: " << delta_;

	//if (src_ && src_->isReady()) {
	if (width_ > 0 && height_ > 0) {
		Eigen::Matrix4d viewPose;

		if (screen_->isVR()) {
			#ifdef HAVE_OPENVR
			
			vr::VRCompositor()->SetTrackingSpace(vr::TrackingUniverseStanding);
			vr::VRCompositor()->WaitGetPoses(rTrackedDevicePose_, vr::k_unMaxTrackedDeviceCount, NULL, 0 );

			if (isStereo() && rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid )
			{
				Eigen::Matrix4d eye_l = ConvertSteamVRMatrixToMatrix4(
					vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left));
				
				//Eigen::Matrix4d eye_r = ConvertSteamVRMatrixToMatrix4(
				//	vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left));

				float baseline_in = 2.0 * eye_l(0, 3);
				
				if (baseline_in != baseline_) {
					baseline_ = baseline_in;
					//src_->set("baseline", baseline_);
					state_.getLeft().baseline = baseline_;
					state_.getRight().baseline = baseline_;
				}
				Eigen::Matrix4d pose = ConvertSteamVRMatrixToMatrix4(rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);
				Eigen::Vector3d ea = pose.block<3, 3>(0, 0).eulerAngles(0, 1, 2);

				Eigen::Vector3d vreye;
				vreye[0] = pose(0, 3);
				vreye[1] = -pose(1, 3);
				vreye[2] = -pose(2, 3);
				
				// NOTE: If modified, should be verified with VR headset!
				Eigen::Matrix3d R;
				R =		Eigen::AngleAxisd(ea[0], Eigen::Vector3d::UnitX()) *
						Eigen::AngleAxisd(-ea[1], Eigen::Vector3d::UnitY()) *
						Eigen::AngleAxisd(-ea[2], Eigen::Vector3d::UnitZ()); 
				
				//double rd = 180.0 / 3.141592;
				//LOG(INFO) << "rotation x: " << ea[0] *rd << ", y: " << ea[1] * rd << ", z: " << ea[2] * rd;
				// pose.block<3, 3>(0, 0) = R;
				
				rotmat_.block(0, 0, 3, 3) = R;

				// TODO: Apply a rotation to orient also

				eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
				eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
				eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;

				Eigen::Translation3d trans(eye_ + vreye);
				Eigen::Affine3d t(trans);
				viewPose = t.matrix() * rotmat_;
			
			} else {
				//LOG(ERROR) << "No VR Pose";
			}
			#endif
		} else {
			// Use mouse to move camera

			float rrx = ((float)ry_ * 0.2f * delta_);
			float rry = (float)rx_ * 0.2f * delta_;
			float rrz = 0.0;

			Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
			rotmat_ = rotmat_ * r.matrix();

			rx_ = 0;
			ry_ = 0;

			eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
			eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
			eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;

			Eigen::Translation3d trans(eye_);
			Eigen::Affine3d t(trans);
			viewPose = t.matrix() * rotmat_;
		}

		{
			UNIQUE_LOCK(mutex_, lk);

			if (isVirtual()) {
				if (transform_ix_ == -1) {
					state_.setPose(viewPose);
				} else if (transform_ix_ >= 0) {
					transforms_[transform_ix_] = viewPose;
				}
			}
		}

		if (framesets_) draw(*framesets_);
	}

	//return texture1_;
}

void ftl::gui::Camera::snapshot(const std::string &filename) {
	cv::Mat flipped;

	{
		UNIQUE_LOCK(mutex_, lk);
		//cv::flip(im1_, flipped, 0);
	}
	cv::cvtColor(flipped, flipped, cv::COLOR_BGRA2BGR);
	cv::imwrite(filename, flipped);
}

void ftl::gui::Camera::startVideoRecording(const std::string &filename) {
	if (!record_stream_) {
		record_stream_ = ftl::create<ftl::stream::File>(screen_->root(), "video2d");
		record_stream_->setMode(ftl::stream::File::Mode::Write);
		record_sender_ = ftl::create<ftl::stream::Sender>(screen_->root(), "videoEncode");
		record_sender_->setStream(record_stream_);
	}

	if (record_stream_->active()) return;

	record_stream_->set("filename", filename);
	record_stream_->begin();
}

void ftl::gui::Camera::stopVideoRecording() {
	if (record_stream_ && record_stream_->active()) record_stream_->end();
}

float ftl::gui::Camera::getDepth(int x, int y) {
	if (x < 0 || y < 0) { return NAN; }
	UNIQUE_LOCK(mutex_, lk);
	if (x >= im_depth_.cols || y >= im_depth_.rows) { return NAN; }
	LOG(INFO) << y << ", " << x;
	return im_depth_.createMatHeader().at<float>(y, x);
}

cv::Point3f ftl::gui::Camera::getPoint(int x, int y) {
	if (x < 0 || y < 0) { return cv::Point3f(); }
	UNIQUE_LOCK(mutex_, lk);
		LOG(INFO) << y << ", " << x;
	if (x >= im_depth_.cols || y >= im_depth_.rows) { return cv::Point3f(); }
	float d = im_depth_.createMatHeader().at<float>(y, x);

	auto point = frame_.getLeftCamera().screenToCam(x, y, d);
	Eigen::Vector4d p(point.x, point.y, point.z, 1.0f);
	Eigen::Matrix4d pose = frame_.getPose();
	Eigen::Vector4d point_eigen = pose * p;
	return cv::Point3f(point_eigen(0), point_eigen(1), point_eigen(2));
}

/*
cv::Point3f ftl::gui::Camera::getNormal(int x, int y) {
	UNIQUE_LOCK(mutex_, lk);
		LOG(INFO) << y << ", " << x;
	if (x >= im_normals_.cols || y >= im_normals_.rows) { return cv::Point3f(); }
	auto n = im_normals_f_.at<cv::Vec4f>(y, x);
	return cv::Point3f(n[0], n[1], n[2]);
}
*/

void ftl::gui::Camera::setTransform(const Eigen::Matrix4d &T) {
	T_ = T * T_;
}

Eigen::Matrix4d ftl::gui::Camera::getTransform() const {
	return T_;
}