/** 
 * @defgroup gstreamer gstreamer
 * gStreamer related routines
 * @{
 */

#include "main.h"
#include "prefs.h"
#include "gui.h"
#include "gstreamer.h"


/************************** GTK-Callbacks **************************/
/**
 * gStreamer sent another frame for this source
 */
gboolean cb_gstr_frame_received(GstElement * element, GstBuffer * buffer,
                               GstPad * pad, struct video_src *source)
{
    GdkPixbuf *pixbuf;
    unsigned char tmp, *input;
    int i;

    if((source) && (source == src_get()))
    {
        _VERBOSE6 printf("%s(): \"%s\"\n", __func__, source->name);
        
        
        /*
         * check if capabilites changed 
         */
        if(src_get()->caps != GST_BUFFER_CAPS(buffer)) 
        {
            _VERBOSE3 printf("%s(): capabilities changed\n", __func__);
            src_caps_init(source, buffer);
        }
        
        
        /*
         * only capture if we should 
         */
        if(source->do_capture) 
        {
            
           
            /*
             * get buffer 
             */
            input = GST_BUFFER_DATA(buffer);
    
   
            /*
             * create pixbuf from input-buffer 
             */
             pixbuf = gdk_pixbuf_new_from_data(input,
                                         GDK_COLORSPACE_RGB,
                                         FALSE, 8, source->width,
                                         source->height,
                                         source->width*3, NULL,
                                         NULL);   
            
            /*
             * copy pixbuf 
             */
            if(GDK_IS_PIXBUF(source->gst_input_pixbuf))
            {
                gdk_pixbuf_copy_area(pixbuf, 0, 0, gdk_pixbuf_get_width(pixbuf),
                                     gdk_pixbuf_get_height(pixbuf), source->gst_input_pixbuf, 0, 0);
                
            }
    
            
            /*
             * correct wrong-endianness 
             */
            if(source->swap_rb) 
            {
    
                input = gdk_pixbuf_get_pixels(GDK_PIXBUF(source->gst_input_pixbuf));
                
                /*
                 * exchange R <-> B 
                 */
                for(i = 0;
                    i < (source->width) *(source->height); i++) 
                {
                    /*
                     * B -> tmp 
                     */
                    tmp = input[3 * i];
                    /*
                     * R -> B 
                     */
                    input[3 * i] = input[3 * i + 2];
                    /*
                     * tmp -> R 
                     */
                    input[3 * i + 2] = tmp;
                }
            }
            
            /*
             * set flag to draw in idle-function 
             */
            source->draw_dst =
                source->draw_source = TRUE;
            
            
        }
        else
        {
           _VERBOSE2 printf("%s(): not capturing...\n", __func__);
        }
    }
    else
    {
        _ERROR printf("%s(): NULL/invalid-pointer received!\n", __func__);
    }
    
    return TRUE;
}


/**
 * handle message from gstreamer-bus 
 */
gboolean cb_gstr_handle_message(GstBus * bus, GstMessage * message, 
                                struct video_src *source)
{
    GstState state;
    GError *error;

    
    _VERBOSE1 printf("%s(): %s\n", __func__, GST_MESSAGE_TYPE_NAME(message));
    
    switch (GST_MESSAGE_TYPE(message)) 
    {
        
        case GST_MESSAGE_STATE_CHANGED:
            gst_message_parse_state_changed(message, NULL, &state, NULL);
    
            switch (state) {
                case GST_STATE_PLAYING:
                    break;
        
                default:
                    _VERBOSE4
                        printf
                        ("gst_handle_message(): Unhandled state-change... (non fatal)\n");
                    break;
                }
                break;
    
                
        case GST_MESSAGE_EOS:
            /*
             * lost stream: halt capture 
             */
            _ERROR printf("gst_handle_message(): End-of-stream.\n");
            break;
    
        
        case GST_MESSAGE_ERROR:
            /*
             * handle error 
             */
            gst_message_parse_error(message, &error, NULL);

            /*
             * error dialog 
             */
            gui_dialog("%s: \"%s\"", source->name, error->message);
            _ERROR printf("gst_handle_message(): Gstreamer error: \"%s\"\n",
                          error->message);
            /*
             * halt capture 
             */
            g_error_free(error);
            break;
    
        
        default:
            _VERBOSE4
                printf
                ("gst_handle_message(): Unhandled message-type: \"%s\" (non fatal)\n",
                 GST_MESSAGE_TYPE_NAME(message));
                 
            break;
    }
    return TRUE;
}



/**
 * use-tuner checkbutton toggled
 */
gboolean cb_tuner_use_changed(GtkWidget * widget)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get())
    {
        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
            gtk_widget_set_sensitive(glade_xml_get_widget
                                     (gui_get_xml(), "entry_input_channel"),
                                     TRUE);
            if(!src_get()->tuner)
                src_get()->tuner = (xmlChar *)
                gtk_entry_get_text(GTK_ENTRY
                                   (glade_xml_get_widget
                                    (gui_get_xml(), "entry_input_channel")));
        }
        else {
            gtk_widget_set_sensitive(glade_xml_get_widget
                                     (gui_get_xml(), "entry_input_channel"),
                                     FALSE);
            if(src_get()->tuner)
                src_get()->tuner = NULL;
        }
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return TRUE;
}

/**
 * tuner-string changed
 */
gboolean cb_tuner_changed(GtkWidget * widget)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get())
    {
        src_get()->tuner = (xmlChar *)
            gtk_entry_get_text(GTK_ENTRY(widget));
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return TRUE;
}

/**
 * click into/out of v4l tuner-string
 */
gboolean cb_tuner_changed_focus(GtkWidget * widget, GdkEventFocus * focus)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get())
    {
        src_get()->tuner = (xmlChar *)
            gtk_entry_get_text(GTK_ENTRY(widget));
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return FALSE;
}

/**
 * refresh-gStreamer pipeline button clicked
 */
gboolean cb_gstr_refresh(GtkWidget * widget)
{
    const gchar *pipeline;

    _VERBOSE2 printf("cb_gst_refresh(): reinitializing gStreamer\n");

    if(src_get())
    {
        /*
         * get pipeline-description 
         */
        if((pipeline =
            gtk_entry_get_text(GTK_ENTRY
                               (glade_xml_get_widget
                                (gui_get_xml(), "entry_gst_string")))))
        {
    
            /*
             * check pipeline for validity 
             */
            if(gstr_pipeline_is_valid((gchar *) pipeline)) {
    
    
                /*
                 * re-allocate gStreamer stuff for this source 
                 */
                if(gstr_source_init(src_get()) == 0)
                {
    
                    /*
                     * re-enable everything 
                     */
    
                }
            }
        }
        
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return FALSE;
}

/**
 * click into/out of gStreamer-pipeline string entry
 */
gboolean cb_gstr_pipeline_changed_focus(GtkWidget * entry, GdkEventFocus * focus)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get())
    {
        if(xmlStrcmp
           (src_get()->gst_string,
            (xmlChar *) gtk_entry_get_text(GTK_ENTRY(entry))) != 0)
        {
            src_get()->gst_string =
                (xmlChar *) g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
            _VERBOSE1 printf("gst_string_changed(): \"%s\"\n",
                             src_get()->gst_string);
        }
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return FALSE;
}

/**
 * callback to constantly check 
 * if the pipeline-description is valid 
 */
gboolean cb_gstr_pipeline_validate(GtkWidget * entry)
{
    const gchar *text;

    
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get()
       && (text = gtk_entry_get_text(GTK_ENTRY(entry)))) {

        /*
         * validate 
         */
        if(gstr_pipeline_is_valid((gchar *) text)) {
            _VERBOSE2 printf("gst_string_validate(): valid\n");

            /*
             * switch to black 
             */
            entry->style->text->red = 0;
            entry->style->text->green = 0;
            entry->style->text->blue = 0;

            /*
             * enable refresh-button 
             */
            gtk_widget_set_sensitive(glade_xml_get_widget
                                     (gui_get_xml(),
                                      "button_gst_refresh"), TRUE);
        }
        else {
            _VERBOSE2 printf("gst_string_validate(): invalid\n");

            /*
             * switch to red 
             */
            entry->style->text->red = 60000;
            entry->style->text->green = 0;
            entry->style->text->blue = 0;

            /*
             * disable refresh-button 
             */
            gtk_widget_set_sensitive(glade_xml_get_widget
                                     (gui_get_xml(),
                                      "button_gst_refresh"), FALSE);
        }

    }
    return FALSE;
}

/**
 * gStreamer pipeline-string changed
 */
gboolean cb_gstr_pipeline_changed(GtkWidget * entry)
{
    
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(src_get())
    {
        
        if(xmlStrcmp
           (src_get()->gst_string,
            (xmlChar *) gtk_entry_get_text(GTK_ENTRY(entry))) != 0)
        {
            src_get()->gst_string =
                (xmlChar *) g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
            _VERBOSE1 printf("gst_string_changed(): \"%s\"\n",
                             src_get()->gst_string);
        }
    }
    else
    {
        _ERROR printf("%s(): NULL-pointer received!\n", __func__);
    }
    return TRUE;
}
/************************** Module **************************/
/**
 * free gStreamer stuff of a source 
 */
void gstr_source_free(struct video_src *source)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
      
    if(source->bin)
    {
        /* stop capturing */
        if(source->do_capture)
        {
            gstr_capture_stop(source);
        }
        gst_element_set_state(source->bin, GST_STATE_NULL);
        gst_object_unref(GST_OBJECT(source->bin));
        source->bin = NULL;
    }
    else
    {
        _VERBOSE1 printf("%s(): NULL pointer received!\n", __func__);
    }
}

/**
 * initialize gStreamer portion of this source 
 */
int gstr_source_init(struct video_src *source)
{
    GError *error_ptr = NULL;
    GstTuner *tuner;
    GstIterator *iter;
    GParamSpec **property_specs;
    guint prop_count, i;
    GParamSpec *prop_param;
    GValue tmp_val = {0,};
    
    _VERBOSE2 printf("%s()\n", __func__);
    
    if((source))
    {
        /* remove previous bin if there is one */
        gstr_source_free(source);
        
        source->bin = gst_parse_launch((const gchar *) source->gst_string,
                                    &error_ptr);
        
        
        if((source->bin)) 
        {
            /*
             * check for errors 
             */
            if(error_ptr) 
            {
                gui_dialog("gStreamer error: \"%s\"", error_ptr->message);
                gstr_source_free(source);
                return -1;
            }

            
            /* find source element */
            iter = gst_bin_iterate_sources(GST_BIN(source->bin));
            
            if(gst_iterator_next(iter, (gpointer) &(source->gst_source)) == GST_ITERATOR_OK)
            {
                if(source->gst_source)
                {
                    /* get all properties */
                    property_specs = g_object_class_list_properties
                                        (G_OBJECT_GET_CLASS(source->gst_source),
                                        &prop_count);
                    
                    for(i = 0; i < prop_count; i++)
                    {
                        prop_param = property_specs[i];
                        
                        _VERBOSE1 printf("%s(): property: \"%s\"\n",
                                            __func__, 
                                            g_param_spec_get_name(prop_param));
                    }
                    
                    g_free(property_specs);
                    
                    /* analyze source element */
                    _VERBOSE1 printf("%s(): Found source \"%s\" in pipeline\n", __func__,
                                        gst_element_get_name(gst_element_get_factory(source->gst_source)));
                    
                }
                else
                {
                    _ERROR printf("%s(): NULL source-element found in pipeline-description!\n",
                        __func__);
                    gui_dialog("NULL source-element found in pipeline-description!");
                    gstr_source_free(source);
                    return -1;
                }
            }
            
            /* no "source" element */
            else
            {
                _ERROR printf("%s(): No source-element found in pipeline-description!\n",
                        __func__);
                gui_dialog("No source-element found in pipeline-description!");
                gstr_source_free(source);
                return -1;
            }
            
            /* find sink element */
            iter = gst_bin_iterate_sinks(GST_BIN(source->bin));
            
            if(gst_iterator_next(iter, (gpointer) &(source->gst_sink)) == GST_ITERATOR_OK)
            {
                
                /* do we have a fakesink? */
                if(strcmp(gst_element_get_name(
                            gst_element_get_factory(source->gst_sink)),
                          "fakesink") != 0)
                {
                    _ERROR printf("%s(): Sink is not \"fakesink\"!\n",
                                        __func__);
                    gui_dialog("sink-element must be a \"fakesink\"!");
                    gstr_source_free(source);
                    return -1;
                }
                    
                /* set "signal-handoffs" property */
                g_value_init(&tmp_val, G_TYPE_BOOLEAN);
                g_value_set_boolean(&tmp_val, TRUE);
                
                g_object_set_property(G_OBJECT(source->gst_sink), 
                    "signal-handoffs", &tmp_val);
                
            }
            /* no "sink" element */
            else
            {
                _ERROR printf("%s(): No sink-element found in pipeline-description!\n",
                            __func__);
                gui_dialog("No sink-element found in pipeline-description!");
                gstr_source_free(source);
                return -1;
            }
            
            
            /*
             * configure bus for pipeline 
             */
            if((source->bus = gst_pipeline_get_bus(GST_PIPELINE(source->bin))))
            {

                /*
                 * switch pipeline into ready-state 
                 */
                gst_element_set_state(GST_ELEMENT(source->bin), GST_STATE_PAUSED);
    
                /*
                 * switch channel if necessary
                 * @todo better v4l implementation
                 */
                if(source->tuner) 
                {
                    tuner =
                        GST_TUNER(source->bin);
    
                    /*
                     * got tuner? 
                     */
                    if(tuner) {
                        gst_tuner_set_channel(tuner,
                                              gst_tuner_find_channel_by_name
                                              (tuner, (gchar *) source->tuner));
                    }
    
                    /*
                     * disable tuner 
                     */
                    else {
                        _ERROR printf("%s(): ERROR: Failed to switch tuner to \"%s\"\n",
                               __func__, source->tuner);
                        source->tuner = NULL;
                    }
                }
    
                /*
                 * callback for when the sink get's ready 
                 */
                gst_bus_add_signal_watch(source->bus);
                g_signal_connect(source->bus, "message",
                                 G_CALLBACK(cb_gstr_handle_message),
                                 (gpointer) source);
                
                /* 
                 * connect gStreamer callback function of the sink bin 
                 */
                g_signal_connect(source->gst_sink,
                                 "handoff", G_CALLBACK(cb_gstr_frame_received),
                                 (gpointer) source);
    
            }
            else
            {
                _ERROR printf("%s(): Failed to get gStreamer-bus!\n",
                                __func__);
                gstr_source_free(source);
                return -1;
            }
        
        }
        else
        {
            _ERROR printf("%s(): no source->bin!\n", __func__);
            return -1;
        }
    }
    return 0;
}

/**
 * check if pipeline is a valid gStreamer pipeline
 */
gboolean gstr_pipeline_is_valid(gchar * pipeline)
{
    GstElement *bin;
    GError *error = NULL;

    _VERBOSE3 printf("pipeline_check_valid(): checking \"%s\"\n", pipeline);

    /* if we capture from "v4lsrc", show v4l options-frame */
    if(strstr(pipeline, "v4lsrc"))
    {
        gtk_widget_show(glade_xml_get_widget(gui_get_xml(), "frame_v4loptions"));
    }
    else
    {
        gtk_widget_hide(glade_xml_get_widget(gui_get_xml(), "frame_v4loptions"));
    }
    
    if((bin = gst_parse_launch((const gchar *) pipeline, &error))) {
        /*
         * check for errors 
         */
        if(error) {
            _ERROR printf(" error: \"%s\"\n", error->message);
            return FALSE;
        }

        /*
         * unref object immediately 
         */
        gst_object_unref(GST_OBJECT(bin));
        return TRUE;
    }

    return FALSE;
}


/** 
 * start capture with source 
 */
void gstr_capture_start(struct video_src *source)
{

    _VERBOSE2 printf("%s()\n", __func__);
    
    if(source->bin) {
        gst_element_set_state(source->bin, GST_STATE_PLAYING);
    }
    else {
        _VERBOSE1 printf("%s(): source has no gStreamer-bin!\n", __func__);
    }
    
    source->do_capture = TRUE;
    gtk_widget_set_sensitive(glade_xml_get_widget
                             (gui_get_xml(),
                              "combobox_sources"), FALSE);
    
    /* activate "Capture" button */
    if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
                                         (glade_xml_get_widget(gui_get_xml(),
                                          "togglebutton_capture")))) == FALSE)
    {
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
                                             (glade_xml_get_widget(gui_get_xml(),
                                               "togglebutton_capture")), TRUE);
    }
    
    src_set_updateSrcGui(TRUE);
}

/**
 * stop capture with source 
 */
void gstr_capture_stop(struct video_src *source)
{
    _VERBOSE2 printf("%s()\n", __func__);
    
    if(source)
    {
        
        if(source->bin) {
            gst_element_set_state(source->bin, GST_STATE_PAUSED);
        }
        else {
            _VERBOSE1 printf("%s(): source has no gStreamer-bin!\n", __func__);
        }
    
        source->do_capture = FALSE;
    
        if(gui_get_xml())
        {
            gtk_widget_set_sensitive(glade_xml_get_widget
                                     (gui_get_xml(),
                                      "combobox_sources"), TRUE);
            
            /* deactivate "Capture" button */
            if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
                                             (glade_xml_get_widget(gui_get_xml(),
                                              "togglebutton_capture")))))
            {
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
                                                     (glade_xml_get_widget(gui_get_xml(),
                                                       "togglebutton_capture")), FALSE);
            }
        }
        
    }
    else
    {
        _ERROR printf("%s(): NULL source\n",__func__);
    }
    
    src_set_updateSrcGui(TRUE);
}



/**
 * initialize gStreamer GUI-related stuff
 */
int gstr_gui_init()
{
    /*g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "entry_gst_string")), "activate",
                     G_CALLBACK(cb_gstr_pipeline_changed), NULL);
                     
    g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "entry_gst_string")), "changed",
                     G_CALLBACK(cb_gstr_pipeline_validate), NULL);
                     
    g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "entry_gst_string")),
                     "focus-out-event",
                     G_CALLBACK(cb_gstr_pipeline_changed_focus), NULL);

    g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "entry_input_channel")), "activate",
                     G_CALLBACK(cb_tuner_changed), NULL);
                     
    g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "entry_input_channel")),
                     "focus-out-event", G_CALLBACK(cb_tuner_changed_focus),
                     NULL);
                     
    g_signal_connect(G_OBJECT
                     (glade_xml_get_widget
                      (gui_get_xml(), "checkbutton_tuner")), "toggled",
                     G_CALLBACK(cb_tuner_use_changed), NULL);
                     
    g_signal_connect(G_OBJECT
                 (glade_xml_get_widget
                  (gui_get_xml(), "button_gst_refresh")), "clicked",
                 G_CALLBACK(cb_gstr_refresh), NULL);*/
                 
    return 0;
}

/** 
 * @} 
 */
