/*
 * File Name: test_output_device.cpp
 */

/*
 * This file is part of uds-plugin-pdf.
 *
 * uds-plugin-pdf is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * uds-plugin-pdf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include <stdio.h>
#include <string.h>
#include <gdk/gdktypes.h>
#include "test_output_device.h"

namespace test
{

OutputDevice::OutputDevice()
: ctx()
, shared_image(0)
, color_dep(MONO_COLOR_DEPTH)
, xor_gc(0)
{
}

OutputDevice::~OutputDevice(void)
{
    if (shared_image)
    {
        g_object_unref(shared_image);
    }

    if (xor_gc)
    {
        g_object_unref(xor_gc);
    }
}

void OutputDevice::map(OutputDeviceContext &context)
{
    ctx.widget = context.widget;
    ctx.gc     = context.gc;

    if (xor_gc)
    {
        g_object_unref(xor_gc);
    }

    xor_gc = gdk_gc_new(ctx.widget->window);

    gdk_gc_set_function(xor_gc, GDK_XOR);

    GdkColor color = {0xffffffff, 0xffff, 0xffff, 0xffff};

    gdk_gc_set_foreground(xor_gc, &color);
}

void OutputDevice::update_shared_image(GtkWidget * widget)
{
    gint width = 0, height = 0;
    
    ctx.widget = widget;

    //Retrieve the size of widget.
    gdk_drawable_get_size(widget->window, &width, &height);

    update_shared_image(width, height);
}

void OutputDevice::update_shared_image(int width, int height)
{
    if (shared_image)
    {
        if (width == shared_image->width && height == shared_image->height)
        {
            return;
        }
        g_object_unref(shared_image);
        shared_image = 0;
    }

    GdkVisual * visual = gdk_drawable_get_visual(ctx.widget->window);
    
    //Create a gdk image for drawing.
    shared_image = gdk_image_new(GDK_IMAGE_FASTEST, visual, width, height);

}

void OutputDevice::clear_background(const int color, bool flush)
{
    update_shared_image(ctx.widget);

    // clear the image now
    unsigned char *p = (unsigned char *)shared_image->mem;
    memset(p, color, shared_image->height * shared_image->bpl);

    // draw the background immediately if necessary
    if (flush)
    {
        gdk_draw_image(ctx.widget->window, ctx.gc, shared_image, 0, 0, 
            ctx.widget->allocation.x, ctx.widget->allocation.y, 
            ctx.widget->allocation.width, ctx.widget->allocation.height); 
    }
}

void OutputDevice::invalidate_rectangle(const GdkRectangle& r)
{
    GdkRectangle rect = r;
    gdk_window_invalidate_rect(ctx.widget->window, &rect, TRUE);
}

void OutputDevice::draw_line(int x1, int y1, int x2, int y2)
{
    gdk_draw_line(ctx.widget->window, ctx.gc, x1, y1, x2, y2);
}

void OutputDevice::draw_highlight_rectangle(const GdkRectangle &rect)
{
    gdk_draw_rectangle(ctx.widget->window, xor_gc, TRUE, rect.x, rect.y
       , rect.width, rect.height);
}

/// copy src (0, 0) - (width, height) to shared_image(xDest, yDest) - (xDest + width, yDest + height)
/// copy data from shared_image to X server.
void OutputDevice::draw_image(const unsigned char *src,
                              int width,
                              int height,
                              int row_stride,
                              int xDest,
                              int yDest)
{
    // check at first, should also check xDest and yDest.
    if (src == 0 || width <= 0 || height <= 0) 
    {
        return;
    }
    
    //Only cover the situation that global color depth is "8" and 
    //output device's color depth is "32"
    update_shared_image(width, height);
    
    unsigned char *dst = (unsigned char *)shared_image->mem +
          (shared_image->bpl * yDest + xDest);

    bool transform_result = false;

    switch(shared_image->bits_per_pixel)
    {
    case RGB_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:
                
                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride);

                break;
            case ARGB_COLOR_DEPTH:
                
                //Print "Unsupport" message
                transform_result = transform_bitmap_ARGB_to_RGB(src, dst,
                    width, height, row_stride);
                
                break;
            case MONO_COLOR_DEPTH:

                transform_result = transform_bitmap_MONO_to_RGB(src, dst,
                    width, height, row_stride);
                
                break;
            default:
                
                //Print "Unsupport" message
                
                break;
            }
            break;
        }
    case ARGB_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:
                
                transform_result = transform_bitmap_RGB_to_ARGB(src, dst,
                    width, height, row_stride);
                
                break;
            case ARGB_COLOR_DEPTH:

                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride);
                
                break;
            case MONO_COLOR_DEPTH:
                
                transform_result = transform_bitmap_MONO_to_ARGB(src, dst,
                    width, height, row_stride);
                
                break;
            default:

                //Print "Unsupport" message
                
                break;
            }
            break;
        }
    case MONO_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:

                transform_result = transform_bitmap_RGB_to_MONO(src, dst,
                    width, height, row_stride);
                
                break;
            case ARGB_COLOR_DEPTH:

                transform_result = transform_bitmap_ARGB_to_MONO(src, dst,
                    width, height, row_stride);
                
                break;
            case MONO_COLOR_DEPTH:
                
                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride);
                
                break;
            default:
                //Print "Unsupport" message
                break;
            }
            break;
        }
    }

    // copy the buffer to gtk widget drawable. use the shared memory automatically.
    // make sure the #ifdef USE_SHM is ture.
    if (transform_result)
    {
        gdk_draw_image(ctx.widget->window, ctx.gc, shared_image, 0, 0, 
                       xDest, yDest, width, height);    
    }
}


/// Steps:
/// 1. copy data from src to image buffer with transform.
/// 2. draw the image on the widget.
/// Caller should call clearBackground at first to update the image.
/// Maybe we should update image here. TODO.
void OutputDevice::draw_image_with_transform(const unsigned char *src, 
                                             int width, 
                                             int height,
                                             int row_stride,
                                             int xDest, 
                                             int yDest,
                                             int transform)
{
    // copy data to gdkImage, check at first.
    if (src == 0 || width <= 0 || height <= 0) 
    {
        return;
    }        

    unsigned char * dst = 0;

    if (transform == PLUGIN_ORIENTATION_LANDSCAPE)
    {
        update_shared_image(height, width);     
    }
    else
    {
        update_shared_image(width, height);
    }
    
    dst = (unsigned char *)shared_image->mem +
          (shared_image->bpl * yDest + xDest);

    bool transform_result = false;
    
    switch(shared_image->bits_per_pixel)
    {
    case RGB_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:
                
                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride, transform);

                break;
            case ARGB_COLOR_DEPTH:
                
                //Print "Unsupport" message
                
                break;
            case MONO_COLOR_DEPTH:

                transform_result = transform_bitmap_MONO_to_RGB(src, dst,
                    width, height, row_stride, transform);
                
                break;
            default:
                
                //Print "Unsupport" message
                
                break;
            }
            break;
        }
    case ARGB_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:
                
                transform_result = transform_bitmap_RGB_to_ARGB(src, dst,
                    width, height, row_stride, transform);
                
                break;
            case ARGB_COLOR_DEPTH:

                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride, transform);
                
                break;
            case MONO_COLOR_DEPTH:
                
                transform_result = transform_bitmap_MONO_to_ARGB(src, dst,
                    width, height, row_stride, transform);
                
                break;
            default:

                //Print "Unsupport" message
                
                break;
            }
            break;
        }
    case MONO_COLOR_DEPTH:
        {
            switch(color_dep)
            {
            case RGB_COLOR_DEPTH:

                transform_result = transform_bitmap_RGB_to_MONO(src, dst,
                    width, height, row_stride, transform);
                
                break;
            case ARGB_COLOR_DEPTH:

                transform_result = transform_bitmap_ARGB_to_MONO(src, dst,
                    width, height, row_stride, transform);
                
                break;
            case MONO_COLOR_DEPTH:
                
                transform_result = transform_bitmap_with_same_depth(src, dst,
                    width, height, row_stride, transform);
                
                break;
            default:
                //Print "Unsupport" message
                break;
            }
            break;
        }
    }

    if (transform_result)
    {
        gdk_draw_image(ctx.widget->window, ctx.gc, shared_image, 0, 0, 
                       xDest, yDest, height, width);
    }
}

// Transform the bitmap with the same color depth
bool OutputDevice::transform_bitmap_with_same_depth(const unsigned char *src,
                                                    unsigned char *dst,
                                                    int width,
                                                    int height, 
                                                    int row_stride,
                                                    int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            memcpy(dst, src, row_stride);
            dst += row_stride;
            src += row_stride;
        }
    }
    else
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                *d = *s++; 
                d += height;
            }
            src += row_stride;
            dst += height;
        }
    }
    return true;
}

/// Transform the bitmap from 24-depth to 32-depth
bool OutputDevice::transform_bitmap_RGB_to_ARGB(const unsigned char *src,
                                      unsigned char *dst,
                                      int width,
                                      int height, 
                                      int row_stride,
                                      int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            unsigned char * d = dst;
            const unsigned char* s = src;
            for(int w = 0; w < width; ++w)
            {
                //Set default value to alpha channel
                s += 2;
                *d++ = *s--; *d++ = *s--; *d++ = *s;
                *d++ = DEFAULT_ALPHA_VALUE;
                s += 3;
                
            }
            src += row_stride;
            dst += (width * 4);
        }
    }
    else
    {
        int dst_heihgt = (height-1) * 4;
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Set value of alpha channel
                *d++ = DEFAULT_ALPHA_VALUE;
                        
                //Set value of R, G, B
                for(int i = 0; i < 3; ++i)
                {
                    *d++ = *s++;
                }
                d += dst_heihgt;
            }
            src += row_stride;
            dst += 4;
        }
    }
    
    return true;
}

// Transform the bitmap from 8-depth to 24-depth
bool OutputDevice::transform_bitmap_MONO_to_RGB(const unsigned char *src,
                                                unsigned char *dst,
                                                int width,
                                                int height, 
                                                int row_stride,
                                                int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char* s = src;
            for(int w = 0; w < width; ++w)
            {
                //Set value of R, G, B
                for(int i = 0; i < 3; i++)
                {
                    *dst++ = *s;
                }
                s++;
            }
            src += row_stride;
        }
    }
    else
    {
        int dst_height = (height-1) * 3;
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Set value of R, G, B
                for(int i = 0; i < 3; ++i)
                {
                    *d++ = *s;
                }

                s++; 
                d += dst_height;
            }
            src += row_stride;
            dst += 3;
        }
    }
    return true;
}

// Transform the bitmap from 8-depth to 32-depth
bool OutputDevice::transform_bitmap_MONO_to_ARGB(const unsigned char *src,
                                                 unsigned char *dst,
                                                 int width,
                                                 int height, 
                                                 int row_stride,
                                                 int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char* s = src;
            for(int w = 0; w < width; ++w)
            {
                //Set default value to alpha channel
                *dst++ = DEFAULT_ALPHA_VALUE;
                
                //Set value of R, G, B
                for(int i = 0; i < 3; i++)
                {
                    *dst++ = *s;
                }
                s++;
            }
            src += row_stride;
        }
    }
    else
    {
        int dst_heihgt = (height-1) * 4;
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Set value of alpha channel
                *d++ = DEFAULT_ALPHA_VALUE;
                        
                //Set value of R, G, B
                for(int i = 0; i < 3; ++i)
                {
                    *d++ = *s;
                }

                s++; 
                d += dst_heihgt;
            }
            src += row_stride;
            dst += 4;
        }
    }
    
    return true;
}

/// Transform the bitmap from 24-depth to 8-depth
bool OutputDevice::transform_bitmap_RGB_to_MONO(const unsigned char *src,
                                                unsigned char *dst,
                                                int width,
                                                int height, 
                                                int row_stride,
                                                int transform)

{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char *s = src;
            for(int w = 0; w < width; ++w)
            {
                //Set the grey to be (R+G+B)/3;
                unsigned char red   = *s++;
                unsigned char green = *s++;
                unsigned char blue  = *s++;
                *dst++ = (red + green + blue) / 3;
            }
            src += row_stride;
        }
    }
    else
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Set the grey to be (R+G+B)/3;
                unsigned char red   = *s++;
                unsigned char green = *s++;
                unsigned char blue  = *s++;
                *d = (red + green + blue) / 3;

                d += height;
            }
            src += row_stride;
            dst ++;
        }
    }

    return true;
}

// Transform the bitmap from 32-depth to 8-depth
bool OutputDevice::transform_bitmap_ARGB_to_MONO(const unsigned char *src,
                                                 unsigned char *dst,
                                                 int width,
                                                 int height, 
                                                 int row_stride,
                                                 int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char *s = src;
            for(int w = 0; w < width; ++w)
            {
                //Get the value of alpha channel
                //This value should be abandoned.
                //unsigned char alpha = *s++;
                //Set the grey to be (R+G+B)/3;
                unsigned char red   = *s++;
                unsigned char green = *s++;
                unsigned char blue  = *s++;
                *dst++ = (red + green + blue) / 3;
            }
            src += row_stride;
        }
    }
    else
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Get the value of alpha channel
                //This value should be abandoned.
                //unsigned char alpha = *s++;
                //Set the grey to be (R+G+B)/3;
                unsigned char red   = *s++;
                unsigned char green = *s++;
                unsigned char blue  = *s++;
                *d = (red + green + blue) / 3;

                d += height;
            }
            src += row_stride;
            dst ++;
        }
    }

    return true;
}

// Transform the bitmap from 32-depth to 24-depth
bool OutputDevice::transform_bitmap_ARGB_to_RGB(const unsigned char *src,
                                                unsigned char *dst,
                                                int width,
                                                int height, 
                                                int row_stride,
                                                int transform)
{
    if (transform == PLUGIN_ORIENTATION_PORTRAIT)
    {
        for(int h = 0; h < height; ++h)
        {
            const unsigned char *s = src;
            for(int w = 0; w < width; ++w)
            {
                //Get the value of alpha channel
                //This value should be abandoned.
                //unsigned char alpha = *s++;
                //Set the grey to be (R+G+B)/3;
                for(int i=0; i < 3; ++i)
                {
                    *dst++ = *s++;
                }
            }
            src += row_stride;
        }
    }
    else
    {
        int dst_height = (height - 1) * 3;
        for(int h = 0; h < height; ++h)
        {
            const unsigned char * s = src;
            unsigned char * d = dst;
            for(int w = 0; w < width; ++w)
            {
                //Get the value of alpha channel
                //This value should be abandoned.
                //unsigned char alpha = *s++;
                //Set the grey to be (R+G+B)/3;
                for(int i=0; i < 3; ++i)
                {
                    *dst++ = *s++;
                }

                d += dst_height;
            }
            src += row_stride;
            dst += 3;
        }
    }
    
    return true;
}

} //namespace view_helper

