#ifndef _FRAMEGRABBER
#define _FRAMEGRABBER

//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 "ofThread.h"
#include "SimpleTimer.h"
#include "FrameRecorder.h"

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

class FrameGrabber : public ofThread {

private:

    unsigned int frameCounter;     
    
    int framesPerFile;
	int frameWidth;
	int frameHeight;
	int frameRate;
    int threadSleep;
    
    bool convertFrame;
    bool configured;
    bool cameraOpen;
    bool deleteFrameRecorder;

    string cameraType;
    string videoSource;
    
    INIReader cfg;
    
    fs::path dataDir;
    
    SimpleTimer recordingTimer;
	
	FrameRecorder * fr;
    VideoCapture inputVideo;
  
public:

    /**
     * @brief Construct a new Video Recorder object
     * 
     */
    FrameGrabber() {
        fr = NULL;
        frameCounter = 0;
        framesPerFile = 0;
		frameRate = 10;
        threadSleep = 10;
        convertFrame = false;
        deleteFrameRecorder = false;
    }
    
    /**
     * @brief Destroy the Video Recorder object
     * 
     */
	~FrameGrabber() {
        if (deleteFrameRecorder && fr != NULL) {
            LOG_S(WARNING) << "Deleting default frame recorder.";
            delete fr;
        }
    }
    
    /**
     * @brief Load config from ini file
     * 
     * @param cfg 
     */
    void setConfig(INIReader & cfg) {
            

        this->cfg = cfg;
        // Get config values

        framesPerFile = cfg.GetInteger("video","frames_in_file",10);
        threadSleep = cfg.GetInteger("video", "thread_sleep", 10);
        videoSource = cfg.Get("video","video_source", "/dev/video0");
        cameraType = cfg.Get("camera","camera_type", "v4l2");
        frameWidth = cfg.GetInteger("camera", "frame_width", 640);
        frameHeight = cfg.GetInteger("camera", "frame_height", 640);

        // Log recorder config
        LOG_S(INFO) << "Camera Config:";
        LOG_S(INFO) << "Video Source: " << videoSource;
        LOG_S(INFO) << "Frames Per File: " << framesPerFile;
        

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

    /**
     * @brief Set the frame recorder object
     */
    void setFrameRecorder(FrameRecorder * fr) {
        this->fr = fr;
    }

    /**
     * @brief Open the camera
     * 
     */
    void openCamera() {
        // Open video input
        if (cameraType == "v4l2") {
            inputVideo.open(videoSource, CAP_V4L2);
            // set output format
            inputVideo.set(CAP_PROP_FRAME_WIDTH, frameWidth);
            inputVideo.set(CAP_PROP_FRAME_HEIGHT, frameHeight);
            inputVideo.set(CAP_PROP_FOURCC, VideoWriter::fourcc('R', 'G', '1', '2'));
            inputVideo.set(CAP_PROP_CONVERT_RGB, 0);  
        }
        else {
            inputVideo.open(videoSource, CAP_GSTREAMER);              
        }

        if (!inputVideo.isOpened())
        {
            LOG_S(ERROR) << "Could not open the input video: " << videoSource << endl;
            return;
        }

        // Log input video format and size information
        int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC));     // Get Codec Type- Int form
        // Transform from int to char via Bitwise operators
        char EXT[] = {(char)(ex & 0XFF) , (char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24), 0};
        Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH),    // Acquire input size
                  (int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
        LOG_S(INFO) << "Input frame resolution: Width=" << S.width << "  Height=" << S.height
            << " of nr#: " << inputVideo.get(CAP_PROP_FRAME_COUNT) << endl;
        LOG_S(INFO) << "Input codec type: " << EXT << endl;
    }

    /**
     * @brief retrun true when camera is open
     * 
     * @return bool 
     */
    bool cameraIsOpen() {
        return inputVideo.isOpened();
    }

    /**
     * @brief Threaded producer to push frames to video recorder
     * 
     */
    void threadedFunction() {
        
        loguru::set_thread_name("Frame Grabber Thread");
		
		LOG_S(INFO) << "Starting Frame Grabber Thread";

        if (!inputVideo.isOpened()) {
            LOG_S(ERROR) << "No camera opened.";
            return;
        }

        // Create recorder and start
        // If there is no frame recorder defined, use the default one
        if (fr == NULL) {
            LOG_S(WARNING) << "No frame recorder provided, using default";
            fr = new FrameRecorder();
            fr->setConfig(cfg);
        }
        
        // Start the recorder
        fr->startThread();

        Mat src; // container for next video frame

        while (isThreadRunning()) {
            
            inputVideo >> src;
            fr->addFrame(src);
            frameCounter++;

        }
        
        // Close the video input
        inputVideo.release();
        fr->stopThread();
        fr->waitForThread();
        
        LOG_S(INFO) << "Ended Frame Grabber Thread";
    }
    
    /**
     * @brief Get the current number of frames received
     * 
     * @return unsigned int 
     */
    unsigned int getFrameCount() {
        return frameCounter;
    }
	
    /**
     * @brief Set the frame rate of the resulting video file
     * 
     * @param frameRate 
     */
	void setFrameRate(int frameRate) {
		this->frameRate = frameRate;
	}
	
    /**
     * @brief Get a status string from the recorder
     * 
     * @return string 
     */
	string getStatus() {
		stringstream ss;
		ss << "FG,"    << frameCounter;
		return ss.str();
	}
    
};

#endif