/**************************************************************************
 *                                                                        *
 *               Copyright (C) 1989, Silicon Graphics, Inc.               *
 *                                                                        *
 *  These coded instructions, statements, and computer programs  contain  *
 *  unpublished  proprietary  information of Silicon Graphics, Inc., and  *
 *  are protected by Federal copyright law.  They  may  not be disclosed  *
 *  to  third  parties  or copied or duplicated in any form, in whole or  *
 *  in part, without the prior written consent of Silicon Graphics, Inc.  *
 *                                                                        *
 **************************************************************************/

/* interface.c */

/* Derrick Burns - 1989 */

#include <glos.h>
#include <GL/gl.h>
#include <GL/glu.h>

#ifndef NT
#include <stdlib.h>
#else
#include "winmem.h"
#include "bufpool.h"
#endif

#include "monotone.h"

static void     do_out_finish(GLUtriangulatorObj *);
static void     __gluTessEndContour(GLUtriangulatorObj *tobj);

GLUtriangulatorObj * APIENTRY gluNewTess(void)
{
    GLUtriangulatorObj *tobj;
    tobj = (GLUtriangulatorObj *) malloc(sizeof(GLUtriangulatorObj));
    tobj->init = 0;
    tobj->minit = 0;
    tobj->in_poly = 0;
    tobj->doingTriangles = 0;
    tobj->tritype = GL_TRIANGLES;
    tobj->vpool = 0;
    tobj->raypool = 0;
    tobj->begin = NULL;
    tobj->end = NULL;
    tobj->vertex = NULL;
    tobj->error = NULL;
    tobj->edgeflag = NULL;
    tobj->parray = (Vert **)NULL;
    tobj->inBegin = GL_FALSE;
    tobj->s = 0;
    tobj->t = 0;
    return tobj;
}

void APIENTRY
gluTessCallback(GLUtriangulatorObj *tobj, GLenum which, void (CALLBACK *fn)())
{
    switch(which) {
      case GLU_BEGIN:
        #ifndef NT
        tobj->begin = (void (*)(GLenum)) fn;
        #else
        tobj->begin = (GLUtessBeginProc) fn;
        #endif
        break;
      case GLU_VERTEX:
        #ifndef NT
        tobj->vertex = (void (*)(void *)) fn;
        #else
        tobj->vertex = (GLUtessVertexProc) fn;
        #endif
        break;
      case GLU_END:
        #ifndef NT
        tobj->end = (void (*)(void)) fn;
        #else
        tobj->end = (GLUtessEndProc) fn;
        #endif
        break;
      case GLU_ERROR:
        #ifndef NT
        tobj->error = (void (*)(GLenum)) fn;
        #else
        tobj->error = (GLUtessErrorProc) fn;
        #endif
        break;
      case GLU_EDGE_FLAG:
        #ifndef NT
        tobj->edgeflag = (void (*)(GLboolean)) fn;
        #else
        tobj->edgeflag = (GLUtessEdgeFlagProc) fn;
        #endif
        break;
      default:
        /* XXX */
        break;
    }
}

void APIENTRY
gluDeleteTess(GLUtriangulatorObj *t)
{
#ifdef NT
    extern void __gl_free_pool( Pool * );
#endif

    __gl_free_priorityq(t);
    if (t->raypool) __gl_free_pool(t->raypool);
    if (t->vpool) __gl_free_pool(t->vpool);
    __gl_clear_triangulate(t);
    __gl_clear_sort(t);
    free(t);
}

/*---------------------------------------------------------------------------
 * gluBeginPolygon - called before each input polygon
 *---------------------------------------------------------------------------
 */

void APIENTRY
gluBeginPolygon(GLUtriangulatorObj *tobj)
{
    if(setjmp(tobj->in_env) != 0)
        return;

    if (tobj->in_poly++) {
        __gl_in_error(tobj, 1);
    }

    /* 17 arbitrarily */
    __gl_init_priorityq(tobj, 17);
    __gl_init_verts(tobj);
    __gl_init_raylist(tobj);

    tobj->nloops =  0;
    tobj->maxarea = (float)0.0;
    tobj->head = 0;
    tobj->looptype = GLU_EXTERIOR;
}

/*---------------------------------------------------------------------------
 * gluEndPolygon - called after each input polygon
 *---------------------------------------------------------------------------
 */

void APIENTRY
gluEndPolygon(GLUtriangulatorObj *tobj)
{
#ifdef NT
    extern void __gl_setpriority_priorityq( GLUtriangulatorObj *, int, int);
#endif

    if(setjmp(tobj->in_env) != 0)
        return;

    if (--tobj->in_poly) {
        __gl_in_error(tobj, 2);
    }

    if (tobj->head) {
        __gluTessEndContour(tobj);
    }

    /* Set edge flag to -1 so that if the user wants edge flag info, we
    ** specify an edge flag for the first edge regardless (then we only
    ** report changes).
    */
    tobj->currentEdgeFlag = (GLboolean) -1;

    __gl_setpriority_priorityq(tobj, tobj->s, tobj->t);

    __gl_monotonize(tobj);
    do_out_finish(tobj);
    __gl_free_verts(tobj);
    __gl_free_priorityq(tobj);
    __gl_free_raylist(tobj);
}

static void
do_out_finish(GLUtriangulatorObj *tobj)
{
    if (tobj->doingTriangles) {
        if (tobj->end) {
            (*tobj->end)();
            tobj->inBegin = GL_FALSE;
        }
        tobj->doingTriangles = 0;
    }
}

/*---------------------------------------------------------------------------
 * gluNextContour - called before each input boundary loop
 *---------------------------------------------------------------------------
 */

void APIENTRY
gluNextContour(GLUtriangulatorObj *tobj, GLenum type)
{
    if(setjmp(tobj->in_env) != 0)
        return;

    if(!tobj->in_poly) {
        __gl_in_error(tobj, 2);
    }

    if (tobj->head) {
        __gluTessEndContour(tobj);
    }

    tobj->head = 0;
    tobj->looptype = type;
}

/*---------------------------------------------------------------------------
 * gluTessEndContour - called after each input boundary loop
 *---------------------------------------------------------------------------
 */

static void
__gluTessEndContour(GLUtriangulatorObj *tobj)
{
    double xyarea, xzarea, yzarea;
    Vert *v;

    tobj->nloops++;
    xyarea = xzarea = yzarea = 0.0;

    v = tobj->head;

    do {
        xyarea += v->v[0] * v->next->v[1] - v->v[1] * v->next->v[0];
        xzarea += v->v[0] * v->next->v[2] - v->v[2] * v->next->v[0];
        yzarea += v->v[1] * v->next->v[2] - v->v[2] * v->next->v[1];
        v = v->next;
    } while(v != tobj->head);

    if(xyarea < 0.0) {
        if(-xyarea > tobj->maxarea) {
            tobj->maxarea = -xyarea; tobj->s =  1; tobj->t = 0;
        }
    } else {
        if(xyarea > tobj->maxarea) {
            tobj->maxarea = xyarea; tobj->s =  0; tobj->t = 1;
        }
    }

    if(xzarea < 0.0) {
        if(-xzarea > tobj->maxarea) {
            tobj->maxarea = -xzarea; tobj->s =  2; tobj->t = 0;
        }
    } else {
         if(xzarea > tobj->maxarea) {
            tobj->maxarea = xzarea; tobj->s =  0; tobj->t = 2;
        }
    }

    if(yzarea < 0.0) {
        if(-yzarea > tobj->maxarea) {
            tobj->maxarea = -yzarea; tobj->s =  2; tobj->t = 1;
        }
    } else {
        if(yzarea > tobj->maxarea) {
            tobj->maxarea = yzarea; tobj->s =  1; tobj->t = 2;
        }
    }

    __gl_unclassify_all(tobj->head);
}

/*---------------------------------------------------------------------------
 * gluTessVertex - called for each input vertex
 *---------------------------------------------------------------------------
 */

void APIENTRY
gluTessVertex(GLUtriangulatorObj *tobj, GLdouble v[3], void *data)
{
    Vert *vert;

    if(setjmp(tobj->in_env) != 0)
        return;

    if(!tobj->in_poly) {
        __gl_in_error(tobj, 2);
    }

    vert = __gl_new_vert(tobj);
    vert->myid = vert;

    vert->v[0] = v[0];
    vert->v[1] = v[1];
    vert->v[2] = v[2];
    vert->ray = 0;
    vert->data = data;
#ifdef ADDED
    vert->added = 0;
#endif
    if(tobj->head == 0) {
        tobj->head = vert->prev = vert->next = vert;
    } else {
        vert->prev = tobj->head->prev;
        vert->next = tobj->head;
        vert->prev->next = vert;
        vert->next->prev = vert;
    }
    __gl_add_priorityq(tobj, vert);
}

/*----------------------------------------------------------------------------
 * in_error - data input error, free all storage
 *----------------------------------------------------------------------------
 */

void
__gl_in_error(GLUtriangulatorObj *tobj, GLenum which)
{
    __gl_clear_sort(tobj);
    __gl_clear_triangulate(tobj);
    __gl_free_raylist(tobj);
    __gl_free_verts(tobj);
    __gl_free_priorityq(tobj);
    __gl_cleanup(tobj);
    tobj->in_poly = 0;
    if (tobj->error) {
        (*tobj->error)(which + (GLU_TESS_ERROR1 - 1));
    }
    longjmp(tobj->in_env, (int) which);
}