/* 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 <glib-object.h>

#include "core-types.h"

#include "base/pixel-region.h"
#include "base/temp-buf.h"

#include "paint-funcs/paint-funcs.h"

#include "config/gimpcoreconfig.h"

#include "gimp.h"
#include "gimp-utils.h"
#include "gimpdrawable-preview.h"
#include "gimpimage.h"
#include "gimpimage-preview.h"
#include "gimplayer.h"
#include "gimplayermask.h"
#include "gimplist.h"


void
gimp_image_get_preview_size (GimpViewable *viewable,
                             gint          size,
                             gboolean      is_popup,
                             gboolean      dot_for_dot,
                             gint         *width,
                             gint         *height)
{
  GimpImage *gimage = GIMP_IMAGE (viewable);

  if (! gimage->gimp->config->layer_previews && ! is_popup)
    {
      *width  = size;
      *height = size;
      return;
    }

  gimp_viewable_calc_preview_size (gimage->width,
                                   gimage->height,
                                   size,
                                   size,
                                   dot_for_dot,
                                   gimage->xresolution,
                                   gimage->yresolution,
                                   width,
                                   height,
                                   NULL);
}

gboolean
gimp_image_get_popup_size (GimpViewable *viewable,
                           gint          width,
                           gint          height,
                           gboolean      dot_for_dot,
                           gint         *popup_width,
                           gint         *popup_height)
{
  GimpImage *gimage = GIMP_IMAGE (viewable);

  if (! gimage->gimp->config->layer_previews)
    return FALSE;

  if (gimage->width > width || gimage->height > height)
    {
      gboolean scaling_up;

      gimp_viewable_calc_preview_size (gimage->width,
                                       gimage->height,
                                       width  * 2,
                                       height * 2,
                                       dot_for_dot, 1.0, 1.0,
                                       popup_width,
                                       popup_height,
                                       &scaling_up);

      if (scaling_up)
        {
          *popup_width  = gimage->width;
          *popup_height = gimage->height;
        }

      return TRUE;
    }

  return FALSE;
}

TempBuf *
gimp_image_get_preview (GimpViewable *viewable,
			gint          width,
			gint          height)
{
  GimpImage *gimage = GIMP_IMAGE (viewable);

  if (! gimage->gimp->config->layer_previews)
    return NULL;

  if (gimage->comp_preview_valid            &&
      gimage->comp_preview->width  == width &&
      gimage->comp_preview->height == height)
    {
      /*  The easy way  */
      return gimage->comp_preview;
    }
  else
    {
      /*  The hard way  */
      if (gimage->comp_preview)
	temp_buf_free (gimage->comp_preview);

      /*  Actually construct the composite preview from the layer previews!
       *  This might seem ridiculous, but it's actually the best way, given
       *  a number of unsavory alternatives.
       */
      gimage->comp_preview = gimp_image_get_new_preview (viewable,
							 width, height);

      gimage->comp_preview_valid = TRUE;

      return gimage->comp_preview;
    }
}

TempBuf *
gimp_image_get_new_preview (GimpViewable *viewable,
			    gint          width,
			    gint          height)
{
  GimpImage   *gimage;
  GimpLayer   *layer;
  GimpLayer   *floating_sel;
  PixelRegion  src1PR, src2PR, maskPR;
  PixelRegion *mask;
  TempBuf     *comp;
  TempBuf     *layer_buf;
  TempBuf     *mask_buf;
  GList       *list;
  GSList      *reverse_list = NULL;
  gdouble      ratio;
  gint         x, y, w, h;
  gint         x1, y1, x2, y2;
  gint         bytes;
  gboolean     construct_flag;
  gboolean     visible_components[MAX_CHANNELS] = { TRUE, TRUE, TRUE, TRUE };
  gint         off_x, off_y;

  gimage = GIMP_IMAGE (viewable);

  if (! gimage->gimp->config->layer_previews)
    return NULL;

  ratio = (gdouble) width / (gdouble) gimage->width;

  switch (gimp_image_base_type (gimage))
    {
    case GIMP_RGB:
    case GIMP_INDEXED:
      bytes = 4;
      break;
    case GIMP_GRAY:
      bytes = 2;
      break;
    default:
      bytes = 0;
      g_assert_not_reached ();
      break;
    }

  /*  The construction buffer  */
  comp = temp_buf_new (width, height, bytes, 0, 0, NULL);
  temp_buf_data_clear (comp);

  floating_sel = NULL;

  for (list = GIMP_LIST (gimage->layers)->list;
       list;
       list = g_list_next (list))
    {
      layer = (GimpLayer *) list->data;

      /*  only add layers that are visible to the list  */
      if (gimp_item_get_visible (GIMP_ITEM (layer)))
	{
	  /*  floating selections are added right above the layer
	   *  they are attached to
	   */
	  if (gimp_layer_is_floating_sel (layer))
	    {
	      floating_sel = layer;
	    }
	  else
	    {
	      if (floating_sel &&
		  floating_sel->fs.drawable == GIMP_DRAWABLE (layer))
		{
		  reverse_list = g_slist_prepend (reverse_list, floating_sel);
		}

	      reverse_list = g_slist_prepend (reverse_list, layer);
	    }
	}
    }

  construct_flag = FALSE;

  for (; reverse_list; reverse_list = g_slist_next (reverse_list))
    {
      gint     src_x, src_y;
      gint     src_width, src_height;
      gboolean use_sub_preview = FALSE;

      layer = (GimpLayer *) reverse_list->data;

      gimp_item_offsets (GIMP_ITEM (layer), &off_x, &off_y);

      if (! gimp_rectangle_intersect (0, 0,
                                      gimp_item_width  (GIMP_ITEM (layer)),
                                      gimp_item_height (GIMP_ITEM (layer)),
                                      -off_x, -off_y,
                                      gimage->width, gimage->height,
                                      &src_x, &src_y,
                                      &src_width, &src_height))
        {
          continue;
        }

      x = (gint) RINT (ratio * off_x);
      y = (gint) RINT (ratio * off_y);
      w = (gint) RINT (ratio * gimp_item_width  (GIMP_ITEM (layer)));
      h = (gint) RINT (ratio * gimp_item_height (GIMP_ITEM (layer)));

      if (w < 1 || h < 1)
	continue;

      if ((w * h) > (width * height * 4))
        use_sub_preview = TRUE;

      x1 = CLAMP (x, 0, width);
      y1 = CLAMP (y, 0, height);
      x2 = CLAMP (x + w, 0, width);
      y2 = CLAMP (y + h, 0, height);

      src1PR.bytes     = comp->bytes;
      src1PR.x         = x1;
      src1PR.y         = y1;
      src1PR.w         = (x2 - x1);
      src1PR.h         = (y2 - y1);
      src1PR.rowstride = comp->width * src1PR.bytes;
      src1PR.data      = (temp_buf_data (comp) +
                          y1 * src1PR.rowstride + x1 * src1PR.bytes);

      if (use_sub_preview)
        {
          layer_buf = gimp_drawable_get_sub_preview (GIMP_DRAWABLE (layer),
                                                     src_x, src_y,
                                                     src_width, src_height,
                                                     (x2 - x1),
                                                     (y2 - y1));

          g_assert (layer_buf);
          g_assert (layer_buf->bytes <= comp->bytes);

          src2PR.bytes     = layer_buf->bytes;
          src2PR.x         = 0;
          src2PR.y         = 0;
          src2PR.w         = src1PR.w;
          src2PR.h         = src1PR.h;
          src2PR.rowstride = layer_buf->width * src2PR.bytes;
          src2PR.data      = temp_buf_data (layer_buf);
        }
      else
        {
          layer_buf = gimp_viewable_get_preview (GIMP_VIEWABLE (layer), w, h);

          g_assert (layer_buf);
          g_assert (layer_buf->bytes <= comp->bytes);

          src2PR.bytes     = layer_buf->bytes;
          src2PR.x         = src1PR.x;
          src2PR.y         = src1PR.y;
          src2PR.w         = src1PR.w;
          src2PR.h         = src1PR.h;
          src2PR.rowstride = layer_buf->width * src2PR.bytes;
          src2PR.data      = (temp_buf_data (layer_buf) +
                              (y1 - y) * src2PR.rowstride +
                              (x1 - x) * src2PR.bytes);
        }

      if (layer->mask && layer->mask->apply_mask)
	{
          if (use_sub_preview)
            {
              mask_buf =
                gimp_drawable_get_sub_preview (GIMP_DRAWABLE (layer->mask),
                                               src_x, src_y,
                                               src_width, src_height,
                                               x2 - x1,
                                               y2 - y1);

              maskPR.bytes     = mask_buf->bytes;
              maskPR.x         = 0;
              maskPR.y         = 0;
              maskPR.w         = src1PR.w;
              maskPR.h         = src1PR.h;
              maskPR.rowstride = mask_buf->width * mask_buf->bytes;
              maskPR.data      = mask_buf_data (mask_buf);
            }
          else
            {
              mask_buf = gimp_viewable_get_preview (GIMP_VIEWABLE (layer->mask),
                                                    w, h);

              maskPR.bytes     = mask_buf->bytes;
              maskPR.x         = src1PR.x;
              maskPR.y         = src1PR.y;
              maskPR.w         = src1PR.w;
              maskPR.h         = src1PR.h;
              maskPR.rowstride = mask_buf->width * mask_buf->bytes;
              maskPR.data      = (mask_buf_data (mask_buf) +
                                  (y1 - y) * maskPR.rowstride +
                                  (x1 - x) * maskPR.bytes);
            }

          mask = &maskPR;
	}
      else
	{
          mask_buf = NULL;
	  mask     = NULL;
	}

      /*  Based on the type of the layer, project the layer onto the
       *   composite preview...
       *  Indexed images are actually already converted to RGB and RGBA,
       *   so just project them as if they were type "intensity"
       *  Send in all TRUE for visible since that info doesn't matter
       *   for previews
       */
      if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
	{
	  if (! construct_flag)
	    initial_region (&src2PR, &src1PR,
			    mask, NULL,
                            layer->opacity * 255.999,
			    layer->mode,
                            visible_components,
                            INITIAL_INTENSITY_ALPHA);
	  else
	    combine_regions (&src1PR, &src2PR, &src1PR,
			     mask, NULL,
                             layer->opacity * 255.999,
			     layer->mode,
                             visible_components,
                             COMBINE_INTEN_A_INTEN_A);
        }
      else
        {
	  if (! construct_flag)
	    initial_region (&src2PR, &src1PR,
			    mask, NULL,
                            layer->opacity * 255.999,
			    layer->mode,
                            visible_components,
                            INITIAL_INTENSITY);
	  else
	    combine_regions (&src1PR, &src2PR, &src1PR,
			     mask, NULL,
                             layer->opacity * 255.999,
			     layer->mode,
                             visible_components,
                             COMBINE_INTEN_A_INTEN);
        }

      if (use_sub_preview)
        {
          temp_buf_free (layer_buf);

          if (mask_buf)
            temp_buf_free (mask_buf);
        }

      construct_flag = TRUE;
    }

  g_slist_free (reverse_list);

  return comp;
}
