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

/***********************************************
 * Creation and callbacks of the OpenGL widget *
 ***********************************************/

#include <GL/glu.h>
#include "opengl.h"

#include "gliv.h"
#include "gl_widget.h"
#include "options.h"
#include "rendering.h"
#include "messages.h"
#include "windows.h"
#include "matrix.h"
#include "files_list.h"
#include "main.h"
#include "next_image.h"
#include "images_menus.h"
#include "cursors.h"

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

static gint old_width, old_height;

enum {
    TARGET_URI_LIST,
    TARGET_STRING
};

void update_bg_color()
{
    glClearColor(options->bg_col.red / 65535.0, options->bg_col.green / 65535.0,
                 options->bg_col.blue / 65535.0, 1.0);
}

static gboolean expose_event(GtkWidget * unused, GdkEventExpose * event)
{
    if (event->count == 0)
        refresh(REFRESH_IMAGE);

    return FALSE;
}

static void resize_widget(gint width, gint height)
{
    gfloat w2, h2;

    width += width % 2;
    height += height % 2;

    w2 = width / 2.0;
    h2 = height / 2.0;

    gdk_gl_drawable_wait_gdk(gtk_widget_get_gl_drawable(gl_widget));

    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    /* Vertical flip to have coordinates like X but centered like OpenGL. */
    glOrtho(-w2, w2, h2, -h2, -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
}

static gboolean configure_event(GtkWidget * unused, GdkEventConfigure * event)
{
    gint what = REFRESH_IMAGE;

    resize_widget(event->width, event->height);

    if ((options->maximize || options->scaledown) &&
        matrix_set_max_zoom(old_width, old_height, FALSE) == FALSE) {
        /*
         * When options->maximize or options->scaledown is set we zoom only
         * if a matrix_set_max_zoom() with previous dimensions would have been
         * ignored.  It happens if the image properties didn't change after the
         * previous matrix_set_max_zoom().
         * This is achieved by the test of matrix_set_max_zoom().
         */
        matrix_set_max_zoom(event->width, event->height, TRUE);
        what |= REFRESH_STATUS;
    }

    refresh(what);

    old_width = event->width;
    old_height = event->height;

    return FALSE;
}

static gboolean drag_data_received(GtkWidget * unused1,
                                   GdkDragContext * context,
                                   gint unused2, gint unused3,
                                   GtkSelectionData * data, guint info,
                                   guint time)
{
    gchar *uri_list;
    gchar **uris;
    gint i, nb_inserted;
    gboolean ok = FALSE;
    GError *err = NULL;

    /* Based on code from gnome-terminal. */

    if (data->format != 8) {
        DIALOG_MSG(_("Wrong URI format: %d (instead of 8)"), data->format);
        goto out;
    }

    if (data->length <= 0) {
        DIALOG_MSG(_("Wrong URI length: %d"), data->length);
        goto out;
    }

    uri_list = g_strndup(data->data, data->length);
    if (info == TARGET_URI_LIST)
        uris = g_strsplit(uri_list, "\r\n", -1);
    else
        uris = g_strsplit(uri_list, "\n", -1);

    if (uris == NULL)
        goto out_free;

    for (i = 0; uris[i] != NULL && uris[i][0] != '\0'; i++) {
        gchar *uri = uris[i];

        if (info == TARGET_URI_LIST) {
            uri = g_filename_from_uri(uris[i], NULL, &err);
            if (err != NULL) {
                DIALOG_MSG("%s", err->message);
                g_error_free(err);
                goto out_strfreev;
            }
            g_free(uris[i]);
            uris[i] = uri;
        }
    }

    ok = TRUE;
    nb_inserted = insert_after_current(uris, i, FALSE, TRUE);
    new_images(nb_inserted);

  out_strfreev:
    g_strfreev(uris);

  out_free:
    g_free(uri_list);

  out:
    gtk_drag_finish(context, ok, FALSE, time);
    return FALSE;
}

static void dnd_init(void)
{
    GtkTargetEntry targets[] = {
/* *INDENT-OFF* */
        {"text/uri-list", 0, TARGET_URI_LIST},
        {"UTF8_STRING",   0, TARGET_STRING},
        {"COMPOUND_TEXT", 0, TARGET_STRING},
        {"TEXT",          0, TARGET_STRING},
        {"STRING",        0, TARGET_STRING}
/* *INDENT-ON* */
    };

    gtk_drag_dest_set(gl_widget, GTK_DEST_DEFAULT_ALL, targets,
                      G_N_ELEMENTS(targets), GDK_ACTION_COPY);

    g_signal_connect(gl_widget, "drag-data-received",
                     G_CALLBACK(drag_data_received), NULL);
}

/*
 * Probe the max_texture_size with a GL_PROXY_TEXTURE_2D.
 * Used only when initializing the widget.
 */
static gboolean check_texture_size(void)
{
    gint width;

    glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGB,
                 rt->max_texture_size, rt->max_texture_size, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, NULL);

    glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);

    return width == rt->max_texture_size;
}

static void get_max_texture_size(void)
{
#if NO_OPENGL
    rt->max_texture_size = 128;
#else
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &rt->max_texture_size);

    while (rt->max_texture_size > 0 && check_texture_size() == FALSE)
        rt->max_texture_size >>= 1;

    if (rt->max_texture_size < 64) {
        gchar *message, *texture;

        texture = g_strdup_printf(_("Fatal error: GL_MAX_TEXTURE_SIZE = %d\n"),
                                  rt->max_texture_size);

        message = g_strconcat(texture,
                              _("GL_MAX_TEXTURE_SIZE must be >= 64"), NULL);
        DIALOG_MSG("%s", message);

        g_free(texture);
        g_free(message);
        quit(1);
    }
#endif
}

/* Direct all OpenGL calls to the created widget. */
static gboolean make_current(void)
{
    GdkGLContext *glcontext = gtk_widget_get_gl_context(gl_widget);
    GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(gl_widget);

    return gdk_gl_drawable_make_current(gldrawable, glcontext);
}

static gboolean first_expose(void)
{
    /* We only handle the first one. */
    g_signal_handlers_disconnect_by_func(gl_widget, G_CALLBACK(first_expose),
                                         NULL);

    if (make_current() == FALSE) {
        DIALOG_MSG(_("glXMakeCurrent() failed"));
        quit(1);
    }

    glDisable(GL_DEPTH_TEST);

    glEnable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glShadeModel(GL_FLAT);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    update_bg_color();

    get_max_texture_size();

    rt->wid_size = &gl_widget->allocation;

    g_signal_connect(gl_widget, "configure-event",
                     G_CALLBACK(configure_event), NULL);

    g_signal_connect(gl_widget, "expose-event", G_CALLBACK(expose_event), NULL);

    g_signal_connect_after(gl_widget, "focus-in-event",
                           G_CALLBACK(set_correct_cursor), NULL);

    schedule_hide_cursor();

    old_width = rt->wid_size->width;
    old_height = rt->wid_size->height;

    if (options->fullscreen)
        toggle_fullscreen(TRUE);

    resize_widget(rt->wid_size->width, rt->wid_size->height);

    dnd_init();

    gtk_widget_queue_draw(GTK_WIDGET(get_current_window()));
    do_later(G_PRIORITY_LOW, load_first_image);

    return FALSE;
}

void create_gl_widget(void)
{
    GdkGLConfigMode mode = GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH |
        GDK_GL_MODE_DOUBLE | GDK_GL_MODE_ALPHA;

    GdkGLConfig *glconfig;

    if (gdk_gl_query_extension() == FALSE) {
        DIALOG_MSG(_("OpenGL not supported"));
        quit(1);
    }

    glconfig = gdk_gl_config_new_by_mode(mode);

    if (glconfig == NULL)
        /* Try without the alpha channel. */
        glconfig = gdk_gl_config_new_by_mode(mode & ~GDK_GL_MODE_ALPHA);

    if (glconfig == NULL) {
        DIALOG_MSG(_("Cannot find an appropriate visual, try glxinfo(1)"));
        quit(1);
    }

    gl_widget = gtk_drawing_area_new();
    if (gtk_widget_set_gl_capability(gl_widget, glconfig, NULL, TRUE,
                                     GDK_GL_RGBA_TYPE) == FALSE) {
        DIALOG_MSG(_("Cannot set the OpenGL capability"));
        quit(1);
    }

    gtk_widget_add_events(gl_widget,
                          GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK |
                          GDK_BUTTON_RELEASE_MASK);

    if (options->delay != 0)
        gtk_widget_add_events(gl_widget, GDK_POINTER_MOTION_MASK);

    /* The real initialization is done on the first expose. */
    g_signal_connect_after(gl_widget, "expose-event", G_CALLBACK(first_expose),
                           NULL);
}

gint report_opengl_errors(const gchar * function_name,
                          const gchar * source_name, gint line,
                          gint glBegin_inc)
{
    static gint glBegin_counter = 0;
    gint nb_errors = 0;
    gchar *errors = NULL;
    GLenum error;

    /* We cannot call glGetError() between a glBegin()/glEnd() pair. */
    glBegin_counter += glBegin_inc;
    if (glBegin_counter != 0)
        return 0;

    /* Get all errors. */
    while ((error = glGetError()) != GL_NO_ERROR) {
        gchar *error_msg;
        gboolean unknown_error = FALSE;

        error_msg = (gchar *) gluErrorString(error);
        if (error_msg == NULL) {
            unknown_error = TRUE;
            error_msg = g_strdup_printf(_("Unknown OpenGL error (%d)"), error);
        }

        if (errors == NULL)
            errors = g_strdup_printf(_("OpenGL error in %s() at %s:%d:\n%s"),
                                     function_name, source_name, line,
                                     error_msg);
        else {
            gchar *old_msg = errors;
            errors = g_strconcat(errors, "\n", error_msg, NULL);
            g_free(old_msg);
        }

        if (unknown_error)
            g_free(error_msg);

        nb_errors++;
    }

    if (errors != NULL) {
        g_printerr("%s\n", errors);
        g_free(errors);
    }

    return nb_errors;
}
