/*         ______   ___    ___
 *        /\  _  \ /\_ \  /\_ \
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      Bitmap stretching functions.
 *
 *      By Michael Bukin.
 *      Fixed and improved in 2007 by Trent Gamblin.
 *
 *      See readme.txt for copyright information.
 */


#include "allegro.h"



/* Information for stretching line */
static struct {
   int xcstart; /* x counter start */
   int sxinc; /* amount to increment src x every time */
   int xcdec; /* amount to deccrement counter by, increase sptr when this reaches 0 */
   int xcinc; /* amount to increment counter by when it reaches 0 */
   int linesize; /* size of a whole row of pixels */
} _al_stretch;



/* Stretcher macros */
#define DECLARE_STRETCHER(type, size, put, get) \
   int xc = _al_stretch.xcstart; \
   uintptr_t dend = dptr + _al_stretch.linesize; \
   ASSERT(dptr); \
   ASSERT(sptr); \
   for (; dptr < dend; dptr += size, sptr += _al_stretch.sxinc) { \
      put(dptr, get((type*)sptr)); \
      if (xc <= 0) { \
	 sptr += size; \
	 xc += _al_stretch.xcinc; \
      } \
      else \
	 xc -= _al_stretch.xcdec; \
   }



#define DECLARE_MASKED_STRETCHER(type, size, put, get, mask) \
   int xc = _al_stretch.xcstart; \
   uintptr_t dend = dptr + _al_stretch.linesize; \
   ASSERT(dptr); \
   ASSERT(sptr); \
   for (; dptr < dend; dptr += size, sptr += _al_stretch.sxinc) { \
      int color = get((type*)sptr); \
      if (color != mask) \
	 put(dptr, get((type*)sptr)); \
      if (xc <= 0) { \
	 sptr += size; \
	 xc += _al_stretch.xcinc; \
      } \
      else \
	 xc -= _al_stretch.xcdec; \
   }



#ifdef GFX_HAS_VGA
/*
 * Mode-X line stretcher.
 */
static void stretch_linex(uintptr_t dptr, unsigned char *sptr)
{
   int plane;
   int first_xc = _al_stretch.xcstart;
   int dw = _al_stretch.linesize;

   ASSERT(dptr);
   ASSERT(sptr);

   for (plane = 0; plane < 4; plane++) {
      int xc = first_xc;
      unsigned char *s = sptr;
      uintptr_t d = dptr / 4;
      uintptr_t dend = (dptr + dw) / 4;

      outportw(0x3C4, (0x100 << (dptr & 3)) | 2);

      for (; d < dend; d++, s += 4 * _al_stretch.sxinc) {
	 bmp_write8(d, *s);
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
      }

      /* Move to the beginning of next plane.  */
      if (first_xc <= 0) {
	  sptr++;
	  first_xc += _al_stretch.xcinc;
      }
      else
	 first_xc -= _al_stretch.xcdec;

      dptr++;
      sptr += _al_stretch.sxinc;
      dw--;
   }
}

/*
 * Mode-X masked line stretcher.
 */
static void stretch_masked_linex(uintptr_t dptr, unsigned char *sptr)
{
   int plane;
   int dw = _al_stretch.linesize;
   int first_xc = _al_stretch.xcstart;

   ASSERT(dptr);
   ASSERT(sptr);

   for (plane = 0; plane < 4; plane++) {
      int xc = first_xc;
      unsigned char *s = sptr;
      uintptr_t d = dptr / 4;
      uintptr_t dend = (dptr + dw) / 4;

      outportw(0x3C4, (0x100 << (dptr & 3)) | 2);

      for (; d < dend; d++, s += 4 * _al_stretch.sxinc) {
	 unsigned long color = *s;
	 if (color != 0)
	    bmp_write8(d, color);
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
	 if (xc <= 0) s++, xc += _al_stretch.xcinc;
	 else xc -= _al_stretch.xcdec;
      }

      /* Move to the beginning of next plane.  */
      if (first_xc <= 0) {
	 sptr++;
	 first_xc += _al_stretch.xcinc;
      }
      else
	 first_xc -= _al_stretch.xcdec;

      dptr++;
      sptr += _al_stretch.sxinc;
      dw--;
   }
}
#endif



#ifdef ALLEGRO_COLOR8
static void stretch_line8(uintptr_t dptr, unsigned char *sptr)
{
   DECLARE_STRETCHER(unsigned char, 1, bmp_write8, *);
}

static void stretch_masked_line8(uintptr_t dptr, unsigned char *sptr)
{
   DECLARE_MASKED_STRETCHER(unsigned char, 1, bmp_write8, *, 0);
}
#endif



#ifdef ALLEGRO_COLOR16
static void stretch_line15(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_STRETCHER(unsigned short, 2, bmp_write15, *);
}

static void stretch_line16(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_STRETCHER(unsigned short, 2, bmp_write16, *);
}

static void stretch_masked_line15(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_MASKED_STRETCHER(unsigned short, 2, bmp_write15, *, MASK_COLOR_15);
}

static void stretch_masked_line16(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_MASKED_STRETCHER(unsigned short, 2, bmp_write16, *, MASK_COLOR_16);
}
#endif



#ifdef ALLEGRO_COLOR24
static void stretch_line24(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_STRETCHER(unsigned char, 3, bmp_write24, READ3BYTES);
}

static void stretch_masked_line24(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_MASKED_STRETCHER(unsigned char, 3, bmp_write24, READ3BYTES, MASK_COLOR_24);
}
#endif



#ifdef ALLEGRO_COLOR32
static void stretch_line32(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_STRETCHER(uint32_t, 4, bmp_write32, *);
}

static void stretch_masked_line32(uintptr_t dptr, unsigned char* sptr)
{
   DECLARE_MASKED_STRETCHER(uint32_t, 4, bmp_write32, *, MASK_COLOR_32);
}
#endif



/*
 * Stretch blit work-horse.
 */
static void _al_stretch_blit(BITMAP *src, BITMAP *dst,
    int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh,
   int masked)
{
   int y; /* current dst y */
   int yc; /* y counter */
   int sxofs, dxofs; /* start offsets */
   int syinc; /* amount to increment src y each time */
   int ycdec; /* amount to deccrement counter by, increase sy when this reaches 0 */
   int ycinc; /* amount to increment counter by when it reaches 0 */
   int size = 0; /* pixel size */
   int dxbeg, dxend; /* clipping information */
   int dybeg, dyend;
   int i;

   void (*stretch_line)(uintptr_t, unsigned char*) = 0;

   ASSERT(src);
   ASSERT(dst);

   /* vtable hook; not called if dest is a memory surface */   
   if (src->vtable->do_stretch_blit && !is_memory_bitmap(dst)) {
      src->vtable->do_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, masked);
      return;
   }

   if ((sw <= 0) || (sh <= 0) || (dw <= 0) || (dh <= 0))
      return;

   /* Find out which stretcher should be used */
   if (masked) {
      switch (bitmap_color_depth(dst)) {
#ifdef ALLEGRO_COLOR8
	 case 8:
	    if (is_linear_bitmap(dst))
	       stretch_line = stretch_masked_line8;
#ifdef GFX_HAS_VGA
	    else
	       stretch_line = stretch_masked_linex;
#endif
	    size = 1;
	    break;
#endif
#ifdef ALLEGRO_COLOR16
	 case 15:
	    stretch_line = stretch_masked_line15;
	    size = 2;
	    break;
	 case 16:
	    stretch_line = stretch_masked_line16;
	    size = 2;
	    break;
#endif
#ifdef ALLEGRO_COLOR24
	 case 24:
	    stretch_line = stretch_masked_line24;
	    size = 3;
	    break;
#endif
#ifdef ALLEGRO_COLOR32
	 case 32:
	    stretch_line = stretch_masked_line32;
	    size = 4;
	    break;
#endif
      }
   }
   else {
      switch (bitmap_color_depth(dst)) {
#ifdef ALLEGRO_COLOR8
	 case 8:
	    if (is_linear_bitmap(dst))
	       stretch_line = stretch_line8;
#ifdef GFX_HAS_VGA
	    else
	       stretch_line = stretch_linex;
#endif
	    size = 1;
	    break;
#endif
#ifdef ALLEGRO_COLOR16
	 case 15:
	    stretch_line = stretch_line15;
	    size = 2;
	    break;
	 case 16:
	    stretch_line = stretch_line16;
	    size = 2;
	    break;
#endif
#ifdef ALLEGRO_COLOR24
	 case 24:
	    stretch_line = stretch_line24;
	    size = 3;
	    break;
#endif
#ifdef ALLEGRO_COLOR32
	 case 32:
	    stretch_line = stretch_line32;
	    size = 4;
	    break;
#endif
      }
   }

   ASSERT(stretch_line);

   if (dst->clip) {
      dybeg = ((dy > dst->ct) ? dy : dst->ct);
      dyend = (((dy + dh) < dst->cb) ? (dy + dh) : dst->cb);
      if (dybeg >= dyend)
	 return;

      dxbeg = ((dx > dst->cl) ? dx : dst->cl);
      dxend = (((dx + dw) < dst->cr) ? (dx + dw) : dst->cr);
      if (dxbeg >= dxend)
	 return;
   }
   else {
      dxbeg = dx;
      dxend = dx + dw;
      dybeg = dy;
      dyend = dy + dh;
   }

   syinc = sh / dh;
   ycdec = sh - (syinc*dh);
   ycinc = dh - ycdec;
   yc = ycinc;
   sxofs = sx * size;
   dxofs = dx * size;

   _al_stretch.sxinc = sw / dw * size;
   _al_stretch.xcdec = sw - ((sw/dw)*dw);
   _al_stretch.xcinc = dw - _al_stretch.xcdec;
   _al_stretch.linesize = (dxend-dxbeg)*size;

   /* get start state (clip) */
   _al_stretch.xcstart = _al_stretch.xcinc;
   for (i = 0; i < dxbeg-dx; i++, sxofs += _al_stretch.sxinc) {
      if (_al_stretch.xcstart <= 0) {
	 _al_stretch.xcstart += _al_stretch.xcinc;
	 sxofs += size;
      }
      else
	 _al_stretch.xcstart -= _al_stretch.xcdec;
   }

   dxofs += i * size;

   /* skip clipped lines */
   for (y = dy; y < dybeg; y++, sy += syinc) {
      if (yc <= 0) {
	 sy++;
	 yc += ycinc;
      }
      else
	    yc -= ycdec;
   }

   /* Stretch it */

   bmp_select(dst);

   for (; y < dyend; y++, sy += syinc) {
      (*stretch_line)(bmp_write_line(dst, y) + dxofs, src->line[sy] + sxofs);
      if (yc <= 0) {
	 sy++;
	 yc += ycinc;
      }
      else
	    yc -= ycdec;
   }
   
   bmp_unwrite_line(dst);
}



/* stretch_blit:
 *  Opaque bitmap scaling function.
 */
void stretch_blit(BITMAP *src, BITMAP *dst, int sx, int sy, int sw, int sh,
		  int dx, int dy, int dw, int dh)
{
   ASSERT(src);
   ASSERT(dst);

   #ifdef ALLEGRO_MPW
      if (is_system_bitmap(src) && is_system_bitmap(dst))
         system_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh);
      else
   #endif
         _al_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, 0);
}



/* masked_stretch_blit:
 *  Masked bitmap scaling function.
 */
void masked_stretch_blit(BITMAP *src, BITMAP *dst, int sx, int sy, int sw, int sh,
                         int dx, int dy, int dw, int dh)
{
   ASSERT(src);
   ASSERT(dst);

   _al_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, 1);
}



/* stretch_sprite:
 *  Masked version of stretch_blit().
 */
void stretch_sprite(BITMAP *dst, BITMAP *src, int x, int y, int w, int h)
{
   ASSERT(src);
   ASSERT(dst);

   _al_stretch_blit(src, dst, 0, 0, src->w, src->h, x, y, w, h, 1);
}
