/*
 * This program 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.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * See the COPYING file for license information.
 *
 * Guillaume Chazarain <guichaz@yahoo.fr>
 */

/*****************************
 * Transition between images *
 *****************************/

#include <gtk/gtkgl.h>
#include <string.h>             /* memcpy() */
#include <stdio.h>              /* printf(), puts() */

#include "gliv.h"
#include "transition.h"
#include "opengl.h"
#include "matrix.h"
#include "gliv-image.h"
#include "rendering.h"
#include "options.h"
#include "loading.h"
#include "callbacks.h"
#include "messages.h"

extern options_struct *options;
extern GlivImage *current_image;
extern GtkWidget *gl_widget;

/* Whether we are executing a transition */
static gboolean in_transition = FALSE;

static void clear(void)
{
    glDisable(GL_DITHER);
    glDisable(GL_BLEND);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glEnable(GL_DITHER);
}

static void draw_image(GlivImage * im, gfloat * matrix, gfloat opacity)
{
    GlivImage *old_image = current_image;
    gfloat old_matrix[8];

    /* Backup the matrix */
    matrix_cpy(old_matrix, NULL);

    /* Load the desired matrix */
    matrix_cpy(NULL, matrix);

    glColor4f(1.0, 1.0, 1.0, opacity);

    current_image = im;
    draw_current_image();

    /* Restore global parameters */
    current_image = old_image;
    if (matrix)
        matrix_cpy(NULL, old_matrix);
}

/* res = big - small */
static void diff_timeval(GTimeVal * res, GTimeVal * big, GTimeVal * small)
{
    res->tv_sec = big->tv_sec - small->tv_sec;
    if (big->tv_usec >= small->tv_usec)
        res->tv_usec = big->tv_usec - small->tv_usec;
    else {
        res->tv_sec--;
        res->tv_usec = big->tv_usec + G_USEC_PER_SEC - small->tv_usec;
    }
}

static void sleep_remaining(GTimeVal * started, GTimeVal * required)
{
    GTimeVal now;
    GTimeVal done;
    GTimeVal sleep_time;
    glong delay;

    g_get_current_time(&now);

    diff_timeval(&done, &now, started);
    diff_timeval(&sleep_time, required, &done);

    delay = sleep_time.tv_sec * G_USEC_PER_SEC + sleep_time.tv_usec;
    if (delay > 0)
        g_usleep(delay);
}


/*
 * This is the transition used if both images lack an alpha channel and the
 * background color is black.
 * For the other cases, we build new images and use them in these conditions.
 */
static void transition_no_alpha_black(GlivImage * im1, gfloat * matrix1,
                                      GlivImage * im2, gfloat * matrix2)
{
    gint sleep, step;
    gfloat alpha;
    GTimeVal sleeping_time;
    GTimeVal now;
    GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(gl_widget);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);

    if (options->trans_sleep < 0)
        sleep = 0;
    else
        sleep = options->trans_sleep;

    sleeping_time.tv_sec = sleep / 1000;
    sleeping_time.tv_usec = (sleep % 1000) * 1000;

    for (step = 0; step < options->trans_steps; step++) {
        clear();
        alpha = (gfloat) (step + 1) / (options->trans_steps + 1);

        draw_image(im1, matrix1, 1.0 - alpha);
        draw_image(im2, matrix2, alpha);

        g_get_current_time(&now);

        gdk_gl_drawable_swap_buffers(gldrawable);
        gdk_gl_drawable_wait_gl(gldrawable);
        process_events();

        sleep_remaining(&now, &sleeping_time);
    }

    glColor4f(1.0, 1.0, 1.0, 1.0);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_BLEND);
}

/*
 * If an image has an alpha channel or the background is not black,
 * we compose it with the background in order to get a new image without
 * an alpha channel which can be used with a black background.
 */
static GlivImage *grab_image(GlivImage * im, gfloat * matrix,
                             gfloat ** new_mat, gboolean bg_is_black)
{
    GlivImage *new_im;
    gpointer data;
    gint viewport[4], x, y, w, h;
    GdkPixbuf *pixbuf;
    gboolean old_mipmap;
    gboolean old_dither;

    *new_mat = new_matrix();

    if (im->has_alpha == FALSE && bg_is_black) {
        /* This image was actually OK */
        matrix_cpy(*new_mat, matrix);
        g_object_ref(im);
        return im;
    }

    clear();

    /* OpenGL coordinates => X coordinates */
    flip_matrix(*new_mat, TRUE);

    draw_image(im, matrix, 1.0);

    /* Area where we take the screenshot */
    glGetIntegerv(GL_VIEWPORT, viewport);
    x = viewport[0];
    y = viewport[1];
    w = viewport[2];
    h = viewport[3];

    /* Will receive the pixels */
    data = g_new(guint8, w * h * 3);

    glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, data);

    pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, FALSE, 8, w, h,
                                      w * 3, (GdkPixbufDestroyNotify) g_free,
                                      NULL);

#if 0
    {
        static gint id = 0;
        gchar *base = g_path_get_basename(im->node->data);
        gchar *filename = g_strdup_printf("%s_%d.png", base, id++);

        g_free(base);
        gdk_pixbuf_save(pixbuf, filename, "png", NULL, NULL);
        g_free(filename);
    }
#endif

    /* We don't need mipmaps and dithering for this image */
    old_mipmap = options->mipmap;
    old_dither = options->dither;
    options->mipmap = FALSE;
    options->dither = FALSE;

    new_im = make_gliv_image(pixbuf);

    options->mipmap = old_mipmap;
    options->dither = old_dither;

    return new_im;
}

void transition(gfloat * old_matrix, GlivImage * im)
{
    GdkColor black;
    gboolean bg_is_black = FALSE;
    gchar *text;
    gchar *base1, *base2;

    if (!options->transitions || options->trans_steps <= 0)
        return;

    in_transition = TRUE;

    base1 = g_path_get_basename(current_image->node->data);
    base2 = g_path_get_basename(im->node->data);
    text = g_strdup_printf(_("transition from `%s' to `%s'"), base1, base2);

    g_free(base1);
    g_free(base2);

    set_loading(text);
    g_free(text);

    black.red = black.green = black.blue = 0;

    if (!gdk_colormap_alloc_color(gdk_colormap_get_system(),
                                  &black, TRUE, TRUE) == FALSE)
        black.pixel = 0;

    bg_is_black = gdk_color_equal(&options->bg_col, &black);
    gdk_colormap_free_colors(gdk_colormap_get_system(), &black, 1);

    if (current_image->has_alpha == FALSE && im->has_alpha == FALSE &&
        bg_is_black) {

        /* Easy case */
        gfloat *matrix2 = get_matrix_for_image(im);

        transition_no_alpha_black(current_image, old_matrix, im, matrix2);
        g_free(matrix2);
    } else {
        GdkColor orig_bg_color;
        GlivImage *im1 = NULL, *im2 = NULL;
        gfloat *matrix1, *matrix2;
        gfloat *matrix2_tmp = get_matrix_for_image(im);

        printf("Transition slow case: (cur: %s, im: %s, bg: %s)\n",
               current_image->has_alpha ? "alpha" : "no alpha",
               im->has_alpha ? "alpha" : "no alpha",
               bg_is_black ? "black" : "not black");

        if (options->one_image) {
            puts("The \"Only one image mode\" is incompatible "
                 "with slow transitions");
            goto out;
        }

        im1 = grab_image(current_image, old_matrix, &matrix1, bg_is_black);
        im2 = grab_image(im, matrix2_tmp, &matrix2, bg_is_black);

        g_free(matrix2_tmp);

        /* Black background */
        memcpy(&orig_bg_color, &options->bg_col, sizeof(GdkColor));
        memcpy(&options->bg_col, &black, sizeof(GdkColor));
        update_bg_color();

        transition_no_alpha_black(im1, matrix1, im2, matrix2);

        /* Restore the background */
        memcpy(&options->bg_col, &orig_bg_color, sizeof(GdkColor));
        update_bg_color();

        g_free(matrix1);
        g_free(matrix2);
        g_object_unref(im1);
        g_object_unref(im2);
    }
  out:
    in_transition = FALSE;
    set_loading(NULL);
}

gboolean is_in_transition(void)
{
    return in_transition;
}
