#include "ANSGStreamer.h"
namespace ANSCENTER {
    //void ANSGStreamerReader::HandleNetworkLoss() {
    //    guint64 currentTimestamp = g_get_monotonic_time(); // Get current time
    //    guint64 timeDiff = currentTimestamp - lastDataTimestamp;
    //    if (timeDiff > dataLossThreshold) { 
    //        // Set the flag to indicate network loss
    //        // Pause the pipeline
    //        Stop();
    //        // Attempt reconnection logic
    //        gboolean reconnected = FALSE;
    //        int attempts = 0;
    //        const int max_attempts = 5;
    //        while (!reconnected && attempts < max_attempts) {
    //            // Try to reconnect by setting the source state to NULL and then PLAYING again
    //            gst_element_set_state(gst_pipeline, GST_STATE_NULL);
    //            GstStateChangeReturn ret = gst_element_set_state(gst_pipeline, GST_STATE_PLAYING);
    //            if (ret == GST_STATE_CHANGE_ASYNC) {
    //                // Wait for the state change to complete
    //                GstState current_state;
    //                gst_element_get_state(gst_pipeline, &current_state, NULL, GST_CLOCK_TIME_NONE);
    //                if (current_state == GST_STATE_PLAYING) {
    //                    reconnected = TRUE;
    //                }
    //                else {
    //                    attempts++;
    //                    g_print("Reconnection attempt %d failed\n", attempts);
    //                    g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
    //                }
    //            }
    //            else {
    //                attempts++;
    //                g_print("Reconnection attempt %d failed\n", attempts);
    //                g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
    //            }
    //        }

    //        if (reconnected) {
    //            g_print("Reconnection successful\n");
    //            // Resume playback
    //            gst_element_set_state(gst_pipeline, GST_STATE_PLAYING);
    //        }
    //        else {
    //            g_print("Reconnection failed\n");
    //            // Handle the case where reconnection fails
    //        }
    //    }
    //}
   
    void ANSGStreamerReader::CheckLicense() {
        try {
            _licenseValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(_licenseKey, 1008, "ANSGS");//Default productId=1005
        }
        catch (std::exception& e) {
            this->_logger->LogFatal("ANSGStreamerReader::CheckLicense()", e.what());
        }
    }
    bool ANSGStreamerReader::Init(std::string licenseKey, std::string URL) {
        CheckLicense();
        if (!_licenseValid) {
            this->_logger->LogFatal("ANSGStreamerReader::Init", "Invalid license");
            return false;
        } 
        try {
            gst_init(NULL, NULL);
            std::string _pipeline = URL;
            _licenseKey = licenseKey;
            width = 0;
            height = 0;
            timer_id = 0;
            dataLossThreshold=1000; // data loss in 1000ms
            lastDataTimestamp = g_get_monotonic_time();
            currentDataTimestamp = g_get_monotonic_time();
            std::string searchString = "urisourcebin";
            size_t found = URL.find(searchString);
            if (found != std::string::npos) {
                _pipeline = URL;
            }
            else {
                _pipeline = "urisourcebin uri=" + URL + " ! decodebin ! videoconvert ! video/x-raw ! appsink name=cvdsink";
            }
            //this->_logger->LogInfo("ANSGStreamerReader::Init. URL", _pipeline);
            return CreatePipeline(_pipeline);
        }
        catch (std::exception& e)
        {
            this->_logger->LogFatal("ANSGStreamerReader::Init. Error", e.what());
            return false;
        }
        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            this->_logger->LogFatal("ANSGStreamerReader::Init", "Unknown error");
            return false;
        }

    }
    void ANSGStreamerReader::Destroy() {
        try {
            gst_element_set_state(gst_pipeline, GST_STATE_NULL);
            gst_object_unref(gst_pipeline);
            gst_object_unref(gst_bus);
            if (timer_id != 0) {
                g_source_remove(timer_id);
            }
        }
        catch (std::exception& e)
        {
            this->_logger->LogFatal("ANSGStreamerReader::Destroy. Error", e.what());
        }
        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            this->_logger->LogFatal("ANSGStreamerReader::Destroy", "Unknown error");
        }
    }
    bool ANSGStreamerReader::CreatePipeline(std::string pipeline)
    {
        try {
            gst_pipeline = gst_parse_launch(pipeline.c_str(), NULL);
            gst_bus = gst_element_get_bus(gst_pipeline);
            cvdsink = gst_bin_get_by_name(GST_BIN(gst_pipeline), "cvdsink");
            gst_app_sink_set_emit_signals(GST_APP_SINK(cvdsink), 1);
            gst_app_sink_set_drop(GST_APP_SINK(cvdsink), 1);
            gst_app_sink_set_max_buffers(GST_APP_SINK(cvdsink), 1);
            gst_app_sink_set_caps(GST_APP_SINK(cvdsink), gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "BGR", NULL));
            gst_bus_add_signal_watch(gst_bus);
            g_signal_connect(gst_bus, "message", G_CALLBACK(OnMessage), this);
            //timer_id = g_timeout_add_seconds(5, OnNetworkLoss, this);
            bool initResult= Start();
            return initResult;
        }
        catch (std::exception& e)
        {
            this->_logger->LogFatal("ANSGStreamerReader::CreatePipeline. Error", e.what());
            return false;
        }
        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            this->_logger->LogFatal("ANSGStreamerReader::CreatePipeline", "Unknown error");
            return false;
        }
    }
    void ANSGStreamerReader::Run() {
        while (g_main_context_iteration(nullptr, FALSE));
    }

    bool ANSGStreamerReader::Start() {
        bool status = false;
        for (int j = 0; j < 5; j++) {
            GstStateChangeReturn ret = gst_element_set_state(gst_pipeline, GST_STATE_PLAYING);
            if (ret == GST_STATE_CHANGE_ASYNC) {
                for (int i = 0; i < 100;i++) {
                    // Wait for the state change to complete
                    GstState current_state;
                    gst_element_get_state(gst_pipeline, &current_state, NULL, GST_CLOCK_TIME_NONE);
                    if (current_state == GST_STATE_PLAYING) {
                        status= true;
                        return status;
                    }
                    else {
                        g_print("Checking %d failed\n", i);
                        g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
                    }
                }
            }
            else {
                g_print("Reconnection attempt %d failed\n", j);
                g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
            }
        }
        return status;
    }

    bool ANSGStreamerReader::Stop() {
        bool status = false;
        for (int j = 0; j < 5; j++) {
            GstStateChangeReturn ret = gst_element_set_state(gst_pipeline, GST_STATE_PLAYING);
            if (ret == GST_STATE_CHANGE_ASYNC) {
                for (int i = 0; i< 100;i++) {
                    // Wait for the state change to complete
                    GstState current_state;
                    gst_element_get_state(gst_pipeline, &current_state, NULL, GST_CLOCK_TIME_NONE);
                    if (current_state == GST_STATE_PAUSED) {
                        status = true;
                        return status;
                    }
                    else {
                        g_print("Checking %d failed\n", i);
                        g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
                    }
                }
            }
            else {
                g_print("Reconnection attempt %d failed\n", j);
                g_usleep(G_USEC_PER_SEC); // Wait for a second before the next attempt
            }
        }
        return status;
    }

    std::tuple<int, int, unsigned char*> ANSGStreamerReader::ProcessSamples()
    {
        try {
            g_signal_emit_by_name(cvdsink, "pull-sample", &sample, NULL);
            //currentDataTimestamp = g_get_monotonic_time();
            if (sample != nullptr) {
                buffer = gst_sample_get_buffer(sample);
                caps = gst_sample_get_caps(sample);
                std::tuple<int, int, unsigned char*> result;
                unsigned char* data = nullptr;
                if ((caps != nullptr) && (buffer != nullptr))
                {
                    GstStructure* structure = gst_caps_get_structure(caps, 0);
                    if (structure != nullptr) {
                        if (gst_structure_get_int(structure, "width", &width) && 
                            gst_structure_get_int(structure, "height", &height))
                        {
                            gst_buffer_map(buffer, &map, GST_MAP_READ);
                            result = std::make_tuple(width, height, map.data);
                            //lastDataTimestamp = currentDataTimestamp;
                            buffer_ts = GST_BUFFER_PTS(buffer);
                            timeStr = std::to_string(buffer_ts);
                        }
                        else {
                            result = std::make_tuple(width, height, data);
                            this->_logger->LogError("ANSGStreamerReader::ProcessSamples.", " Cannot get structure");
                        }
                    }
                    else {
                        result = std::make_tuple(width, height, data);
                        this->_logger->LogError("ANSGStreamerReader::ProcessSamples.", " Structure is null");
                    }
        
                }
                else {
                    result = std::make_tuple(width, height, data);
                    this->_logger->LogError("ANSGStreamerReader::ProcessSamples.", " Cap and buffer are null");
                }
                UnRefSample();
                //std::cout << "Time: " << timeStr << std::endl;
                Run();
                return result;
            }
            else {
                unsigned char* data = nullptr;
                std::tuple<int, int, unsigned char*> result= std::make_tuple(width, height, data);
                this->_logger->LogError("ANSGStreamerReader::ProcessSamples.", " Sample is null");
                //std::cout << "Time: " << timeStr << std::endl;
                Run();
                return result;
            }
        }
        catch (std::exception& e)
        {
            this->_logger->LogFatal("ANSGStreamerReader::ProcessSamples. Error", e.what());
            unsigned char* data = nullptr;
            std::tuple<int, int, unsigned char*> result = std::make_tuple(width, height, data);
            //std::cout << "Time: " << timeStr << std::endl;
            Run();
            return result;
        }

        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            this->_logger->LogFatal("ANSGStreamerReader::ProcessSamples","Unknown error");
            unsigned char* data = nullptr;
            std::tuple<int, int, unsigned char*> result = std::make_tuple(width, height, data);
            //std::cout << "Time: " << timeStr << std::endl;
            Run();
            return result;
        }
        
    }

    bool ANSGStreamerReader::GetImageInfo(int& imageWidth, int& imageHeight, int& status, std::string& timeStamp)
    {
        imageWidth = width;
        imageHeight = height;
        timeStamp = timeStr;
        GstState current_state;
        gst_element_get_state(gst_pipeline, &current_state, NULL, GST_CLOCK_TIME_NONE);
        if (current_state == GST_STATE_PLAYING) {
            status =1;
        }
        else {
            status = 0;
        }
        if ((imageWidth > 0) && (imageHeight > 0)) {
            return true;
        }
        else {
            return false;
        }
    }


    void ANSGStreamerReader::UnRefSample()
    {
        try {
            if ((buffer!=nullptr)&&(map.data != nullptr))
            {
                gst_buffer_unmap(buffer, &map);
            }
            if (sample != nullptr)
            {
                gst_sample_unref(sample);
            }
        }
        catch (std::exception& e)
        {
            this->_logger->LogFatal("ANSGStreamerReader::UnRefSample. Error", e.what());
        }
        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            std::cout << "ANSGStreamerReader::UnRefSample. Unknown error";
        }

    }

    cv::Mat ANSGStreamerReader::GetImage() {
        try {
            std::tuple<int, int, unsigned char*> result = ProcessSamples();
            cv::Mat frame(std::get<1>(result), std::get<0>(result), CV_8UC3, std::get<2>(result));
            return frame;
        }
        catch (std::exception& e)
        {
            cv::Mat emptyImage;
            this->_logger->LogFatal("ANSGStreamerReader::GetImage. Error", e.what());
            return emptyImage;
        }
        catch (...) {
            // Catch any other exceptions that were not caught by previous catch blocks
            cv::Mat emptyImage;
            std::cout << "ANSGStreamerReader::JpegStringToMat. Unknown error";
            return emptyImage;
        }
    }
   

    std::string ANSGStreamerReader::MatToBinaryData(const cv::Mat& image) {
        if (!image.empty()) {
            if (image.data != nullptr) {
                try {
                    // Encode the image to a memory buffer
                    std::vector<uchar> imageData;
                    bool success = cv::imencode(".jpg", image, imageData);
                    if (!success) {
                        std::cout << "ANSGStreamerReader::MatToBinaryData. Error:" << "Failed to encode the image.";
                        return "";
                    }

                    std::string binaryData(imageData.begin(), imageData.end());
                    return binaryData;
                }
                catch (const std::exception& e) {
                    std::cout << "ANSGStreamerReader::MatToBinaryData. Error:" << e.what();
                    return "";
                }
                catch (...) {
                    // Catch any other exceptions that were not caught by previous catch blocks
                    std::cerr << "Caught unknown exception!" << std::endl;
                    return "";
                }
            }
            else return "";   
        }
        else return "";
    }

    cv::Mat ANSGStreamerReader::JpegStringToMat(const std::string& jpegStr) {
        if (jpegStr.length() > 10) {
            try {
                // Convert the string to a vector of bytes
                std::vector<uchar> jpegData(jpegStr.begin(), jpegStr.end());
                // Decode the JPEG data into a cv::Mat
                cv::Mat image = cv::imdecode(jpegData, cv::IMREAD_COLOR);
                return image;
            }
            catch (std::exception& e)
            {
                cv::Mat emptyImage;
                std::cout << "ANSGStreamerReader::JpegStringToMat. Error:" << e.what();
                return emptyImage;
            }
            catch (...) {
                // Catch any other exceptions that were not caught by previous catch blocks
                cv::Mat emptyImage;
                std::cout << "ANSGStreamerReader::JpegStringToMat. Unknown error";
                return emptyImage;
            }
        }
        else {
            cv::Mat emptyImage;
            return emptyImage;
        }
    }
}
extern "C" __declspec(dllexport) int CreateANSGSHandle(ANSCENTER::ANSGStreamerReader **Handle, const char* licenseKey, const char* URL) {
    (*Handle) = new ANSCENTER::ANSGStreamerReader();
    if (*Handle == NULL) return 0;
    else {
        bool result = (*Handle)->Init(licenseKey,URL);
        if (result) return 1;
        else return 0;
    }
}
extern "C" __declspec(dllexport) int ReleaseANSGSHandle(ANSCENTER::ANSGStreamerReader * *Handle) {
    (*Handle)->Destroy();
    delete* Handle;
    if (*Handle == NULL) return 0;
    else return 1;
}
extern "C" __declspec(dllexport) std::string GetImage(ANSCENTER::ANSGStreamerReader * *Handle) {
    cv::Mat imageResult= (*Handle)->GetImage();
    return ANSCENTER::ANSGStreamerReader::MatToBinaryData(imageResult);
}


extern "C" __declspec(dllexport) int GetImage_LV(ANSCENTER::ANSGStreamerReader * *Handle, LStrHandle detectionResult) {
    std::string st = GetImage(Handle);
    if (st.empty()) return 0;
    int size = st.length();
    if (size > 0) {
        MgErr error;
        int32 i;
        error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
        if (error != mFullErr && error != mZoneErr)
        {
            (*detectionResult)->cnt = size;
            for (i = 0; i < size; i++)
                (*detectionResult)->str[i] = (uChar)(st.c_str()[i]);  //assign the st value
            return 1;
        }
        else return 0;
    }
    else return 0;
}

extern "C" __declspec(dllexport) int GetImageInformation(ANSCENTER::ANSGStreamerReader * *Handle, int* iWidth, int* iHeight, int *status, LStrHandle timeStamp) {
    int imageWidth, imageHeight,imageStatus;
    std::string st;
    bool result = (*Handle)->GetImageInfo(imageWidth, imageHeight, imageStatus, st);
    *iWidth = imageWidth;
    *iHeight = imageHeight;
    *status = imageStatus;
    int size = st.length();
    if (size > 0) {
        MgErr error;
        int32 i;
        error = DSSetHandleSize(timeStamp, sizeof(int32) + size * sizeof(uChar));
        if (error != mFullErr && error != mZoneErr)
        {
            (*timeStamp)->cnt = size;
            for (i = 0; i < size; i++)
                (*timeStamp)->str[i] = (uChar)(st.c_str()[i]);  //assign the st value
        }
        if (result)return 1;
        else return 0;
    }
    else return 0;
    
}


extern "C" __declspec(dllexport) int Start(ANSCENTER::ANSGStreamerReader * *Handle) {
    bool result = (*Handle)->Start();
    if (result)return 1;
    else return 0;
}


extern "C" __declspec(dllexport) int Stop(ANSCENTER::ANSGStreamerReader * *Handle) {
    bool result = (*Handle)->Stop();
    if (result)return 1;
    else return 0;
}
