#ifndef _VIDEORECORDER
#define _VIDEORECORDER

//std lib
#include <queue>
#include <iostream>
#include <fstream>
#include <experimental/filesystem>

// OpenCV
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

// Submodules
#include "../libs/inih/INIReader.h"
#include "../libs/date/include/date/date.h"

// Allow streams in loguru for cout << style logging
#define LOGURU_WITH_STREAMS 1
#include "../libs/loguru/loguru.hpp"

// Project Files
#include "FrameRecorder.h"

// namespaces 
using namespace std;
using namespace cv;
namespace fs = std::experimental::filesystem;

class VideoRecorder : public FrameRecorder {

private:

    int videoPeriod;
	int frameRate;
	int fourCC;

    bool videoOpen;

	string gstPipeline;
	string gstExt;
	
	VideoWriter video;
  
public:

    /**
     * @brief Construct a new Video Recorder object
     * 
     */
    VideoRecorder() {
        frameCounter = 0;
        framesPerFile = 0;
        videoPeriod = 0;
		filesWritten = 0;
		frameRate = 10;
        threadSleep = 10;
        videoOpen = false;
        enabled = true;
        paused = false;
        configured = false;
        subDir = "video";
        baseDir = "./";

    }
    
    /**
     * @brief Destroy the Video Recorder object
     * 
     */
	~VideoRecorder() {
    }
    
    /**
     * @brief Load config from ini file
     * 
     * @param cfg 
     */
    void setConfig(INIReader & cfg) {
            
        // Get config values
        baseDir = cfg.Get("application", "base_path", "./");
        subDir = cfg.Get("video", "subdir_name", "video");
        framesPerFile = cfg.GetInteger("video","frames_in_file",10);
        videoPeriod = cfg.GetInteger("video", "video_period", 3600);
        enabled = cfg.GetBoolean("video", "enabled", false);
        frameModulus = cfg.GetInteger("video", "frame_modulus", 1);
        gstPipeline = cfg.Get("video", "gst_pipeline","");
        gstExt = cfg.Get("video", "gst_ext","mp4");
        fourCC = cfg.GetInteger("video", "fourcc_tag", 0x00000021);
        threadSleep = cfg.GetInteger("video", "thread_sleep", 2);

        // Log recorder config
        LOG_S(INFO) << "Video Config:" << endl;
        LOG_S(INFO) << "Data Path: " << baseDir;
        LOG_S(INFO) << "Frames per File: " << framesPerFile << endl;
        LOG_S(INFO) << "Video Period: " << videoPeriod << endl;
        LOG_S(INFO) << "Enabled: " << enabled << endl; 
        
        // Create output dirs
        updateDataDir(baseDir);

        configured = true;
        
        // start the recording timer
        recordingTimer.start();
    }

    /**
     * @brief create a new file name from timestamp and file extension 
     */
    string getFilepath() {

        string filename = date::format("%Y-%m-%d-%H-%M-%S." + gstExt, std::chrono::system_clock::now());
		fs::path filepath = dataDir / filename;
        return filepath.c_str();
    }
	
    /**
     * @brief Open a new video file using date/time format for the file name
     * 
     */
	void openVideoFile() {
		
        string filepath = getFilepath();
		
        // Build the GStreamer pipeline
		stringstream ss;
		ss << gstPipeline;
		ss << " location=" << filepath.c_str();
		
		try {
            
            // write frames through opencv 
			//video.open(filepath.c_str(), VideoWriter::fourcc('M','P','4','V'), 10, Size(frameWidth,frameHeight));
			
            // write frames through gstreamer
            video.open(ss.str().c_str(), 0x7634706d, frameRate, Size(frameWidth,frameHeight), true);
			videoOpen = true;
		
		}
		catch (exception& e)
		{
			LOG_S(ERROR) << e.what() << '\n';
		}
	}
	
    /**
     * @brief Close the current video file
     * 
     */
	void closeVideoFile() {
		video.release();
		videoOpen = false;
	}
    
    /**
     * @brief Threaded consumer to pull frames from queue and pass to video writer object
     * 
     */
    void threadedFunction() {
        
        loguru::set_thread_name("Video Recorder Thread");
		
		LOG_S(INFO) << "Starting Video Recorder Thread";
        
        while (isThreadRunning() || frameBuffer.size() > 0) {
            
            if (frameBuffer.size() > 0 && configured) {
                
                // pull the latest frame from the queue
                lock();
                Mat img = frameBuffer.front();
                frameBuffer.pop();
                unlock();
				
                // set the frame width and height from the image
				frameWidth = img.size().width;
				frameHeight = img.size().height;
                
                // Open new video file if needed
				if (!videoOpen) {
					openVideoFile();
					framesWritten = 0;
				}
				
				// save frame and increment frame counter
				video << img;
				framesWritten++;
				
				// Close the video file if we have exceed the frames per file
                if (framesWritten >= framesPerFile) {
                    closeVideoFile();
					filesWritten++;
                }

            }
            else {
                sleep(threadSleep); 
            }
        }
        
		if (videoOpen) {
			videoOpen = false;
			video.release();
		}
        
        LOG_S(INFO) << "Ended Video Recorder Thread";
    }
	
    /**
     * @brief Get a status string from the recorder
     * 
     * @return string 
     */
	string getStatus() {
		stringstream ss;
		ss << "VR,"    << frameCounter << "," 
                        << framesWritten << "," 
                        << filesWritten << "," 
                        << frameBuffer.size();
		return ss.str();
	}
    
};

#endif