/*
 * Copyright (c) 2019 TOYOTA MOTOR CORPORATION
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "videoutils.h"
#include <opencv2/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <boost/algorithm/string.hpp>
#include <glib.h>
#include <sys/time.h>

using namespace VIDEO_UTILS;

GMainLoop * mainloop = NULL;
static int grabImage(void *data);
static void printhelp(const char *argv0);
static void mainLoopQuit(int sig);

VideoUtils::VideoUtils(int device, int framerate, int wid, int hght, std::string file, std::string co)
		: fps(framerate), width(wid), height(hght), deviceno(device), codec(co), filename(file)
{
	cvCapture = std::unique_ptr < cv::VideoCapture > (new cv::VideoCapture());
	cvWriter = std::unique_ptr < cv::VideoWriter > (new cv::VideoWriter());

	isReady = false;
	framecount = 0;

	if (cvCapture && !cvCapture->isOpened())
	{
		if (cvCapture->open(deviceno))
		{
			cvCapture->set(cv::CAP_PROP_FRAME_WIDTH, width);
			cvCapture->set(cv::CAP_PROP_FRAME_HEIGHT, height);

			if (cvWriter && !cvWriter->isOpened())
			{
				isReady = openVideoWriter();
			}
		}
	}
}

void VideoUtils::releaseSource()
{
	if (cvCapture && cvCapture->isOpened())
	{
		cvCapture->release();
	}

	if (cvWriter && cvWriter->isOpened())
	{
		cvWriter->release();
	}
}

bool VideoUtils::openVideoWriter()
{
	if (!cvCapture || !cvCapture->isOpened() || !cvWriter)
	{
		return false;
	}

	/* Get the size of a frame from the device.	*/
	cv::Size framesize = cv::Size((int) cvCapture->get(cv::CAP_PROP_FRAME_WIDTH), (int) cvCapture->get(cv::CAP_PROP_FRAME_HEIGHT));

	/* Get the CODEC.	*/
	if (codec.empty() || codec.size() != 4)
	{
		codec = "MJPG";
	}
	boost::algorithm::to_upper(codec);

	return cvWriter->open(filename, cv::VideoWriter::fourcc(codec.at(0), codec.at(1), codec.at(2), codec.at(3)), fps, framesize);
}

void VideoUtils::writeVideoFrame(const cv::UMat& f)
{
	if (!cvWriter || !cvWriter->isOpened())
	{
		return;
	}

	if (framecount < fps)
	{
		framecount++;
		return;
	}

	cv::Mat frame;
	f.copyTo(frame);

	/* Put the current time into the frame.	*/
	int fontFace = cv::FONT_HERSHEY_SIMPLEX;
	double fontScale = 0.6;
	int baseline = 0;
	int thickness = 2;
	std::stringstream datestr;

	datestr << getCurrentTime();

	cv::Size dateTextSize = cv::getTextSize(datestr.str(), fontFace, fontScale, thickness, &baseline);

	for (; fontScale > 0.1 && dateTextSize.width > frame.cols; )
	{
		fontScale -= 0.2;
		dateTextSize = cv::getTextSize(datestr.str(), fontFace, fontScale, thickness, &baseline);
	}

	cv::Point dateOrg(frame.cols - dateTextSize.width, dateTextSize.height);

	cv::putText(frame, datestr.str(), dateOrg, fontFace, fontScale, cv::Scalar(255, 255, 255), thickness);

	/* Write the frame into the log file.	*/
	*(cvWriter.get()) << frame;
}

std::string VideoUtils::getCurrentTime()
{
	time_t seconds;
	struct tm currenttime;
	if (time(&seconds) == (time_t) - 1)
	{
		return "";
	}
	localtime_r(&seconds, &currenttime);

	char timestr[] = "1900/01/01/00/00/00";

	sprintf(timestr, "%04d/%02d/%02d %02d:%02d:%02d", currenttime.tm_year + 1900, currenttime.tm_mon + 1, currenttime.tm_mday, currenttime.tm_hour, currenttime.tm_min, currenttime.tm_sec);

	return timestr;
}

static int grabImage(void *data)
{
	VideoUtils* utils = reinterpret_cast<VideoUtils*>(data);

	if (!utils->cvCapture || !utils->cvCapture->isOpened())
	{
		return false;
	}

	cv::UMat frame;
	*(utils->cvCapture.get()) >> frame;

	if (!frame.empty())
	{
		/* Write the frame into the log file.	*/
		utils->writeVideoFrame(frame);
		return true;
	}
	else
	{
		return false;
	}
}

static void printhelp(const char *argv0)
{
	printf("Usage: %s [args]\n"
			"   [-d|--device <0-5> ] set the device\n"
			"   [-w|--width] specify the width\n"
			"   [-h|--height] specify the height\n"
			"   [-r]--frame rate] specify the frame rate\n"
			"   [-c]--codec] specify the codec\n"
			"   [-f]--folder name] specify a folder to save the video file\n", argv0);
}

static void mainLoopQuit(int sig)
{
	if ((sig == SIGUSR1) && (mainloop != NULL))
	{
		g_main_loop_quit(mainloop);
	}
}

int main(int argc, char **argv)
{
	static int opt;
	int device = 0;
	int framerate = 30;
	int width = 640;
	int height = 480;
	std::string filename = "/tmp/temp.avi";
	std::string codec = "MP42";

	while ((opt = getopt(argc, argv, "d:w:h:r:f:c:")) != -1)
	{
		switch (opt)
		{
		case 'd':
			device = atoi(optarg);
			break;
		case 'w':
			width = atoi(optarg);
			break;
		case 'h':
			height = atoi(optarg);
			break;
		case 'r':
			framerate = atoi(optarg);
			break;
		case 'f':
			filename.assign(optarg);
			break;
		case 'c':
			codec.assign(optarg);
			break;
		default:
			printhelp(basename(argv[0]));
			return (0);
		}
	}

	VideoUtils utils(device, framerate, width, height, filename, codec);

	if (utils.isReady && framerate > 0)
	{
		void (*oldSigUsr1Handler)(int);

		oldSigUsr1Handler = signal(SIGUSR1, mainLoopQuit);

		mainloop = g_main_loop_new(NULL, FALSE);

		g_timeout_add(1000 / framerate, grabImage, &utils);

		g_main_loop_run(mainloop);
		g_main_loop_unref(mainloop);

		if (oldSigUsr1Handler != SIG_ERR)
		{
			signal(SIGUSR1, oldSigUsr1Handler);
		}
	}

	utils.releaseSource();

	return 0;
}