/*
 * 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>
 */

/***********************
 * Rendering functions *
 ***********************/

#include <sys/time.h>           /* struct timeval, gettimeofday() */
#include <gtk/gtkgl.h>

#include "gliv.h"
#include "rendering.h"
#include "options.h"
#include "gliv-image.h"
#include "matrix.h"
#include "params.h"
#include "zoom_frame.h"
#include "scrollbars.h"
#include "textures.h"
#include "files_list.h"
#include "loading.h"
#include "windows.h"
#include "history.h"
#include "next_image.h"
#include "opengl.h"
#include "callbacks.h"
#include "transition.h"

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

/* The gtk timer that calls redraw(). */
static guint idle_id = 0;

/* When did the last redraw occured, used when limiting the fps */
static struct timeval last_redraw;

static void set_filter(gint filter)
{
    gint id;
    texture_map *map;

    /*
     * Only the textures in the first map change their filter,
     * since in the others maps the image is zoomed out.
     */
    map = current_image->maps;

    for (id = 0; id < map->nb_tiles; id++) {
        glBindTexture(GL_TEXTURE_2D, map->tex_ids[id]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
    }
}

static gboolean is_filtering_enabled(void)
{
    texture_map *map;
    gint filter;

    /* Only the first map changes its filter. */
    map = current_image->maps;

    glBindTexture(GL_TEXTURE_2D, map->tex_ids[0]);
    glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &filter);

    return filter == GL_LINEAR;
}

static void call_lists(gboolean smooth, gint level)
{
    gint id;
    texture_map *map;

    if (level == 0 && is_filtering_enabled() != smooth)
        set_filter(smooth ? GL_LINEAR : GL_NEAREST);

    map = current_image->maps + level;

    for (id = 0; id < map->nb_tiles; id++)
        if (matrix_tile_visible(map->tiles + id))
            glCallList(map->list + id);
}

static void draw_checker(void)
{
    static guint tex_id = 0;
    gint i;
    gfloat half_w, half_h;
    gboolean matrix_changed;

    glDisable(GL_DITHER);
    matrix_changed = get_matrix_has_changed();

    if (rt->alpha_checks_changed) {
        gushort texture[12];
        gushort alpha1[3] = {
            options->alpha1.red,
            options->alpha1.green,
            options->alpha1.blue
        };
        gushort alpha2[3] = {
            options->alpha2.red,
            options->alpha2.green,
            options->alpha2.blue
        };

        if (tex_id == 0)
            glGenTextures(1, &tex_id);

        for (i = 0; i < 3; i++) {
            texture[i] = texture[9 + i] = alpha1[i];
            texture[3 + i] = texture[6 + i] = alpha2[i];
        }

        glBindTexture(GL_TEXTURE_2D, tex_id);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB,
                     GL_UNSIGNED_SHORT, texture);

        rt->alpha_checks_changed = FALSE;
    } else
        glBindTexture(GL_TEXTURE_2D, tex_id);

    if (matrix_changed == FALSE)
        /* glMatrixMode(GL_MODELVIEW); */
        /* Save the matrix only if we will not replace it when redrawing. */
        glPushMatrix();

    glLoadIdentity();

    half_w = rt->wid_size->width / 2.0;
    half_h = rt->wid_size->height / 2.0;

    glBegin(GL_QUADS);

    glTexCoord2f(0.0, 0.0);
    glVertex2f(-half_w, -half_h);

    glTexCoord2f(half_w / 16.0, 0.0);
    glVertex2f(half_w, -half_h);

    glTexCoord2f(half_w / 16.0, half_h / 16.0);
    glVertex2f(half_w, half_h);

    glTexCoord2f(0.0, half_h / 16.0);
    glVertex2f(-half_w, half_h);

    glEnd();

    if (matrix_changed == FALSE)
        /* glMatrixMode(GL_MODELVIEW); */
        glPopMatrix();

    glEnable(GL_DITHER);
}

static gint choose_mipmap_level(void)
{
    gfloat zoom, mipmap_ratio = MIPMAP_RATIO;
    gint level = 1;

    if (options->mipmap) {
        zoom = get_matrix_zoom();

        while (mipmap_ratio > zoom && level < current_image->nb_maps) {
            mipmap_ratio *= MIPMAP_RATIO;
            level++;
        }
    }

    /* Mipmaps should only be scaled down. */
    return level - 1;
}

static void display_last_image_notice(void)
{
    static GdkGC *gc = NULL;
    const gchar *msg;
    PangoContext *pc;
    PangoLayout *pl;
    gint x = 10, y = 10, dx = 5, dy = 5;
    gint w, h;

    msg = get_image_notice();
    if (msg == NULL)
        return;

    pc = gtk_widget_get_pango_context(gl_widget);
    pl = pango_layout_new(pc);

    if (gc == NULL)
        /* First time. */
        gc = gdk_gc_new(gl_widget->window);

    pango_layout_set_text(pl, msg, -1);

    gdk_gc_set_foreground(gc, &(gl_widget->style->white));
    pango_layout_get_pixel_size(pl, &w, &h);
    gdk_draw_rectangle(gl_widget->window, gc, TRUE,
                       x - dx, y - dy, w + 2 * dx, h + 2 * dy);

    gdk_gc_set_foreground(gc, &(gl_widget->style->black));
    gdk_draw_layout(gl_widget->window, gc, x, y, pl);

    g_object_unref(pl);
}

static gboolean need_alpha_checks(void)
{
    return current_image != NULL && current_image->has_alpha &&
        options->alpha_checks;
}

void draw_current_image(void)
{
    gint mipmap_level;
    gboolean filtering;
    gboolean alpha_checks = need_alpha_checks();

    if (alpha_checks)
        draw_checker();

    write_gl_matrix();

    mipmap_level = choose_mipmap_level();
    if (mipmap_level == 0)
        filtering = is_filtering_needed();
    else
        filtering = TRUE;

    if (alpha_checks) {
        glPushAttrib(GL_ENABLE_BIT);
        glEnable(GL_BLEND);
    }

    call_lists(filtering, mipmap_level);

    if (alpha_checks)
        glPopAttrib();

    if (options->fps > 0)
        gettimeofday(&last_redraw, NULL);
}

/* Called by the timer when a redraw is needed. */
static gboolean redraw(void)
{
    static GdkGLDrawable *gldrawable = NULL;

    if (is_in_transition())
        goto out;

    if (gldrawable == NULL)
        /* First time. */
        gldrawable = gtk_widget_get_gl_drawable(gl_widget);

    gdk_gl_drawable_wait_gdk(gldrawable);
    clear_zoom_frame();

    if (need_alpha_checks() == FALSE) {
        glDisable(GL_DITHER);
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_DITHER);
    }

    if (current_image != NULL)
        draw_current_image();

    gdk_gl_drawable_swap_buffers(gldrawable);
    update_scrollbars();

    gdk_gl_drawable_wait_gl(gldrawable);

    display_last_image_notice();

  out:
    /* To remove the idle func. */
    idle_id = 0;
    return FALSE;
}

/* We are going to change the image. */
gfloat *prepare_rendering(GlivImage * im)
{
    GlivImage *old = current_image;
    gfloat *old_matrix = new_matrix();

    current_image = im;

    delete_selected_image();
    fill_ident(im);
    update_window_title();
    refresh(REFRESH_STATUS);

    current_image = old;

    g_free(im->ident);
    im->ident = NULL;

    if (options->fullscreen == FALSE)
        goto_window(im, FALSE);

    /* Be sure to update the display now. A transition may take place ... */
    gtk_widget_queue_draw(GTK_WIDGET(get_current_window()));
    gdk_window_process_all_updates();
    process_events();

    matrix_cpy(old_matrix, NULL);
    configure_matrix(im);

    return old_matrix;
}

/* Called the first time an image is displayed. */
void render(void)
{
    configure_matrix(current_image);
    prioritize_textures(current_image, TRUE);

    refresh(REFRESH_NOW | APPEND_HISTORY | REFRESH_STATUS);

    /* A bit dumb, but needed on some cards. */
    refresh(REFRESH_NOW);
}

void zoom_in(gfloat ratio)
{
    gint pointer_x, pointer_y;
    gfloat x, y;

    if (options->zoom_pointer) {
        /* The pointer is the zoom center. */
        gdk_window_get_pointer(gl_widget->window, &pointer_x, &pointer_y, NULL);
        x = (gfloat) pointer_x;
        y = (gfloat) pointer_y;
    } else {
        /* The zoom center is the midle of the window. */
        x = rt->wid_size->width / 2.0;
        y = rt->wid_size->height / 2.0;
    }

    matrix_zoom(ratio, x, y);
    refresh(REFRESH_IMAGE | REFRESH_STATUS | APPEND_HISTORY);
}

static gint get_fps(void)
{
    struct timeval now;
    gint diff;

    gettimeofday(&now, NULL);
    if (now.tv_sec - last_redraw.tv_sec > 2)
        return 0;

    diff = (now.tv_sec - last_redraw.tv_sec) * G_USEC_PER_SEC +
        now.tv_usec - last_redraw.tv_usec;

    if (diff == 0)
        return G_USEC_PER_SEC;

    return G_USEC_PER_SEC / diff;
}


static gboolean refresh_image(void)
{
    idle_id = 0;
    refresh(REFRESH_IMAGE);

    return FALSE;
}

void refresh(gint what)
{
    /* GTK does that automatically but with more overhead. */
    if (what & REFRESH_IMAGE) {
        if (idle_id == 0)
            idle_id = gtk_idle_add_priority(GDK_PRIORITY_REDRAW,
                                            (GtkFunction) redraw, NULL);

    } else if (what & REFRESH_BURST) {
        if (options->fps <= 0)
            refresh(REFRESH_IMAGE);

        else if (idle_id == 0) {
            gint fps = get_fps();

            if (fps <= 0)
                refresh(REFRESH_IMAGE);

            else {
                gint diff =
                    G_USEC_PER_SEC / options->fps - G_USEC_PER_SEC / fps;
                diff /= 1000;

                if (diff > 0)
                    idle_id = gtk_timeout_add(diff,
                                              (GtkFunction) refresh_image,
                                              NULL);
                else
                    refresh(REFRESH_IMAGE);
            }
        }
    } else if (what & REFRESH_NOW) {
        if (idle_id != 0)
            g_source_remove(idle_id);

        redraw();
    }

    if (what & REFRESH_STATUS)
        update_status_bar();

    if (what & APPEND_HISTORY)
        append_history();
}
