#include "source_bin.hpp"

// Initialize static member (required for non-const static members)
// the linker needs real storage for them.

// string statics
std::string SourceBin::ip_address;
std::string SourceBin::port;
std::string SourceBin::uri_list;
std::string SourceBin::sensor_id_list;
std::string SourceBin::sensor_name_list;
std::string SourceBin::config_file_path;

// int statics
int SourceBin::max_batch_size = 0;
int SourceBin::batched_push_timeout = 0;
int SourceBin::rtsp_reconnect_interval = 0;
int SourceBin::rtsp_reconnect_attempts = 0;
int SourceBin::drop_frame_interval = 0;
int SourceBin::width = 0;
int SourceBin::height = 0;
int SourceBin::latency = 0;
int SourceBin::cudadec_memtype = 0;
int SourceBin::buffer_pool_size = 0;
int SourceBin::max_latency = 0;
int SourceBin::num_extra_surfaces = 0;
int SourceBin::num_surfaces_per_frame = 0;
int SourceBin::live_source = 0;

// bool statics
bool SourceBin::drop_pipeline_eos = false;
bool SourceBin::file_loop = false;
bool SourceBin::disable_audio = false;

GstElement *SourceBin::nvmultiurisrcbin = NULL;

void SourceBin::configs() {
    const auto &config = ConfigManager::get_instance().get_config();
    // MUXER_OUTPUT_HEIGHT = config["MUXER_OUTPUT_HEIGHT"];
    // MUXER_OUTPUT_WIDTH = config["MUXER_OUTPUT_WIDTH"];
    // config.at("nvmultiurisrc").at("max-batch-size").get_to(max_batch_size);
    // auto eos = get_nested_value<bool>(config, {"nvmultiurisrc",
    // "drop-pipeline-eos"});

    max_batch_size = config.at("nvmultiurisrc").at("max-batch-size").get<int>();
    live_source = config.at("nvmultiurisrc").at("live-source").get<int>();
    batched_push_timeout =
        config.at("nvmultiurisrc").at("batched-push-timeout").get<int>();
    rtsp_reconnect_interval =
        config.at("nvmultiurisrc").at("rtsp-reconnect-interval").get<int>();
    rtsp_reconnect_attempts =
        config.at("nvmultiurisrc").at("rtsp-reconnect-attempts").get<int>();
    drop_frame_interval =
        config.at("nvmultiurisrc").at("drop-frame-interval").get<int>();
    width = config.at("nvmultiurisrc").at("width").get<int>();
    height = config.at("nvmultiurisrc").at("height").get<int>();
    latency = config.at("nvmultiurisrc").at("latency").get<int>();
    cudadec_memtype =
        config.at("nvmultiurisrc").at("cudadec-memtype").get<int>();
    buffer_pool_size =
        config.at("nvmultiurisrc").at("buffer-pool-size").get<int>();
    drop_pipeline_eos =
        config.at("nvmultiurisrc").at("drop-pipeline-eos").get<bool>();
    file_loop = config.at("nvmultiurisrc").at("file-loop").get<bool>();
    disable_audio = config.at("nvmultiurisrc").at("disable-audio").get<bool>();
    ip_address = config.at("nvmultiurisrc").at("ip-address").get<std::string>();
    port = config.at("nvmultiurisrc").at("port").get<std::string>();
    uri_list = config.at("nvmultiurisrc").at("uri-list").get<std::string>();
    sensor_id_list =
        config.at("nvmultiurisrc").at("sensor-id-list").get<std::string>();
    sensor_name_list =
        config.at("nvmultiurisrc").at("sensor-name-list").get<std::string>();
    config_file_path =
        config.at("nvmultiurisrc").at("config-file-path").get<std::string>();
    // ****************************config_files*****************************
    // /opt/nvidia/deepstream/deepstream/service-maker/sources/apps/cpp/deepstream_test5_app/test5_b16_dynamic_source.yaml
    // ****************************source_config_files*****************************
    // /opt/nvidia/deepstream/deepstream/service-maker/sources/apps/cpp/deepstream_test5_app/source_list_dynamic.yaml
    max_latency = config.at("nvmultiurisrc").at("max-latency").get<int>();
    num_extra_surfaces =
        config.at("nvmultiurisrc").at("num-extra-surfaces").get<int>();
    num_surfaces_per_frame =
        config.at("nvmultiurisrc").at("num-surfaces-per-frame").get<int>();
}

// Definition of static function
void SourceBin::decodebin_child_added(GstChildProxy *child_proxy,
                                      GObject *object, gchar *name,
                                      gpointer user_data) {
    (void)child_proxy;  // This explicitly marks it as unused

    StreamData *data = static_cast<StreamData *>(user_data);
    gint source_id = data->source_id;
    // gint source_id = (*(gint *) user_data);

    g_print(
        "Decodebin child added %s for stream_id %d"
        "\n",
        name, source_id);
    if (g_strrstr(name, "decodebin") == name) {
        g_signal_connect(G_OBJECT(object), "child-added",
                         G_CALLBACK(decodebin_child_added), user_data);
    }

    if (g_strrstr(name, "nvv4l2decoder") == name) {
        if (data->prop.integrated) {
            g_object_set(object, "enable-max-performance", TRUE, NULL);
            g_object_set(object, "bufapi-version", TRUE, NULL);
            g_object_set(object, "drop-frame-interval", 0, NULL);
            g_object_set(object, "num-extra-surfaces", 0, NULL);
        } else {
            g_object_set(object, "gpu-id", GPU_ID, NULL);
        }
    }
}

// Definition of static function
void SourceBin::cb_newpad(GstElement *decodebin, GstPad *pad,
                          gpointer user_data, gboolean *flag) {
    (void)decodebin;  // This explicitly marks it as unused
    (void)flag;       // This explicitly marks it as unused
    StreamData *data = static_cast<StreamData *>(user_data);
    gint source_id = data->source_id;
    GstElement *streammux = data->streammux;
    // gint source_id = (*(gint *) data);
    g_print(
        "In cb_newpad for stream_id %d"
        "\n",
        source_id);
    GstCaps *caps = gst_pad_query_caps(pad, NULL);
    const GstStructure *str = gst_caps_get_structure(caps, 0);
    const gchar *name = gst_structure_get_name(str);

    //    g_print ("decodebin new pad %s\n", name);
    g_print(
        "decodebin new pad %s for stream_id %d"
        "\n",
        name, source_id);
    if (!strncmp(name, "video", 5)) {
        gchar pad_name[16] = {0};
        GstPad *sinkpad = NULL;
        g_snprintf(pad_name, 15, "sink_%u", source_id);
        sinkpad = gst_element_request_pad_simple(
            streammux, pad_name);  // gst_element_get_request_pad

        if (!sinkpad) {
            g_printerr(
                "Streammux request sink pad failed for stream_id %d or "
                ". Exiting. \n",
                source_id);
        }
        if (gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK) {
            g_printerr(
                "Failed to link decodebin bin to pipeline for stream_id %d or "
                ". Exiting.\n",
                source_id);
        } else {
            //            g_print ("Decodebin linked to pipeline\n");
            g_print(
                "Decodebin linked to pipeline for stream_id %d"
                "\n",
                source_id);
        }
        gst_object_unref(sinkpad);
    }
}

// Definition of static function
GstElement *SourceBin::create_uridecode_bin(guint index, gchar *filename,
                                            GstElement *streammux,
                                            cudaDeviceProp prop) {
    GstElement *decodebin = NULL;
    gchar decodebin_name[16] = {};
    // Create data structure for callbacks
    StreamData *stream_data = new StreamData{(int)index, streammux, prop};

    //    g_print ("creating uridecodebin for [%s]\n", filename);
    g_print("Creating uridecodebin for stream_id %d or stream %s \n", index,
            filename);
    // g_source_id_list[index] = index;
    g_snprintf(decodebin_name, 15, "source-bin-%02d", index);
    decodebin = gst_element_factory_make("uridecodebin", decodebin_name);
    g_object_set(G_OBJECT(decodebin), "uri", filename, NULL);
    g_signal_connect(G_OBJECT(decodebin), "pad-added", G_CALLBACK(cb_newpad),
                     stream_data);  //&g_source_id_list[index]
    g_signal_connect(
        G_OBJECT(decodebin), "child-added", G_CALLBACK(decodebin_child_added),
        stream_data);  //&g_source_id_list[index] //&stream_data->source_id
    // g_source_enabled[index] = TRUE;

    return decodebin;
}

// static void check_versions() {
//     guint major, minor, micro, nano;
//     gst_version(&major, &minor, &micro, &nano);
//     g_print("GStreamer version: %u.%u.%u.%u\n", major, minor, micro, nano);

//     // Check if nvmultiurisrcbin is available
//     GstElementFactory *factory =
//     gst_element_factory_find("nvmultiurisrcbin"); if (factory) {
//         // Get the plugin name from the factory
//         const gchar *plugin_name =
//         gst_plugin_feature_get_plugin_name(GST_PLUGIN_FEATURE(factory));

//         // Find the plugin in the registry
//         GstRegistry *registry = gst_registry_get();
//         GstPlugin *plugin = gst_registry_find_plugin(registry, plugin_name);

//         if (plugin) {
//             const gchar *version = gst_plugin_get_version(plugin);
//             g_print("nvmultiurisrcbin plugin: %s, version: %s\n",
//             plugin_name, version); gst_object_unref(plugin);
//         } else {
//             g_print("nvmultiurisrcbin found (plugin: %s), but couldn't get
//             version\n", plugin_name);
//         }
//         gst_object_unref(factory);
//     } else {
//         g_print("nvmultiurisrcbin not found\n");
//     }
// }

// static void check_versions() {
//     guint major, minor, micro, nano;
//     gst_version(&major, &minor, &micro, &nano);
//     g_print("GStreamer version: %u.%u.%u.%u\n", major, minor, micro, nano);

//     // Check nvmultiurisrcbin version
//     GstPluginFeature *feature = gst_registry_find_feature(
//         gst_registry_get(), "nvmultiurisrcbin", GST_TYPE_ELEMENT_FACTORY);
//     if (feature) {
//         const gchar *plugin_name =
//         gst_plugin_feature_get_plugin_name(feature); GstPlugin *plugin =
//         gst_registry_find_plugin(gst_registry_get(), plugin_name); if
//         (plugin) {
//             const gchar *version = gst_plugin_get_version(plugin);
//             g_print("nvmultiurisrcbin plugin version: %s\n", version);
//             gst_object_unref(plugin);
//         }
//         gst_object_unref(feature);
//     }
// }

GstElement *SourceBin::create_nv_multi_urisrc_bin(std::string filenames) {
    configs();
    g_print("Creating nvmultiurisrcbin for stream %s \n", filenames.c_str());
    // g_source_id_list[index] = index;
    gchar nvmultiurisrcbin_name[32] = {};
    // the buffer can hold 31 characters + 1 null terminator.
    // In g_snprintf() the second argument is the maximum number of characters
    // (including the '\0') that can be written into the buffer.
    g_snprintf(nvmultiurisrcbin_name, sizeof(nvmultiurisrcbin_name),
               "nvmultiurisrc-bin-%02d", 0);
    nvmultiurisrcbin =
        gst_element_factory_make("nvmultiurisrcbin", nvmultiurisrcbin_name);
    if (!nvmultiurisrcbin) {
        std::cerr << "Failed to create nvmultiurisrcbin" << std::endl;
        return NULL;
    }
    // // Try setting a config file that enables REST API
    // // g_object_set(G_OBJECT(nvmultiurisrcbin), "config-file-path",
    // "/etc/deepstream/rest_api.conf", NULL);
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "uri-list",
    // ""/*filenames.c_str()*/, NULL); g_object_set(G_OBJECT(nvmultiurisrcbin),
    // "max-batch-size", 20/*(gint)filenames.size()*/, NULL);
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "live-source", 1, NULL); //1 for
    // RTSP/camera, 0 for file g_object_set(G_OBJECT(nvmultiurisrcbin),
    // "batched-push-timeout", 33333, NULL); //1 for RTSP/camera, 0 for file
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "rtsp-reconnect-interval", 1,
    // NULL);
    // // g_object_set(G_OBJECT(nvmultiurisrcbin), "rtsp-reconnect-attempts",
    // 10, NULL); g_object_set(G_OBJECT(nvmultiurisrcbin), "drop-pipeline-eos",
    // TRUE, NULL); g_object_set(G_OBJECT(nvmultiurisrcbin),
    // "drop-frame-interval", 5, NULL); //Skip frames if decoding lags behind.
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "cudadec-memtype", 0, NULL); //
    // Memory type for CUDA decoding (0=default, 1=NVBUF_MEM_CUDA_PINNED,
    // 2=NVBUF_MEM_CUDA_DEVICE, 3=NVBUF_MEM_CUDA_UNIFIED).
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "latency", 200, NULL); //Network
    // jitter buffer latency (milliseconds). Used for RTSP.
    // g_object_set(G_OBJECT(nvmultiurisrcbin), "sensor-id-list",
    // ""/*"UniqueSensorId1"*/, NULL); g_object_set(G_OBJECT(nvmultiurisrcbin),
    // "sensor-name-list", ""/*"UniqueSensorName1"*/, NULL);

    // The property **`buffer-pool-size`** of `nvmultiurisrcbin` is **the number
    // of decoded frame buffers allocated per source stream** in the internal
    // buffer pool.
    // ### 🔎 Details
    // * Each URI (RTSP/file) source inside `nvmultiurisrcbin` uses NVIDIA’s
    // decoder (`nvdec` / `nvv4l2decoder`).
    // * The decoder requires a pool of surfaces (video frame buffers in GPU/CPU
    // memory) that it cycles through while decoding.
    // * `buffer-pool-size` defines **how many such surfaces per stream** are
    // preallocated and kept ready.

    // ### ⚖️ Trade-offs

    // * **Small pool size**

    // * Saves GPU/CPU memory.
    // * But if your pipeline lags, or downstream elements (like inference or
    // tiler) are slower, the decoder may run out of free buffers → frame drops
    // or stuttering.

    // * **Large pool size**

    // * Reduces risk of frame drops during spikes in processing latency.
    // * Increases GPU memory usage (each 1080p NV12 buffer is \~6 MB, so with
    // 16 buffers → \~96 MB per stream).

    // ### 📌 Default

    // * If you don’t set it, the plugin picks an internal default (usually
    // `8`).
    // * Many NVIDIA sample configs set `16` for stable real-time decoding.
    // means each camera/file gets 16 GPU decode buffers reserved.

    // ### ✅ When to tune

    // * If you have **high-resolution streams (4K, 8K)** or **many concurrent
    // sources**, and see frame drops → increase `buffer-pool-size` (e.g., `24`
    // or `32`).
    // * If you are memory-constrained (e.g., Jetson devices), you can lower it
    // (but risk frame loss).

    // gchar *file_uri = g_strdup("file:///root/Put.mp4"); //=
    // g_strdup_printf("file://%s", filename);
    // g_object_set(G_OBJECT(nvmultiurisrcbin),
    //             "uri-list", "",
    //             "max-batch-size",20,
    //             "sensor-id-list","",
    //             "width", 1920,
    //             "height", 1080,
    //             // "gpu-id", GPU_ID,
    //             "sensor-name-list", "",
    //             "ip-address", "localhost",
    //             "port", "9456",
    //             "batched-push-timeout", 33000,
    //             NULL);
    // g_free(file_uri);

    g_object_set(G_OBJECT(nvmultiurisrcbin), "port", port.c_str(), nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "ip-address", ip_address.c_str(),
                 nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "batched-push-timeout",
                 batched_push_timeout, nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "max-batch-size", max_batch_size,
                 nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "drop-pipeline-eos",
                 drop_pipeline_eos, nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "rtsp-reconnect-interval",
                 rtsp_reconnect_interval, nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "live-source", live_source,
                 nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "width", width, nullptr);
    g_object_set(G_OBJECT(nvmultiurisrcbin), "height", height, nullptr);

    if (!nvmultiurisrcbin) {
        std::cerr << "Failed to create nvmultiurisrcbin" << std::endl;
        return NULL;
    }

    // // Set to READY state first to initialize REST server
    // gst_element_set_state(nvmultiurisrcbin, GST_STATE_READY);

    // // Wait a bit for initialization
    // GstState state, pending;
    // gst_element_get_state(nvmultiurisrcbin, &state, &pending, 2 *
    // GST_SECOND);

    // // Then set to PLAYING
    // gst_element_set_state(nvmultiurisrcbin, GST_STATE_PLAYING);
    // gst_element_get_state(nvmultiurisrcbin, &state, &pending, 2 *
    // GST_SECOND);

    return nvmultiurisrcbin;
}