/*
 * 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@gmail.com>
 */

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

#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 "messages.h"

/*
 * Define as 1 to activate the framerate counter during transitions.
 * Set 0 as delay between steps to have a more accurate framerate.
 */
#define FPS_COUNTER 0

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 */
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 transition_single_step(GdkGLDrawable * gldrawable,
                                   GlivImage * im1, gfloat * matrix1,
                                   GlivImage * im2, gfloat * matrix2,
                                   gfloat alpha)
{
    clear();

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

    gdk_gl_drawable_swap_buffers(gldrawable);
    gdk_gl_drawable_wait_gl(gldrawable);
}

/*
 * 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 steps = 0;
    GTimeVal now, transition_start;
    gint advance = 0;
    GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(gl_widget);

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

    g_get_current_time(&transition_start);
    g_get_current_time(&now);

    for (;;) {
        gint delay = diff_timeval_us(&now, &transition_start);
        gfloat alpha = (gfloat) delay / (options->trans_time * 1000);

        if (alpha > 1.0)
            break;

        g_get_current_time(&now);

        transition_single_step(gldrawable, im1, matrix1, im2, matrix2, alpha);

        if (options->fps > 0) {
            if (get_fps_limiter_delay(&now) > 0)
                gtk_main_iteration_do(FALSE);
            delay = get_fps_limiter_delay(&now) + advance;
            if (delay > 0)
                g_usleep(delay);
            advance += get_fps_limiter_delay(&now);

#if FPS_COUNTER
            printf("transition lag: %dus\n", -get_fps_limiter_delay(&now));
#endif
        }
        steps++;
    }

#if FPS_COUNTER
    {
        gfloat usecs = (gfloat) diff_timeval_us(&now, &transition_start);
        printf("%dx%d -> %dx%d : %d frames in %f sec => %f fps\n",
               im1->width, im1->height, im2->width, im2->height,
               steps, usecs / G_USEC_PER_SEC, G_USEC_PER_SEC * steps / usecs);
    }
#endif

    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 */
#if NO_OPENGL
    x = 0;
    y = 0;
    w = 640;
    h = 480;
#else
    glGetIntegerv(GL_VIEWPORT, viewport);
    x = viewport[0];
    y = viewport[1];
    w = viewport[2];
    h = viewport[3];
#endif

    /* 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(GlivImage * im)
{
    GdkColor black;
    gboolean bg_is_black = FALSE;
    gchar *text;
    gchar *base1, *base2;
    gfloat *old_matrix = new_matrix();
    gfloat *new_matrix = get_matrix_for_image(im);

    in_transition = TRUE;

    matrix_cpy(old_matrix, NULL);
    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 */
        transition_no_alpha_black(current_image, old_matrix, im, new_matrix);
    } else {
        GdkColor orig_bg_color;
        GlivImage *im1 = NULL, *im2 = NULL;
        gfloat *matrix1, *matrix2;

        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, new_matrix, &matrix2, bg_is_black);

        /* 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:
    g_free(old_matrix);
    g_free(new_matrix);
    in_transition = FALSE;
    set_loading(NULL);
}

gboolean is_in_transition(void)
{
    return in_transition;
}
