/*
 * Copyright © 2005 Novell, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Novell, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Novell, Inc. makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#ifdef HAVE_CONFIG_H
#  include "../config.h"
#endif

#include "glitzint.h"

/* whether 't' is a well defined not obviously empty trapezoid */
#define TRAPEZOID_VALID(t) ((t)->left.p1.y != (t)->left.p2.y &&         \
			    (t)->right.p1.y != (t)->right.p2.y &&       \
			    (int) ((t)->bottom - (t)->top) > 0)

/* whether 't' is a well defined not obviously empty trap */
#define TRAP_VALID(t) ((int) ((t)->bottom.y - (t)->top.y) > 0)

typedef struct _glitz_edge {
    glitz_float_t dx, dy;
    glitz_float_t x0, y0;
    glitz_float_t kx, ky;
    glitz_float_t tx, bx;
    glitz_float_t hypx;
    int           hyp;
} glitz_edge_t;

#define EDGE_INIT(e, p1x, p1y, p2x, p2y)        \
    (e)->hyp = 1;                               \
    (e)->dx = (p2x) - (p1x);                    \
    (e)->dy = (p2y) - (p1y);                    \
    if ((e)->dy)                                \
	(e)->kx = (e)->dx / (e)->dy;            \
    else                                        \
	(e)->kx = 0;                            \
    (e)->x0 = (p1x) - (e)->kx * (p1y);          \
    if ((e)->dx)                                \
	(e)->ky = (e)->dy / (e)->dx;            \
    else                                        \
	(e)->ky = 0;                            \
    (e)->y0 = (p1y) - (e)->ky * (p1x)

#define EDGE_X(e, _y) (((e)->kx * (_y)) + (e)->x0)
#define EDGE_Y(e, _x) (((e)->ky * (_x)) + (e)->y0)

#define EDGE_INTERSECT_BOX(upper_x, lower_y, left_y, right_y,   \
			   box_x1, box_x2, box_y1, box_y2,      \
			   max_x, max_y,                        \
			   _x1, _y1, _x2, _y2)                  \
    if (upper_x < (box_x1))                                     \
    {                                                           \
	(_x1) = 0.0f;                                           \
	(_y1) = (left_y) - (box_y1);                            \
    }                                                           \
    else if ((upper_x) > (box_x2))                              \
    {                                                           \
	(_x1) = (max_x);                                        \
	(_y1) = (right_y) - (box_y1);                           \
    }                                                           \
    else                                                        \
    {                                                           \
	(_x1) = (upper_x) - (box_x1);                           \
	(_y1) = 0.0f;                                           \
    }                                                           \
    if ((lower_x) < (box_x1))                                   \
    {                                                           \
	(_x2) = 0.0f;                                           \
	(_y2) = (left_y) - (box_y1);                            \
    }                                                           \
    else if ((lower_x) > (box_x2))                              \
    {                                                           \
	(_x2) = (max_x);                                        \
	(_y2) = (right_y) - (box_y1);                           \
    }                                                           \
    else                                                        \
    {                                                           \
	(_x2) = (lower_x) - (box_x1);                           \
	(_y2) = (max_y);                                        \
    }

#define AREA_ABOVE_LEFT(x1, y1, x2, y2, bottom) \
    ((x1) * (y1) +                              \
     (((x1) + (x2)) / 2.0f) * ((y2) - (y1)) +   \
     (x2) * ((bottom) - (y2)))

/*
  Given a single pixel position this function calculates
  trapezoid pixel coverage.
*/
static glitz_float_t
_glitz_pixel_area (glitz_float_t pixel_x,
		   glitz_float_t pixel_y,
		   glitz_float_t top,
		   glitz_float_t bottom,
		   glitz_edge_t  *left,
		   glitz_edge_t  *right)
{
    glitz_float_t area;
    glitz_float_t upper_x, lower_x;
    glitz_float_t left_y, right_y;
    glitz_float_t pixel_x_1 = pixel_x + 1.0f;
    glitz_float_t pixel_y_1 = pixel_y + 1.0f;
    glitz_float_t x1, x2, y1, y2;

    if (bottom >= pixel_y_1)
	bottom = 1.0f;
    else
	bottom = bottom - pixel_y;

    if (top <= pixel_y)
	top = 0.0f;
    else
	top = top - pixel_y;

    if (right->ky)
    {
	upper_x = EDGE_X (right, pixel_y);
	lower_x = EDGE_X (right, pixel_y_1);

	left_y  = EDGE_Y (right, pixel_x);
	right_y = EDGE_Y (right, pixel_x_1);

	EDGE_INTERSECT_BOX (upper_x, lower_y, left_y, right_y,
			    pixel_x, pixel_x_1, pixel_y, pixel_y_1,
			    1.0f, 1.0f, x1, y1, x2, y2);

	if (bottom <= y1)
	{
	    if (left_y > right_y)
		area = bottom;
	    else
		area = 0.0f;
	}
	else
	{
	    if (bottom < y2)
	    {
		x2 -= right->kx * (y2 - bottom);
		y2 = bottom;
	    }
	    area = AREA_ABOVE_LEFT (x1, y1, x2, y2, bottom);
	}

	if (top <= y1)
	{
	    if (left_y > right_y)
		area -= top;
	}
	else
	{
	    if (top < y2)
	    {
		x2 -= right->kx * (y2 - top);
		y2 = top;
	    }

	    area -= AREA_ABOVE_LEFT (x1, y1, x2, y2, top);
	}
    }
    else
    {
	/* Vertical Edge */
	if (right->x0 < pixel_x_1)
	    area = (right->x0 - pixel_x) * (bottom - top);
	else
	    area = bottom - top;
    }

    if (left->kx)
    {
	upper_x = EDGE_X (left, pixel_y);
	lower_x = EDGE_X (left, pixel_y_1);

	left_y  = EDGE_Y (left, pixel_x);
	right_y = EDGE_Y (left, pixel_x_1);

	EDGE_INTERSECT_BOX (upper_x, lower_y, left_y, right_y,
			    pixel_x, pixel_x_1, pixel_y, pixel_y_1,
			    1.0f, 1.0f, x1, y1, x2, y2);

	if (bottom <= y1)
	{
	    if (left_y > right_y)
		area -= bottom;
	}
	else
	{
	    if (bottom < y2)
	    {
		x2 -= left->kx * (y2 - bottom);
		y2 = bottom;
	    }
	    area -= AREA_ABOVE_LEFT (x1, y1, x2, y2, bottom);
	}

	if (top <= y1)
	{
	    if (left_y > right_y)
		area += top;
	}
	else
	{
	    if (top < y2)
	    {
		x2 -= left->kx * (y2 - top);
		y2 = top;
	    }

	    area += AREA_ABOVE_LEFT (x1, y1, x2, y2, top);
	}
    }
    else
    {
	/* Vertical Edge */
	if (left->x0 > pixel_x)
	    area -= (left->x0 - pixel_x) * (bottom - top);
    }

    return area;
}

#define TRAPINIT(trap, _top, _bottom, _left, _right)    \
    if (!TRAPEZOID_VALID (trap))                        \
	continue;                                       \
							\
    (_top)    = FIXED_TO_FLOAT ((trap)->top);           \
    (_bottom) = FIXED_TO_FLOAT ((trap)->bottom);        \
							\
    (_left)->tx = FIXED_TO_FLOAT ((trap)->left.p1.x);   \
    (_left)->bx = FIXED_TO_FLOAT ((trap)->left.p1.y);   \
							\
    (_right)->tx = FIXED_TO_FLOAT ((trap)->right.p1.x); \
    (_right)->bx = FIXED_TO_FLOAT ((trap)->right.p1.y); \
							\
    EDGE_INIT (_left,                                   \
	       (_left)->tx, (_left)->bx,                \
	       FIXED_TO_FLOAT ((trap)->left.p2.x),      \
	       FIXED_TO_FLOAT ((trap)->left.p2.y));     \
							\
    EDGE_INIT (_right,                                  \
	       (_right)->tx, (_right)->bx,              \
	       FIXED_TO_FLOAT ((trap)->right.p2.x),     \
	       FIXED_TO_FLOAT ((trap)->right.p2.y));    \
							\
    if ((_left)->dx)                                    \
    {                                                   \
	(_left)->tx = EDGE_X (_left, _top);             \
	(_left)->bx = EDGE_X (_left, _bottom);          \
    } else                                              \
	(_left)->tx = (_left)->bx = (_left)->x0;        \
							\
    if ((_right)->dx)                                   \
    {                                                   \
	(_right)->tx = EDGE_X (_right, _top);           \
	(_right)->bx = EDGE_X (_right, _bottom);        \
    } else                                              \
	(_right)->tx = (_right)->bx = (_right)->x0

#define TRAP  glitz_trapezoid_t

#define UNIT  glitz_short_t
#define TRAPS _glitz_add_trapezoids_short
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_int_t
#define TRAPS _glitz_add_trapezoids_int
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_float_t
#define TRAPS _glitz_add_trapezoids_float
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_double_t
#define TRAPS _glitz_add_trapezoids_double
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#undef  TRAP
#undef  TRAPINIT

#define TRAPINIT(trap, _top, _bottom, _left, _right)            \
    if (!TRAP_VALID (trap))                                     \
	continue;                                               \
								\
    (_top)    = FIXED_TO_FLOAT ((trap)->top.y);                 \
    (_bottom) = FIXED_TO_FLOAT ((trap)->bottom.y);              \
								\
    (_left)->tx = FIXED_TO_FLOAT ((trap)->top.left);            \
    (_left)->bx = FIXED_TO_FLOAT ((trap)->bottom.left);         \
								\
    (_right)->tx = FIXED_TO_FLOAT ((trap)->top.right);          \
    (_right)->bx = FIXED_TO_FLOAT ((trap)->bottom.right);       \
								\
    EDGE_INIT (_left,                                           \
	       (_left)->tx, _top,                               \
	       (_left)->bx, _bottom);                           \
								\
    EDGE_INIT (_right,                                          \
	       (_right)->tx, _top,                              \
	       (_right)->bx, _bottom)

#define TRAP  glitz_trap_t

#define UNIT  glitz_short_t
#define TRAPS _glitz_add_traps_short
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_int_t
#define TRAPS _glitz_add_traps_int
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_float_t
#define TRAPS _glitz_add_traps_float
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#define UNIT  glitz_double_t
#define TRAPS _glitz_add_traps_double
#include "glitz_trapimp.h"
#undef  TRAPS
#undef  UNIT

#undef  TRAP
#undef  TRAPINIT

int
glitz_add_trapezoids (glitz_buffer_t    *buffer,
		      int               offset,
		      unsigned int      size,
		      glitz_data_type_t type,
		      glitz_surface_t   *mask,
		      glitz_trapezoid_t *traps,
		      int               n_traps,
		      int               *n_added)
{
    int     count, n = n_traps;
    uint8_t *ptr;

    *n_added = 0;

    ptr = glitz_buffer_map (buffer, GLITZ_BUFFER_ACCESS_WRITE_ONLY);
    if (!ptr)
	return 0;

    ptr += offset;

    switch (type) {
    case GLITZ_DATA_TYPE_SHORT:
	count = _glitz_add_trapezoids_short (ptr, size, mask, traps, &n_traps);
	break;
    case GLITZ_DATA_TYPE_INT:
	count = _glitz_add_trapezoids_int (ptr, size, mask, traps, &n_traps);
	break;
    case GLITZ_DATA_TYPE_DOUBLE:
	count = _glitz_add_trapezoids_double (ptr, size, mask,
					      traps, &n_traps);
	break;
    default:
	count = _glitz_add_trapezoids_float (ptr, size, mask, traps, &n_traps);
	break;
    }

    if (glitz_buffer_unmap (buffer))
	return 0;

    *n_added = n - n_traps;

    return count;
}

int
glitz_add_traps (glitz_buffer_t    *buffer,
		 int               offset,
		 unsigned int      size,
		 glitz_data_type_t type,
		 glitz_surface_t   *mask,
		 glitz_trap_t      *traps,
		 int               n_traps,
		 int               *n_added)
{
    int     count, n = n_traps;
    uint8_t *ptr;

    *n_added = 0;

    ptr = glitz_buffer_map (buffer, GLITZ_BUFFER_ACCESS_WRITE_ONLY);
    if (!ptr)
	return 0;

    ptr += offset;

    switch (type) {
    case GLITZ_DATA_TYPE_SHORT:
	count = _glitz_add_traps_short (ptr, size, mask, traps, &n_traps);
	break;
    case GLITZ_DATA_TYPE_INT:
	count = _glitz_add_traps_int (ptr, size, mask, traps, &n_traps);
	break;
    case GLITZ_DATA_TYPE_DOUBLE:
	count = _glitz_add_traps_double (ptr, size, mask, traps, &n_traps);
	break;
    default:
	count = _glitz_add_traps_float (ptr, size, mask, traps, &n_traps);
	break;
    }

    if (glitz_buffer_unmap (buffer))
	return 0;

    *n_added = n - n_traps;

    return count;
}
