/*
   (c) Copyright 2001-2008  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; 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 <directfb.h>

#include <direct/util.h>

#include <misc/util.h>

#include <gfx/clip.h>

#define REGION_CODE(x,y,cx1,cx2,cy1,cy2) ( ( (y) > (cy2) ? 8 : 0) | \
                                           ( (y) < (cy1) ? 4 : 0) | \
                                           ( (x) > (cx2) ? 2 : 0) | \
                                           ( (x) < (cx1) ? 1 : 0) )


DFBBoolean
dfb_clip_line( const DFBRegion *clip, DFBRegion *line )
{
     unsigned char region_code1 = REGION_CODE( line->x1, line->y1,
                                               clip->x1,
                                               clip->x2,
                                               clip->y1,
                                               clip->y2 );

     unsigned char region_code2 = REGION_CODE( line->x2, line->y2,
                                               clip->x1,
                                               clip->x2,
                                               clip->y1,
                                               clip->y2 );

     while (region_code1 | region_code2) {
          /* line completely outside the clipping rectangle */
          if (region_code1 & region_code2)
              return DFB_FALSE;


          if (region_code1) {
               if (region_code1 & 8) { /* divide line at bottom*/
                    line->x1 = line->x1 +(line->x2-line->x1) * (clip->y2 - line->y1) / (line->y2-line->y1);
                    line->y1 = clip->y2;
               }
               else
                    if (region_code1 & 4) { /* divide line at top*/
                    line->x1 = line->x1 +(line->x2-line->x1) * (clip->y1 - line->y1) / (line->y2-line->y1);
                    line->y1 = clip->y1;
               }
               else
                    if (region_code1 & 2) { /* divide line at right*/
                    line->y1 = line->y1 +(line->y2-line->y1) * (clip->x2 - line->x1) / (line->x2-line->x1);
                    line->x1 = clip->x2;
               }
               else
                    if (region_code1 & 1) { /* divide line at right*/
                    line->y1 = line->y1 +(line->y2-line->y1) * (clip->x1 - line->x1) / (line->x2-line->x1);
                    line->x1 = clip->x1;
               }
               region_code1 = REGION_CODE( line->x1, line->y1,
                                           clip->x1,
                                           clip->x2,
                                           clip->y1,
                                           clip->y2 );
          }
          else {
               if (region_code2 & 8) {  /* divide line at bottom*/
                    line->x2 = line->x1 +(line->x2-line->x1) * (clip->y2 - line->y1) / (line->y2-line->y1);
                    line->y2 = clip->y2;
               }
               else
                    if (region_code2 & 4) { /* divide line at top*/
                    line->x2 = line->x1 +(line->x2-line->x1) * (clip->y1 - line->y1) / (line->y2-line->y1);
                    line->y2 = clip->y1;
               }
               else
                    if (region_code2 & 2) { /* divide line at right*/
                    line->y2 = line->y1 +(line->y2-line->y1) * (clip->x2 - line->x1) / (line->x2-line->x1);
                    line->x2 = clip->x2;
               }
               else
                    if (region_code2 & 1) { /* divide line at right*/
                    line->y2 = line->y1 +(line->y2-line->y1) * (clip->x1 - line->x1) / (line->x2-line->x1);
                    line->x2 = clip->x1;
               }
               region_code2 = REGION_CODE( line->x2, line->y2, clip->x1,
                                                     clip->x2,
                                                     clip->y1,
                                                     clip->y2 );
          }
     }

     /* successfully clipped or clipping not neccessary */
     return DFB_TRUE;
}

DFBEdgeFlags
dfb_clip_edges( const DFBRegion *clip, DFBRectangle *rect )
{
     DFBEdgeFlags flags = DFEF_ALL;

     if ((clip->x1 >= rect->x + rect->w) ||
         (clip->x2 < rect->x) ||
         (clip->y1 >= rect->y + rect->h) ||
         (clip->y2 < rect->y))
          return DFEF_NONE;

     if (clip->x1 > rect->x) {
          rect->w += rect->x - clip->x1;
          rect->x = clip->x1;

          flags &= ~DFEF_LEFT;
     }

     if (clip->y1 > rect->y) {
          rect->h += rect->y - clip->y1;
          rect->y = clip->y1;

          flags &= ~DFEF_TOP;
     }

     if (clip->x2 < rect->x + rect->w - 1) {
          rect->w = clip->x2 - rect->x + 1;

          flags &= ~DFEF_RIGHT;
     }

     if (clip->y2 < rect->y + rect->h - 1) {
          rect->h = clip->y2 - rect->y + 1;

          flags &= ~DFEF_BOTTOM;
     }

     return flags;
}

DFBBoolean
dfb_clip_rectangle( const DFBRegion *clip, DFBRectangle *rect )
{
     if ((clip->x1 >= rect->x + rect->w) ||
         (clip->x2 < rect->x) ||
         (clip->y1 >= rect->y + rect->h) ||
         (clip->y2 < rect->y))
          return DFB_FALSE;

     if (clip->x1 > rect->x) {
          rect->w += rect->x - clip->x1;
          rect->x = clip->x1;
     }

     if (clip->y1 > rect->y) {
          rect->h += rect->y - clip->y1;
          rect->y = clip->y1;
     }

     if (clip->x2 < rect->x + rect->w - 1)
          rect->w = clip->x2 - rect->x + 1;

     if (clip->y2 < rect->y + rect->h - 1)
          rect->h = clip->y2 - rect->y + 1;

     return DFB_TRUE;
}

DFBBoolean
dfb_clip_triangle_precheck( const DFBRegion *clip, const DFBTriangle *tri )
{
    int x, y, w, h;

    x = MIN (MIN (tri->x1, tri->x2), tri->x3);
    y = MIN (MIN (tri->y1, tri->y2), tri->y3);
    w = MAX (MAX (tri->x1, tri->x2), tri->x3) - x;
    h = MAX (MAX (tri->y1, tri->y2), tri->y3) - y;

    if (clip->x1 > x ||
        clip->x2 < x + w ||
        clip->y1 > y ||
        clip->y2 < y + h)
      return DFB_FALSE;

    return DFB_TRUE;
}

static void 
dfb_add_unique_point(int x, int y, DFBPoint *p, int *num)
{
    int i;

    for(i=0; i<*num; i++)
    {
        if ((x == p[i].x) &&
            (y == p[i].y))
        {
            /* Duplicate point - don't add - return */
            return;
        }
    }
    p[*num].x = x;
    p[*num].y = y;
    (*num)++;
}

static int 
dfb_cross_product(DFBPoint test, DFBPoint apex1, DFBPoint apex2) 
{
    return((test.x-apex1.x) * (apex2.y-apex1.y)) - 
          ((test.y-apex1.y) * (apex2.x-apex1.x)); 
}

static DFBBoolean 
dfb_point_in_triangle(int x, int y, const DFBTriangle *tri) 
{
    DFBPoint test;
    DFBPoint triangle[3];
    int crossProductValue[3];

    test.x = x; test.y = y;
    triangle[0].x = tri->x1; triangle[0].y = tri->y1;
    triangle[1].x = tri->x2; triangle[1].y = tri->y2;
    triangle[2].x = tri->x3; triangle[2].y = tri->y3;

    crossProductValue[0] = dfb_cross_product(test, triangle[0], triangle[1]);
    crossProductValue[1] = dfb_cross_product(test, triangle[1], triangle[2]);
    crossProductValue[2] = dfb_cross_product(test, triangle[2], triangle[0]);

    return(((crossProductValue[0] < 0) && (crossProductValue[1] < 0) && (crossProductValue[2]) < 0) ||
           ((crossProductValue[0] > 0) && (crossProductValue[1] > 0) && (crossProductValue[2]) > 0)) ? DFB_TRUE : DFB_FALSE;
}

static void 
dfb_sort_polygon_points(DFBPoint *p, int *num, DFBPoint centre)
{
    DFBBoolean swap = DFB_TRUE;

    /* Simple bubble sort to form an clockwise polygon */
    while (swap)
    {
        int i;
        swap = DFB_FALSE;
        for (i=1; i<*num; i++)
        {
            if (dfb_cross_product(centre, p[i-1], p[i]) < 0)
            {
                DFBPoint tmp;
                tmp.x = p[i].x;
                tmp.y = p[i].y;
    
                p[i].x = p[i-1].x;
                p[i].y = p[i-1].y;
                p[i-1].x = tmp.x;
                p[i-1].y = tmp.y;
                swap = DFB_TRUE;
            }
        }
    }
}

DFBBoolean
dfb_clip_triangle( const DFBRegion *clip, const DFBTriangle *tri, DFBPoint p[6], int *num )
{
    *num = 0;

    /* Check to see if triangle needs clipping */
    if (dfb_clip_triangle_precheck( clip, tri ))
    {
        p[0].x = tri->x1; p[0].y = tri->y1; 
        p[1].x = tri->x2; p[1].y = tri->y2; 
        p[2].x = tri->x3; p[2].y = tri->y3;
        *num = 3;
    }
    else
    {
        int i;
        DFBRegion edges[3];

        /* Initialize edges. */
        edges[0].x1 = tri->x1; edges[0].y1 = tri->y1;
        edges[0].x2 = tri->x2; edges[0].y2 = tri->y2;
        edges[1].x1 = tri->x2; edges[1].y1 = tri->y2;
        edges[1].x2 = tri->x3; edges[1].y2 = tri->y3;
        edges[2].x1 = tri->x3; edges[2].y1 = tri->y3;
        edges[2].x2 = tri->x1; edges[2].y2 = tri->y1;

        for (i = 0; i < 3; i++)
        {
            /* Clip the edge to the clipping region. */
            if (dfb_clip_line( clip, &edges[i] ))
            {
                /* Add the clipped points */
                dfb_add_unique_point(edges[i].x1, edges[i].y1, p, num);
                dfb_add_unique_point(edges[i].x2, edges[i].y2, p, num);
            }
        }

        /* Add clip points to list of points if they are inside the triangle */
        if (dfb_point_in_triangle(clip->x1, clip->y1, tri))
        {
            dfb_add_unique_point(clip->x1, clip->y1, p, num);
        }
        if (dfb_point_in_triangle(clip->x1, clip->y2, tri))
        {
            dfb_add_unique_point(clip->x1, clip->y2, p, num);
        }
        if (dfb_point_in_triangle(clip->x2, clip->y1, tri))
        {
            dfb_add_unique_point(clip->x2, clip->y1, p, num);
        }
        if (dfb_point_in_triangle(clip->x2, clip->y2, tri))
        {
            dfb_add_unique_point(clip->x2, clip->y2, p, num);
        }

        if (*num < 3)
        {
            /* Not enough points for a triangle */
            *num = 0;
        }
        else
        {
            if (*num > 3)
            {
                DFBPoint centre;
                /* Too many points for a triangle - created a sort polygon */
                centre.x = 0;
                centre.y = 0;

                /* Find the centre point of the polygon */
                for (i = 0; i < *num; i++)
                {
                    centre.x += p[i].x;
                    centre.y += p[i].y;
                }
                centre.x /= *num;
                centre.y /= *num;

                /* Sort the points */
                dfb_sort_polygon_points(p, num, centre);
            }
        }
    }

    /* Actually fail if the number of vertices is below 3. */
    return(*num >= 3);
}

void
dfb_clip_blit( const DFBRegion *clip,
               DFBRectangle *srect, int *dx, int *dy )
{
     if (clip->x1 > *dx ) {
          srect->w = MIN( (clip->x2 - clip->x1) + 1,
                    (*dx + srect->w) - clip->x1);

          srect->x+= clip->x1 - *dx;
          *dx = clip->x1;
     }
     else if (clip->x2 < *dx + srect->w - 1) {
          srect->w = clip->x2 - *dx + 1;
     }

     if (clip->y1 > *dy ) {
          srect->h = MIN( (clip->y2 - clip->y1) + 1,
                          (*dy + srect->h) - clip->y1);
          srect->y+= clip->y1 - *dy;
          *dy = clip->y1;
     }
     else if (clip->y2 < *dy + srect->h - 1) {
          srect->h = clip->y2 - *dy + 1;
     }
}

void
dfb_clip_stretchblit( const DFBRegion *clip,
                      DFBRectangle *srect, DFBRectangle *drect )
{
     DFBRectangle orig_dst = *drect;

     dfb_clip_rectangle( clip, drect );

     if (drect->x != orig_dst.x)
          srect->x += (int)( (drect->x - orig_dst.x) *
                             (srect->w / (float)orig_dst.w) );

     if (drect->y != orig_dst.y)
          srect->y += (int)( (drect->y - orig_dst.y) *
                             (srect->h / (float)orig_dst.h) );

     if (drect->w != orig_dst.w)
          srect->w = (int)( srect->w * (drect->w / (float)orig_dst.w) );

     if (drect->h != orig_dst.h)
          srect->h = (int)( srect->h * (drect->h / (float)orig_dst.h) );
}

