/** 
 * @defgroup source source
 * code to manage sources
 * @{
 */

#include "include/x11.h"
#include "include/source.h"
#include "include/gfx.h"
#include "include/prefs.h"
#include "include/util.h"



void init_source_caps(struct video_src *source, GstBuffer * buffer);
void src_handleSensor(struct video_src *source, struct sensor *sens);
    

/* local info structure */
static struct Source
{
    struct video_src *first_source;
}src;




/********************* getter/setter for local variables *********************/
struct video_src *src_get()
{
    return src.first_source;
}

/************************** GTK-Callbacks **************************/
/**
 * handle message vom gstreamer-bus 
 */
gboolean cb_gst_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 printf
            ("gst_handle_message(): Gstreamer error: \"%s\"\n",
             error->message);
        /*
         * error dialog 
         */
        /*
         * 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;
}


/**
 * gStreamer sent another frame for this source
 */ 
gboolean cb_gst_frame_received(GstElement * element, GstBuffer * buffer,
                               GstPad * pad, struct video_src *source)
{
    struct sensor *current_sensor;
    unsigned char tmp, *input, *output;
    int i;

    _VERBOSE4 printf("frame_received() - %s:\n", source->name);

    /*
     * check if capabilites changed 
     */
    if(source->caps != GST_BUFFER_CAPS(buffer)) {
        init_source_caps(source, buffer);
    }

    /*
     * get buffer 
     */
    input = GST_BUFFER_DATA(buffer);
    output = source->out_buffer;

    /*
     * correct wrong-endianness 
     */
    if(source->swap_rb) 
    {
        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;
        }
    }

    /* for every sensor try to detect something */
    for(current_sensor = source->first_sensor;
        current_sensor; current_sensor = current_sensor->next_sensor) 
    {

        /*
         * warp image 
         */
        g_warp_image(input, output,
                   source->width, source->height,
                   current_sensor->wrp_x1,
                   current_sensor->wrp_y1,
                   current_sensor->wrp_x2,
                   current_sensor->wrp_y2,
                   current_sensor->wrp_x3,
                   current_sensor->wrp_y3,
                   current_sensor->wrp_x4,
                   current_sensor->wrp_y4);

        /*
         * threshold 
         */
        g_threshold_frame(output, output,
                        source->width * source->height * 3,
                        current_sensor->thr_threshold,
                        current_sensor->thr_R,
                        current_sensor->thr_G,
                        current_sensor->thr_B);

        /*
         * track 
         */
        g_track_xy(output, source, current_sensor);
        
        /* trigger event */
        src_handleSensor(source, current_sensor);
        
        /* increase framecounts */
        (current_sensor->m_framecount)++;
        (current_sensor->key_framecount)++;
    }

    /*
     * one frame done 
     */
    (source->framecount)++;
    return TRUE;
}

/************************** Module **************************/
/**
 * trigger event upon detection
 */
void src_handleSensor(struct video_src *source, struct sensor *sens)
{
    pid_t pid;
    char tmpChar[255];
    char *tmpString, *tmpString2;

    
    
    if(sens->laser_detected)
    {
        
        if(sens->m_framecount >= (unsigned int) sens->ondelay)
        {
            
            /*
             * handle sensor 
             */
            switch (sens->out_type) 
            {
                
                /*
                 * mouse-output 
                 */
                case OUT_TYPE_MOUSE:
                    x_warp(sens->x,sens->y);
                    x_click(True, sens->buttonmask);
                    x_flush();
                    break;
    
                /*
                 * keyboard-output 
                 */
                case OUT_TYPE_KEY:
                    x_keyPress((KeySym) sens->key);
                    sens->key_framecount = 0;
                    break;
                
                /*
                 * execute program-output
                 */
                case OUT_TYPE_EXE:
                    sens->laser_detected = FALSE;
                    pid = fork();
                        
                    switch(pid)
                    {
                        /* new process */
                        case 0:
                            
                            /* provide coordinates if needed */
                            if(strstr(sens->out_program, "%x") ||
                                strstr(sens->out_program, "%y"))
                            {
                                /* replace x-coordinate */
                                snprintf((char *) &tmpChar, 255, "%d", sens->x);
                                tmpString = u_strReplace(sens->out_program, "%x", (char *) &tmpChar);
                                
                                /* replace y-coordinate */
                                if(!tmpString)
                                    tmpString = sens->out_program;
                                snprintf((char *) &tmpChar, 255, "%d", sens->y);
                                tmpString2 = u_strReplace(tmpString, "%y", (char *) &tmpChar);
                                free(tmpString);
                                if(!tmpString2)
                                    tmpString2 = tmpString;
                                
                                _VERBOSE1 printf("%s(): running \"%s\"\n",
                                    __func__, tmpString2);
                                system(tmpString2);
                                free(tmpString2);
                            }
                            else
                            {
                                _VERBOSE1 printf("%s(): running \"%s\"\n",
                                    __func__, sens->out_program);
                            
                                system(sens->out_program);
                            }
                            exit(EXIT_SUCCESS);
                            break;
                        
                        /* error */
                        case -1:
                            fprintf(stderr, "%s(): fork() error!\n",
                                __func__);
                            break;
                        
                        /* parent process */
                        default:
                            break;
                    }
                
                    
                    break;
            }
        
        }
    }
    
    /* no laser detected */
    else
    {
        
        /*
         * handle sensor 
         */
        switch (sens->out_type) 
        {
            
           
            /*
             * release mouse if no laser found 
             */
            case OUT_TYPE_MOUSE:
                x_click(False, sens->buttonmask);
                break;
            
        }

    }
}

/**
 * free list of sensors
 */
void free_sensors(struct sensor *sensors)
{
    struct sensor *current_sensor, *next_sensor;

    
    _VERBOSE1 printf("%s()\n", __func__);
    
    if(sensors) {
        current_sensor = sensors;

        while (current_sensor) {
            _VERBOSE2 printf("free_sensors(): freeing \"%s\"\n",
                             current_sensor->name);
            next_sensor = current_sensor->next_sensor;
            free(current_sensor);
            current_sensor = next_sensor;
        }
    }
}

/**
 * free list of sources
 */
void free_sources(struct video_src *sources)
{
    struct video_src *current_source, *next_source = NULL;

    _VERBOSE1 printf("%s()\n", __func__);
    
    if(sources) {
        for(current_source = sources;
            current_source; next_source = current_source->next_source) {
            _VERBOSE1 printf("free_source(): freeing \"%s\"\n",
                             current_source->name);

            /*
             * free gstreamer stuff 
             */
            if(current_source->bin) {
                gst_element_set_state(current_source->bin, GST_STATE_NULL);
                gst_object_unref(GST_OBJECT(current_source->bin));
            }

            /*
             * freeing sensors 
             */
            free_sensors(current_source->first_sensor);

            free(current_source);

            current_source = next_source;
        }
    }
}


/**
 * append a sensor to the current sensor-list 
 * @param first_sensor - head of sensor-list or NULL if first sensor
 * @param node - sensor node
 * @note we use a dtd file to validate our data, so we won't do much checks
 *       here. Decide for yourself whether this is good or bad ;)
 */
struct sensor *src_sensor_append(struct sensor *first_sensor, xmlNode * node)
{
    struct sensor *tmp_sensor, *result = NULL;
    xmlNode *tmp, *tmp2;
    char *tmp_char;
    xmlChar *tmp_char2;

    _VERBOSE2 printf("append_sensor(): \"%s\"\n",
                     xmlGetProp(node, (xmlChar *) "name"));

    if((result = calloc(1, sizeof(struct sensor)))) {
        /*
         * get name 
         */
        result->name = xmlGetProp(node, (xmlChar *) "name");
        result->laser_detected = FALSE;
        
        /* get delay */
        tmp_char = (char *) xmlGetProp(node, (xmlChar *) "on_delay");
        if(tmp_char) 
        {
            result->ondelay = atoi(tmp_char);
            xmlFree(tmp_char);
        }
        else result->ondelay = 0;
        
        /* get delay */
        tmp_char = (char *) xmlGetProp(node, (xmlChar *) "off_delay");
        if(tmp_char)
        {
            result->offdelay = atoi(tmp_char);
            xmlFree(tmp_char);
        }
        else result->offdelay = 0;
            
        
        /* get delay */
        tmp_char = (char *) xmlGetProp(node, (xmlChar *) "repeat_delay");
        if(tmp_char)
        {
            result->repeat_delay = atoi(tmp_char);
            xmlFree(tmp_char);
        }
        else result->repeat_delay = 0;
            
        
        /*
         * get output node 
         */
        if((tmp = get_xml_node("output", prefs_get_xml(), node)))
        {
            /*
             * default dimensions 
             */
            result->out_width = 720;
            result->out_height = 576;


            if((tmp_char2 = xmlGetProp(tmp, (xmlChar *) "type")))
            {
                /*
                 * OUT_TYPE_MOUSE 
                 */
                if(xmlStrcmp(tmp_char2, (xmlChar *) "mouse") == 0) 
                {
                    result->out_type = OUT_TYPE_MOUSE;
                    /*
                     * get mouse-node 
                     */
                    tmp2 = get_xml_node("mouse", prefs_get_xml(), tmp);
                    
                        
                    tmp_char = 
                        (char *) xmlGetProp(tmp2, (xmlChar *) "width");
                    result->out_width = atoi(tmp_char);
                    xmlFree(tmp_char);
                
                    tmp_char = 
                        (char *) xmlGetProp(tmp2, (xmlChar *) "height");
                    result->out_height = atoi(tmp_char);
                    xmlFree(tmp_char);
                
                    tmp_char = 
                        (char *) xmlGetProp(tmp2, (xmlChar *) "buttonmask");
                    result->buttonmask = atoi(tmp_char);
                    xmlFree(tmp_char);
                
                    
                }
                
                /*
                 * OUT_TYPE_KEY 
                 */
                if(xmlStrcmp(tmp_char2, (xmlChar *) "keyboard") == 0) 
                {
                    result->out_type = OUT_TYPE_KEY;
                    
                    /* get key-symbol to simulate */
                    tmp2 = get_xml_node("keyboard", prefs_get_xml(), tmp);
                    
                    tmp_char = (char *) xmlGetProp(tmp2, (xmlChar *) "key");
                    
                    if((result->key = (int) XStringToKeysym(tmp_char))) 
                    {
                        _VERBOSE1
                            printf(" keypress output: %s (%d)\n", tmp_char,
                                   result->key);
                    }
                    else 
                    {
                        printf("** ERROR converting to X-KeySymbol: \"%s\"\n",
                               tmp_char);
                    }
                
                }
                

                /*
                 * OUT_TYPE_EXE
                 */
                if(xmlStrcmp(tmp_char2, (xmlChar *) "execute") == 0)
                {
                    result->out_type = OUT_TYPE_EXE;
                    tmp2 = get_xml_node("program", prefs_get_xml(), tmp);
                    result->out_program = (char *) xmlGetProp(tmp2, (xmlChar *) "path");
                    _VERBOSE1 printf("%s(): executing \"%s\" as output.\n",
                            __func__, result->out_program);
                }
                
                /* free string */
                xmlFree(tmp_char2);
            }
          
        }

        /*
         * get color node 
         */
        if((tmp = get_xml_node("color", prefs_get_xml(), node)))
        {
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "threshold")))
            {
                result->thr_threshold = strtod(tmp_char, NULL);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "R")))
            {           
                result->thr_R = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "G")))
            {
                result->thr_G = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "B")))
            {
                result->thr_B = atoi(tmp_char);
                xmlFree(tmp_char);
            }
        }

        /*
         * get field node 
         */
        if((tmp = get_xml_node("field", prefs_get_xml(), node)))
        {
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "x1")))
            {
                result->wrp_x1 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "x2")))
            {
                result->wrp_x2 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "x3")))
            {           
                result->wrp_x3 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "x4")))
            {          
                result->wrp_x4 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "y1")))
            {         
                result->wrp_y1 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "y2")))
            {        
                result->wrp_y2 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "y3")))
            {       
                result->wrp_y3 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
            if((tmp_char = (char *) xmlGetProp(tmp, (xmlChar *) "y4")))
            {      
                result->wrp_y4 = atoi(tmp_char);
                xmlFree(tmp_char);
            }
        }
    }
    _VERBOSE2
        printf
        (" got: thresh(%f,%d,%d,%d) warp(%d/%d, %d/%d, %d/%d, %d/%d)\n",
         result->thr_threshold, result->thr_R, result->thr_G,
         result->thr_B, result->wrp_x1, result->wrp_y1,
         result->wrp_x2, result->wrp_y2, result->wrp_x3,
         result->wrp_y3, result->wrp_x4, result->wrp_y4);

    /*
     * append sensor 
     */
    if(first_sensor) {
        tmp_sensor = first_sensor;
        while (tmp_sensor->next_sensor)
            tmp_sensor = tmp_sensor->next_sensor;
        tmp_sensor->next_sensor = result;
        return first_sensor;
    }
    else
        return result;
}


/**
 * append source to list of sources
 */
struct video_src *src_init_from_xml(xmlNode * node)
{
    struct video_src *parent, *source = NULL;
    GError *error_ptr = NULL;
    GstTuner *tuner;
    GstIterator *iter;
    GParamSpec **property_specs;
    guint prop_count, i;
    GParamSpec *prop_param;
    GValue tmp_val = {0,};
    
    xmlNode *tmp_child;
    xmlChar *tmp_char;
    
    
    _VERBOSE1 printf("%s()\n", __func__);
    
    if((source = calloc(1, sizeof(struct video_src)))) 
    {
             
        
        /*
         * register source 
         */
        if(src.first_source) {
            /*
             * find parent 
             */
            for(parent = src.first_source; parent->next_source; parent = parent->next_source);
            parent->next_source = source;
        }
        
        /* first source */
        else
            src.first_source = source;


        /*
         * copy xml-properties to structure 
         */
        source->name = xmlGetProp(node, (xmlChar *) "name");
        source->gst_string = xmlGetProp(node, (xmlChar *) "gst_string");
        source->tuner = xmlGetProp(node, (xmlChar *) "tuner");
        
        /* source disabled? */
        if((tmp_char = xmlGetProp(node, (xmlChar *) "disabled")))
        {
            source->disabled = atoi((char *) tmp_char);
            xmlFree(tmp_char);
        }
        else
        {
            source->disabled = TRUE;
        }
        
        /* if source is disabled, our journey ends here */
        if(source->disabled)
        {
            _VERBOSE1 printf("%s(): skipping disabled source \"%s\"\n", 
                                __func__, source->name);
            return source;
        }
        
        /* swap red<->blue? */
        if((tmp_char = xmlGetProp(node, (xmlChar *) "swap-rb")))
        {
            source->swap_rb = atoi((char *) tmp_char);
            xmlFree(tmp_char);
        }
        
        
        /*
         * initialize gstreamer 
         */
        if((source->bin =
            gst_parse_launch((const gchar *) source->gst_string, &error_ptr)))
        {
            /*
             * check for errors 
             */
            if(error_ptr) {
                _ERROR printf(" gstreamer error: \"%s\"\n",
                              error_ptr->message);
                goto is_error;
            }

            
            /* find source element */
            if((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__);
                        return NULL;
                    }
                }
                
                /* no "source" element */
                else
                {
                    _ERROR printf("%s(): No source-element found in pipeline-description!\n",
                            __func__);
                    return NULL;
                }
                
                gst_iterator_free(iter);
            }
            else
            {
                _ERROR printf("%s(): No source-element found in pipeline-description!\n",
                            __func__);
                    return NULL;
            }
            
            /* find sink element */
            if((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((tmp_char = (xmlChar *) gst_element_get_name(
                                gst_element_get_factory(source->gst_sink))))
                    {
                        if(strcmp((char *) tmp_char, "fakesink") != 0)
                        {
                            _ERROR printf("%s(): Sink is not \"fakesink\"!\n",
                                                __func__);
                            return NULL;
                        }
                        
                        g_free(tmp_char);
                    }
                    else
                    {
                        _ERROR printf("%s(): could not get gStreamer-element's name!\n",
                                        __func__);
                        return NULL;
                    }
                    /* 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__);
                    
                    return NULL;
                }
                
                gst_iterator_free(iter);
            }
            
            /*
             * configure bus for pipeline 
             */
            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_PLAYING);

            /*
             * switch channel if necessary 
             */
            if(source->tuner) {
                tuner =
                    GST_TUNER(source->gst_source);

                /*
                 * got tuner? 
                 */
                if(tuner) {
                    gst_tuner_set_channel(tuner,
                                          gst_tuner_find_channel_by_name
                                          (tuner,
                                           (gchar *) source->tuner));
                }

                /*
                 * disable tuner 
                 */
                else {
                    printf("ERROR: Failed to switch tuner to \"%s\"\n",
                           source->tuner);
                    source->tuner = NULL;
                }
            }

            /*
             * add fakesink callback function 
             */
            g_signal_connect(source->gst_sink, "handoff",
                             G_CALLBACK(cb_gst_frame_received),
                             (gpointer) source);

            /*
             * callback for when the sink get's ready 
             */
            gst_bus_add_signal_watch(source->bus);
            g_signal_connect(source->bus, "message",
                             G_CALLBACK(cb_gst_handle_message),
                             (gpointer) source);

            /*
             * initialize sensors 
             */
            for(tmp_child = xmlGetLastChild(node);
                tmp_child; tmp_child = tmp_child->prev) 
            {
                if(xmlStrcmp(tmp_child->name, (xmlChar *) "sensor") == 0) {
                    /*
                     * append sensor to this source's sensor-list 
                     */
                    source->first_sensor =
                        src_sensor_append(source->first_sensor, tmp_child);
                }
            }
        
        }
        else {
            _ERROR printf(" failed to create pipeline from description\n");
            goto is_error;
        }
    }
    else {
        _ERROR printf(" no more memory...\n");
    }
    return source;

  is_error:
    if(source)
        free_sources(source);
    return NULL;
}

/**
 * get gstreamer capabilites
 */
void init_source_caps(struct video_src *source, GstBuffer * buffer)
{
    const GstStructure *cap_struct;
    gchar *tmp;
    
    
    source->caps = GST_BUFFER_CAPS(buffer);
    cap_struct = gst_caps_get_structure(source->caps, 0);

    /*
     * save attribtues of video-stream 
     */
    gst_structure_get_int(cap_struct, "width", &source->width);
    gst_structure_get_int(cap_struct, "height", &source->height);
    gst_structure_get_int(cap_struct, "bpp", &source->bpp);
    gst_structure_get_int(cap_struct, "depth", &source->depth);
    gst_structure_get_int(cap_struct, "endianness", &source->endianess);
    gst_structure_get_int(cap_struct, "red_mask", &source->red_mask);
    gst_structure_get_int(cap_struct, "green_mask", &source->green_mask);
    gst_structure_get_int(cap_struct, "blue_mask", &source->blue_mask);

    /* free old buffer */
    free(source->out_buffer);
    
    /*
     * allocate output-buffer 
     */
    if(!(source->out_buffer = calloc(1, source->width * source->height * 3))) {
        /*
         * fatal 
         */
        _ERROR printf("Could not allocate memory to buffer frame!");
    }

    _VERBOSE1 
    {
        tmp = gst_caps_to_string(source->caps);
        printf("init_source_caps(): Got new capabilities: \"%s\"\n", tmp);
        g_free(tmp);
    }
}



/**
 * init source-modules
 */
int src_init()
{
    _VERBOSE1 printf("%s()\n", __func__);
    
    memset(&src, 0, sizeof(struct Source));
        
    return 0;
}
/** 
 * @} 
 */
