/** 
 * @defgroup gfx gfx
 * graphics processing code
 * @{
 */

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

/************************** Module **************************/

/**
 * apply threshold filter to frame
 */
void g_threshold_frame(unsigned char *src, unsigned char *dst, int size,
                     double threshold, unsigned char R, unsigned char G,
                     unsigned char B)
{
    int count;
    /*
     * double per_r, per_g, per_b, sum; double min_r, min_g, min_b, max_r,
     * max_g, max_b, cmp_sum; 
     */

    /*
     * calc reference 
     */
    /*
     * cmp_sum = (double) (R+G+B) / 100; min_r = (double) R / cmp_sum; min_g
     * = (double) G / cmp_sum; min_b = (double) B / cmp_sum; max_r = min_r +
     * min_r*threshold; max_g = min_g + min_g*threshold; max_b = min_b +
     * min_b*threshold; min_r -= min_r * threshold; min_g -= min_g *
     * threshold; min_b -= min_b * threshold; 
     */

    /*
     * walk all data 
     */
    for(count = 0; count < size; count += 3) {
        if((src[count] < R + (unsigned char) (255 * threshold)) &&
           (src[count] > R - (unsigned char) (255 * threshold)) &&
           (src[count + 1] < G + (unsigned char) (255 * threshold))
           && (src[count + 1] > G - (unsigned char) (255 * threshold))
           && (src[count + 2] < B + (unsigned char) (255 * threshold))
           && (src[count + 2] > B - (unsigned char) (255 * threshold)))

            /*
             * sum = (double) (data[count] + data[count+1] + data[count+2]) / 
             * 100; per_r = (double) data[count] / sum; per_g = (double)
             * data[count+1] / sum; per_b = (double) data[count+2] / sum;
             * if((per_r <= max_r && per_r >= min_r) && (per_g <= max_g &&
             * per_g >= min_g) && (per_b <= max_b && per_b >= min_b)) 
             */
        {
            dst[count] = 0xFF;
            dst[count + 1] = 0xFF;
            dst[count + 2] = 0xFF;
        }
        else {
            dst[count] = 0;
            dst[count + 1] = 0;
            dst[count + 2] = 0;
        }
    }
}

/**
 * scan line, save pixels to buffer and return length 
 */
int g_scan_line(unsigned char *src, unsigned char *buffer, int src_width,
              int x1, int y1, int x2, int y2)
{
    int length = 0;
    int x, y;
    int tmp, flag, delta, d_sub, dx, dy, e;
    int step_x, step_y;
    int count;

    /*
     * default step is 1 
     */
    step_x = step_y = 1;

    /*
     * calc delta 
     */
    dx = x2 - x1;
    dy = y2 - y1;

    if(dx < 0) {
        step_x = -step_x;
        dx = -dx;
    }
    if(dy < 0) {
        step_y = -step_y;
        dy = -dy;
    }

    if(dx > dy) {
        x = x1;
        y = y1;
        e = dx >> 1;
        length = dx;
        d_sub = dx;
        delta = dy;
        flag = TRUE;
    }
    else {
        x = y1;
        y = x1;
        e = dy >> 1;
        length = dy;
        d_sub = dy;
        delta = dx;
        tmp = step_x;
        step_x = step_y;
        step_y = tmp;
        flag = FALSE;
    }

    /*
     * draw loop 
     */
    for(count = 0; count < length; count++) {
        /*
         * save-pixel (x,y) 
         */
        if(flag) {
            buffer[count * 3] = src[src_width * 3 * y + x * 3];
            buffer[count * 3 + 1] = src[src_width * 3 * y + x * 3 + 1];
            buffer[count * 3 + 2] = src[src_width * 3 * y + x * 3 + 2];
        }
        else {
            buffer[count * 3] = src[src_width * 3 * x + y * 3];
            buffer[count * 3 + 1] = src[src_width * 3 * x + y * 3 + 1];
            buffer[count * 3 + 2] = src[src_width * 3 * x + y * 3 + 2];
        }

        /*
         * adjust x 
         */
        x += step_x;
        /*
         * adjust error 
         */
        e += delta;

        /*
         * error too large? 
         */
        if(e > d_sub) {
            /*
             * correct error 
             */
            e -= d_sub;
            /*
             * adjust y 
             */
            y += step_y;
        }
    }

    /*
     * amount of pixels in line 
     */
    return length;
}


/**
 * interpolate one line to new length
 */
void g_map_line(unsigned char *dst, unsigned char *buffer,
              int buffer_width, int dst_width, int line)
{
    double step, x;
    int count;

    x = 0;
    step = (double) buffer_width / (double) dst_width;

    for(count = 0; count < dst_width; count++) {
        dst[line * 3 * dst_width + count * 3] = buffer[(int) x * 3];
        dst[line * 3 * dst_width + count * 3 + 1] = buffer[(int) x * 3 + 1];
        dst[line * 3 * dst_width + count * 3 + 2] = buffer[(int) x * 3 + 2];
        x += step;
    }
}


/**
 * fill a single line with zeroes
 */
void g_clear_line(unsigned char *buffer, int width, int y)
{
    unsigned char *tmp = &buffer[width * 3 * y];
    int count;

    for(count = width * 3; count > 0; count--)
        tmp[count] = 0;
}
/**
 * find right border of a blob
 */
int g_find_border(unsigned char *data, int width, unsigned char byte)
{
    int x;

    for(x = 0; x < width * 3; x += 3)
        if(data[x] == byte && data[x + 1] == byte && data[x + 2] == byte)
            break;
    if(x == width * 3)
        return -1;
    else
        return x / 3;
}

/**
 * find the middle of a found blob
 */
void g_find_spot_middle(struct sensor *current_sensor, unsigned char *data,
                      int y, int width, int height)
{

    int line, tmp_left, tmp_right, b_x, b_y, b_x2, b_height;

    b_x2 = b_x = b_y = 0;

    for(line = y; line < height; line++) {
        tmp_left = g_find_border(&data[width * 3 * line], width, 0xFF);
        if(tmp_left < 0)
            break;
        if(tmp_left < b_x || b_x == 0)
            b_x = tmp_left;
        tmp_right =
            g_find_border(&data[width * 3 * line + b_x * 3],
                        width - tmp_left, 0x00) + tmp_left;
        if(tmp_right > b_x2)
            b_x2 = tmp_right;
        /*
         * no right border found 
         */
        if(tmp_right < 0)
            break;
    }

    b_y = y;
    b_height = line - y;
    if(b_y + b_height >= height)
        b_height = (height - b_y) - 1;
    if(b_x2 >= width)
        b_x2 = width - 1;

    // g_print("X: %d, Y: %d, Width: %d, Height: %d\n", b_x, b_y, b_x2-b_x,
    // b_height);
    // g_print("X: %d, Y: %d\n", b_x+(b_x2-b_x)/2, b_y+b_height/2);

    /*
     * size threshold 
     */
    if((b_x2 - b_x) > SPOT_MIN_WIDTH && b_height > SPOT_MIN_HEIGHT &&
       (b_x2 - b_x) < SPOT_MAX_WIDTH && b_height < SPOT_MAX_HEIGHT) {
        /*
         * save coords 
         */
        current_sensor->x = b_x + (b_x2 - b_x) / 2;
        current_sensor->y = b_y + b_height / 2;
    }
    else {
        current_sensor->x = 0;
        current_sensor->y = 0;
    }
}

/**
 * find X/Y position of laser
 */
void g_track_xy(unsigned char *data, struct video_src *source,
              struct sensor *sens)
{
#define TREMBLE 0

    int line;
    int old_x, old_y;
    int width, height, screen_width, screen_height;

    
    width = source->width;
    height = source->height;
    screen_width = sens->out_width;
    screen_height = sens->out_height;
    old_x = sens->x;
    old_y = sens->y;
    

    
    
    for(line = 0; line < height; line++) 
    {
        /*
         * find first white pixel 
         */
        if(g_find_border(&data[width * 3 * line], width, 0xFF) > 0) 
        {
            
            g_find_spot_middle(sens, data, line, width, height);

            /*
             * respect spot-size threshold 
             */
            if(sens->x != 0 && sens->y != 0) 
            {
                
                /*
                 * adjust coords 
                 */
                sens->x =
                    (int) ((double) sens->x /
                           (double) width * (double) screen_width);
                sens->y =
                    (int) ((double) sens->y /
                           (double) height * (double) screen_height);

                /*
                 * flatten movement 
                 */
                if((sens->x > old_x + TREMBLE
                    || sens->x < old_x - TREMBLE)
                   && (sens->y > old_y + TREMBLE
                       || sens->y < old_y - TREMBLE)) 
                {
                    
                      
                    
                    if(sens->ondelay <= sens->m_framecount)
                    {
                        
                        if(sens->laser_detected == FALSE &&
                            sens->repeat_delay <= sens->m_framecount)
                        {
                            sens->m_framecount = 0;
                            sens->laser_detected = TRUE;
                            
                            
                            if(sens->out_type == OUT_TYPE_MOUSE)
                            {
                                _VERBOSE3
                                    printf("%s(): Sensor: \"%s\" %d/%d\n", __func__,
                                           sens->name, sens->x, sens->y);
                            }
                            else
                            {
                                _VERBOSE3
                                    printf("%s(): Sensor: \"%s\" detected.\n", 
                                            __func__, sens->name);
                            }
                            
                        }
                        
                    }                    
                    
                }
                
            }
            
            goto txy_end;
        }
    }
    
    sens->x = old_x;
    sens->y = old_y;
    
    if(sens->offdelay <= sens->m_framecount)
    {
        
        if(sens->laser_detected == TRUE)
        {
            sens->m_framecount = 0;
            sens->laser_detected = FALSE;
        }
        
        
    }
    
txy_end:
    return;
}



/**
 * map 4-point polygon to rectangular dest-buffer 
 */
void g_warp_image(unsigned char *src, unsigned char *dest,
                int dst_width, int dst_height,
                int x1, int y1, int x2, int y2, int x3, int y3, int x4,
                int y4)

{

    int length;
    double src_x1, src_y1, src_x2, src_y2, line;
    double x_step1, y_step1, x_step2, y_step2;
    unsigned char *linebuffer;

    /*
     * buffer for one line of src-polygon (may not be larger than
     * 2*dst_width) 
     */
    if(!(linebuffer = malloc(2 * dst_width * 3)))
        return;

    src_x1 = x1;
    src_y1 = y1;
    src_x2 = x2;
    src_y2 = y2;

    x_step1 = (double) (x4 - x1) / (double) dst_height;
    y_step1 = (double) (y4 - y1) / (double) dst_height;
    x_step2 = (double) (x3 - x2) / (double) dst_height;
    y_step2 = (double) (y3 - y2) / (double) dst_height;

    if((x_step1 > 0 || y_step1 > 0) && (x_step2 > 0 || y_step2 > 0)) {
        _VERBOSE5 printf("warp(): %d/%d - %d/%d - %d/%d - %d/%d\n",
                         x1, y1, x2, y2, x3, y3, x4, y4);

        for(line = 0; line < dst_height; line++) {
            length = g_scan_line(src, linebuffer, dst_width,
                               src_x1, src_y1, src_x2, src_y2);
            g_map_line(dest, linebuffer, length, dst_width, line);

            src_x1 += x_step1;
            src_y1 += y_step1;
            src_x2 += x_step2;
            src_y2 += y_step2;
        }
    }

    free(linebuffer);
}

/** 
 * @} 
 */
