/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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.
 */

#include "config.h"

#include <string.h>
#include <gtk/gtk.h>

#include "libgimpcolor/gimpcolor.h"
#include "libgimpbase/gimpbase.h"
#include "libgimp/gimpuitypes.h"

#include "gimppreviewarea.h"


static void     gimp_preview_area_class_init    (GimpPreviewAreaClass *klass);
static void     gimp_preview_area_init          (GimpPreviewArea      *area);

static void     gimp_preview_area_finalize      (GObject              *object);

static void     gimp_preview_area_size_allocate (GtkWidget            *widget,
                                                 GtkAllocation        *allocation);
static gboolean gimp_preview_area_expose        (GtkWidget            *widget,
                                                 GdkEventExpose       *event);

static GtkDrawingAreaClass *parent_class = NULL;

GType
gimp_preview_area_get_type (void)
{
  static GType view_type = 0;

  if (! view_type)
    {
      static const GTypeInfo view_info =
      {
        sizeof (GimpPreviewAreaClass),
        NULL,           /* base_init */
        NULL,           /* base_finalize */
        (GClassInitFunc) gimp_preview_area_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GimpPreviewArea),
        0,              /* n_preallocs */
        (GInstanceInitFunc) gimp_preview_area_init,
      };

      view_type = g_type_register_static (GTK_TYPE_DRAWING_AREA,
                                          "GimpPreviewArea",
                                          &view_info, 0);
    }

  return view_type;
}

static void
gimp_preview_area_class_init (GimpPreviewAreaClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  object_class->finalize      = gimp_preview_area_finalize;

  widget_class->size_allocate = gimp_preview_area_size_allocate;
  widget_class->expose_event  = gimp_preview_area_expose;

}

static void
gimp_preview_area_init (GimpPreviewArea *area)
{
  area->buf  = NULL;
  area->cmap = NULL;
}

static void
gimp_preview_area_size_allocate (GtkWidget     *widget,
                                 GtkAllocation *allocation)
{
  GimpPreviewArea *area = GIMP_PREVIEW_AREA (widget);

  if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
    GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);

  if (widget->allocation.width  != area->width ||
      widget->allocation.height != area->height)
    {
      area->width  = widget->allocation.width;
      area->height = widget->allocation.height;

      if (area->buf == NULL)
        area->buf = g_new (guchar, area->width * area->height * 3);
      else
        g_renew (guchar, area->buf, area->width * area->height * 3);
    }
}

static void
gimp_preview_area_finalize (GObject *object)
{
  GimpPreviewArea *area = GIMP_PREVIEW_AREA (object);

  if (area->buf)
    {
      g_free (area->buf);
      area->buf = NULL;
    }
  if (area->cmap)
    {
      g_free (area->cmap);
      area->cmap = NULL;
    }
  G_OBJECT_CLASS (parent_class)->finalize (object);
}


static gboolean
gimp_preview_area_expose (GtkWidget      *widget,
                          GdkEventExpose *event)
{
  GimpPreviewArea *area ;
  gint             width, height;


  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GIMP_IS_PREVIEW_AREA (widget), FALSE);

  area = GIMP_PREVIEW_AREA (widget);

  if (! area->buf || !GTK_WIDGET_DRAWABLE (widget))
    return FALSE;

  width  = widget->allocation.width;
  height = widget->allocation.height;

/* draw */
  gdk_draw_rgb_image (widget->window,
                      widget->style->base_gc[GTK_STATE_NORMAL],
                      0, 0, width, height,
                      GDK_RGB_DITHER_MAX,
                      area->buf,
                      width * 3);

  return FALSE;
}


GtkWidget *
gimp_preview_area_new ()
{
  GtkWidget *area = g_object_new (GIMP_TYPE_PREVIEW_AREA, NULL);

  return area;
}
/**
 * gimp_preview_area_from_buf:
 * @area: Pointer to a #GimpPreviewArea.
 * @buf: Pointer to a #guchar buffer that contains the preview data.
 * @x: column offset in buffer.
 * @y: row offset in buffer.
 * @width: Width of buffer in pixels.
 * @height: Height of buffer in pixels.
 * @type: The image type
 *
 * Draw's buf on #GtkDrawingArea.
 **/
void
gimp_preview_area_draw_buf (GimpPreviewArea *area,
                            const guchar    *buf,
                            gint             x,
                            gint             y,
                            gint             width,
                            gint             height,
                            GimpImageType    type)
{
  guchar          *rgb_buf = NULL;
  guchar          *rgb_bufpos;
  const guchar    *bufpos;
  gint             rgb_pos;  /* RGB buffer vars to generate checkerboard  */
  gint             rgb_row;
  gint             rgb_col;
  gint             rgb_width;
  gint             rgb_height;
  gint             pos, col, row;
  gint             widget_width;
  gint             widget_height;
  gint             check;
  GtkWidget       *widget;

  widget = GTK_WIDGET (area);

  /* Check if buffer is allocated */
  g_return_if_fail (area->buf != NULL);
  g_return_if_fail (((type != GIMP_INDEXED_IMAGE)  &&
                     (type != GIMP_INDEXEDA_IMAGE)) ||
                    area->cmap != NULL);                    

  if (! GTK_WIDGET_DRAWABLE (widget))
    return;

  /* Determine widget size */
  widget_width  = widget->allocation.width;
  widget_height = widget->allocation.height;

  /* substract the real drawing offset */
  rgb_width =   width - x;
  rgb_height =  height - y;

  /* Don't go out of positive limmits */
  rgb_width  = MIN (rgb_width, widget_width);
  rgb_height = MIN (rgb_height, widget_height);

  /* Stop if negative limmits */
  g_return_if_fail (rgb_width > 0 && rgb_height > 0);

  rgb_buf = area->buf;

  /* Fill checkerboard if needed */
  if (type == GIMP_RGBA_IMAGE || type == GIMP_GRAY_IMAGE /* FIXME: what about GIMP_INDEXA_IMAGE? */)
    {
      for (rgb_row = 0 ; rgb_row <  widget_height ; rgb_row++)
        for (rgb_col = 0 ; rgb_col <  widget_width ; rgb_col++)
          {
            rgb_pos = (rgb_row * rgb_width + rgb_col) * 3;
            rgb_bufpos = rgb_buf + rgb_pos;

            if ((rgb_row & GIMP_CHECK_SIZE) ^ (rgb_col & GIMP_CHECK_SIZE))
              check = GIMP_CHECK_LIGHT * 255;
            else
              check = GIMP_CHECK_DARK * 255;

            rgb_bufpos[0] = check;
            rgb_bufpos[1] = check;
            rgb_bufpos[2] = check;
          }
    }

  /* Start in real buffer at x,y and  0,0 for preview buffer
   *
   *   Drawing            PreviewArea   Drawing            PreviewArea
   *   0,0                0,0           0,0                0,0
   *   +----------+       +--+--+       +----------+       +-+---+
   *   |  3,1     |       |XX|  |       |          |       |X|   |
   *   |  +--+    |       +--+  |       |        +--+      +-+   |
   *   |  |XX|    |       |     |       |        |XX|      |     |
   *   |  +--+    |       +-----+       |        +--+      +-----+
   *   |          |                     |          |
   *   |          |                     |          |
   *   +----------+                     +----------+
   *
   */
  for (row = y, rgb_row=0; row < height && rgb_row < rgb_height; row++, rgb_row++)
    for (col = x, rgb_col=0; col < width && rgb_col < rgb_width; col++, rgb_col++ )
      {
        switch (type)
          {
            case GIMP_GRAY_IMAGE:
            case GIMP_INDEXED_IMAGE:
              pos = (row * width + col) * 1;
              break;

            case GIMP_INDEXEDA_IMAGE:
            case GIMP_GRAYA_IMAGE:
              pos = (row * width + col) * 2;
              break;

            case GIMP_RGB_IMAGE:
              pos = (row * width + col) * 3;
              break;

            case GIMP_RGBA_IMAGE:
              pos = (row * width + col) * 4;
              break;
          }
        bufpos = buf + pos;

        rgb_pos = (rgb_row * rgb_width + rgb_col) * 3;
        rgb_bufpos = rgb_buf + rgb_pos;

        switch (type)
          {
            case GIMP_GRAY_IMAGE:
              rgb_bufpos[0] = bufpos[0];
              rgb_bufpos[1] = bufpos[0];
              rgb_bufpos[2] = bufpos[0];
              break;

            case GIMP_GRAYA_IMAGE:
              if (buf[pos+1] == 255)
                {
                  rgb_bufpos[0] = bufpos[0];
                  rgb_bufpos[1] = bufpos[0];
                  rgb_bufpos[2] = bufpos[0];
                }
              else if (buf[pos+1] != 0)
                {
                  rgb_bufpos[0] += ((bufpos[0] - rgb_bufpos[0]) * bufpos[1]) / 255;
                  rgb_bufpos[1]  = rgb_bufpos[0];
                  rgb_bufpos[2]  = rgb_bufpos[0];
                }
              break;

            case GIMP_RGB_IMAGE:
              rgb_bufpos[0] = bufpos[0];
              rgb_bufpos[1] = bufpos[1];
              rgb_bufpos[2] = bufpos[2];
              break;

            case GIMP_RGBA_IMAGE:
              if (buf[pos+3] == 255)
                {
                  rgb_bufpos[0] = bufpos[0];
                  rgb_bufpos[1] = bufpos[1];
                  rgb_bufpos[2] = bufpos[2];
                }
              else if (buf[pos+3] != 0)
                {
                  rgb_bufpos[0] += (bufpos[0] - rgb_bufpos[0]) * bufpos[3] / 255;
                  rgb_bufpos[1] += (bufpos[1] - rgb_bufpos[1]) * bufpos[3] / 255;
                  rgb_bufpos[2] += (bufpos[2] - rgb_bufpos[2]) * bufpos[3] / 255;
                }
              break;

            case GIMP_INDEXED_IMAGE:
            case GIMP_INDEXEDA_IMAGE:
              
              /* FIXME: INDEXED images aren't handled... */
              break;
          }
      }
}

/**
 * gimp_preview_area_set_cmap:
 * @area: Pointer to a #GimpPreviewArea.
 * @cmap: Pointer to a #guchar buffer that contains the new colormap.
 * @num_colors: The number of colors in the new colormap.
 *
 * Set a new colormap for the #GtkDrawingArea widget. This is mainly for
 * use with indexed images.
 **/
void gimp_preview_area_set_cmap (GimpPreviewArea *area,
                                 const guchar    *cmap,
                                 gint             num_colors)
{
  if (area->cmap)
    g_free (area->cmap);

  area->cmap = g_new (guchar, 3*num_colors);
  memcpy (area->cmap, cmap, 3*num_colors);
}

