/*
 * Copyright © 2026  Behdad Esfahbod
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Author(s): Behdad Esfahbod
 */

#ifndef HB_RASTER_PAINT_HH
#define HB_RASTER_PAINT_HH

#include "hb.hh"

#include "hb-raster-image.hh"
#include "hb-geometry.hh"

/* hb_raster_clip_t — alpha mask for clipping */
struct hb_raster_clip_t
{
  hb_vector_t<uint8_t> alpha;	/* A8 mask, same extents as root surface */
  unsigned width  = 0;
  unsigned height = 0;
  unsigned stride = 0;

  /* Fast path: simple rectangle (no alpha buffer needed) */
  bool is_rect = true;
  int rect_x0 = 0, rect_y0 = 0;
  int rect_x1 = 0, rect_y1 = 0;

  /* Bounding box of non-zero alpha region (valid for both rect and mask) */
  unsigned min_x = 0, min_y = 0;
  unsigned max_x = 0, max_y = 0;

  void init_full (unsigned w, unsigned h)
  {
    width = w;
    height = h;
    stride = (w + 3u) & ~3u;
    is_rect = true;
    rect_x0 = 0;
    rect_y0 = 0;
    rect_x1 = (int) w;
    rect_y1 = (int) h;
    min_x = 0;
    min_y = 0;
    max_x = w;
    max_y = h;
  }

  void update_bounds_from_rect ()
  {
    min_x = (unsigned) hb_max (rect_x0, 0);
    min_y = (unsigned) hb_max (rect_y0, 0);
    max_x = (unsigned) hb_max (hb_min (rect_x1, (int) width), 0);
    max_y = (unsigned) hb_max (hb_min (rect_y1, (int) height), 0);
  }

  uint8_t get_alpha (unsigned x, unsigned y) const
  {
    if (is_rect)
      return ((int) x >= rect_x0 && (int) x < rect_x1 &&
	      (int) y >= rect_y0 && (int) y < rect_y1) ? 255 : 0;
    if (x >= width || y >= height) return 0;
    if (stride < width) return 0;
    if (height && stride > (size_t) -1 / height) return 0;
    size_t idx = (size_t) y * stride + x;
    if (!alpha.arrayZ || idx >= alpha.length) return 0;
    return alpha[idx];
  }

  bool has_valid_alpha_mask () const
  {
    if (is_rect)
      return true;
    if (!alpha.arrayZ || stride < width)
      return false;
    if (height && stride > (size_t) -1 / height)
      return false;
    return (size_t) stride * height <= alpha.length;
  }
};


/* hb_raster_paint_t — color glyph paint context */
struct hb_raster_paint_t
{
  hb_object_header_t header;

  /* Configuration */
  hb_transform_t<>    base_transform     = {1, 0, 0, 1, 0, 0};
  float               x_scale_factor     = 1.f;
  float               y_scale_factor     = 1.f;
  hb_raster_extents_t fixed_extents      = {};
  bool                has_extents  = false;
  hb_color_t          foreground         = HB_COLOR (0, 0, 0, 255);
  hb_color_t          background         = HB_COLOR (0, 0, 0, 0);
  unsigned            palette            = 0;
  hb_map_t           *custom_palette     = nullptr;

  /* Stacks */
  hb_vector_t<hb_transform_t<>>     transform_stack;
  hb_vector_t<hb_raster_clip_t>     clip_stack;
  hb_vector_t<hb_raster_clip_t>     clip_cache;
  hb_vector_t<hb_raster_image_t *>  surface_stack;

  /* Cached surface pool (freelist for reuse across push/pop group) */
  hb_vector_t<hb_raster_image_t *>  surface_cache;
  hb_vector_t<hb_color_stop_t>      scratch_color_stops;

  /* Internal rasterizer for clip-to-glyph */
  hb_raster_draw_t *clip_rdr = nullptr;

  /* Cumulative work budget for the current paint session; reset by
   * hb_raster_paint_clear().  Bounds total pixel and outline work so
   * that per-node costs cannot multiply with the paint-graph traversal
   * limits of the font tables driving us (e.g. COLR). */
  int64_t work_left = HB_RASTER_MAX_PAINT_WORK;

  /* Helpers */

  /* Returns whether the operation should proceed.  Allows a single
   * overshoot past zero; callers skip work once the budget is spent. */
  bool charge_work (int64_t work)
  {
    if (unlikely (work_left <= 0)) return false;
    work_left -= work;
    return true;
  }

  hb_raster_image_t *acquire_surface ()
  {
    hb_raster_image_t *img;
    if (surface_cache.length)
      img = surface_cache.pop ();
    else
    {
      img = hb_raster_image_create_or_fail ();
      if (unlikely (!img)) return nullptr;
    }

    if (unlikely (!img->configure (HB_RASTER_FORMAT_BGRA32, fixed_extents)))
    {
      hb_raster_image_destroy (img);
      return nullptr;
    }
    img->clear ();
    return img;
  }

  void release_surface (hb_raster_image_t *img)
  {
    if (!surface_cache.push_or_fail (img))
      hb_raster_image_destroy (img);
  }

  hb_raster_clip_t acquire_clip (unsigned w, unsigned h)
  {
    hb_raster_clip_t clip;
    if (clip_cache.length)
      clip = clip_cache.pop ();
    clip.width = w;
    clip.height = h;
    clip.stride = (w + 3u) & ~3u;
    clip.is_rect = false;
    clip.rect_x0 = clip.rect_y0 = 0;
    clip.rect_x1 = clip.rect_y1 = 0;
    clip.min_x = clip.min_y = clip.max_x = clip.max_y = 0;
    return clip;
  }

  void release_clip (hb_raster_clip_t &&clip)
  {
    if (clip.alpha.arrayZ)
      clip_cache.push (std::move (clip));
  }

  void release_all_clips ()
  {
    while (clip_stack.length)
      release_clip (clip_stack.pop ());
  }

  hb_raster_image_t *current_surface ()
  {
    return surface_stack.length ? surface_stack.tail () : nullptr;
  }

  hb_raster_clip_t &current_clip ()
  {
    return clip_stack.tail ();
  }

  hb_transform_t<> current_transform ()
  {
    if (unlikely (!transform_stack.length))
      return {1, 0, 0, 1, 0, 0};
    return transform_stack.tail ();
  }

  void apply_scale_factor (hb_transform_t<> &t) const
  {
    t.xx /= x_scale_factor;
    t.xy /= x_scale_factor;
    t.x0 /= x_scale_factor;
    t.yx /= y_scale_factor;
    t.yy /= y_scale_factor;
    t.y0 /= y_scale_factor;
  }

  hb_transform_t<> current_effective_transform ()
  {
    hb_transform_t<> t = current_transform ();
    apply_scale_factor (t);
    return t;
  }

  bool fetch_color_stops (hb_color_line_t *color_line)
  {
    unsigned count = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr);
    if (unlikely (!count || !scratch_color_stops.resize (count)))
    {
      scratch_color_stops.resize (0);
      return false;
    }
    hb_color_line_get_color_stops (color_line, 0, &count, scratch_color_stops.arrayZ);
    return true;
  }
};


#endif /* HB_RASTER_PAINT_HH */
