/******************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  Tesselated Polygon Object
 * Author:   David Register
 *
 ***************************************************************************
 *   Copyright (C) 2010 by David S. Register   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.             *
 ***************************************************************************
 *
 *
 */
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifndef  WX_PRECOMP
#include "wx/wx.h"
#endif //precompiled headers

#include "wx/tokenzr.h"
#include <wx/mstream.h>

#include "config.h"

#include "gdal/ogr_geometry.h"

#include "cutil.h"

#include "vector2D.h"

#include "s52s57.h"

#include "mygeom.h"
#include "georef.h"

#include "dychart.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "tesselator.h"
#include "Striper.h"

#ifdef __WXMSW__
#include <windows.h>
#endif

static const double   CM93_semimajor_axis_meters = 6378388.0;            // CM93 semimajor axis



//      Module Internal Prototypes


#if defined( __UNIX__ ) && !defined(__WXOSX__)  // high resolution stopwatch for profiling
class OCPNStopWatchTess
{
public:
    OCPNStopWatchTess() { Reset(); }
    void Reset() { clock_gettime(CLOCK_REALTIME, &tp); }

    double GetTime() {
        timespec tp_end;
        clock_gettime(CLOCK_REALTIME, &tp_end);
        return (tp_end.tv_sec - tp.tv_sec) * 1.e3 + (tp_end.tv_nsec - tp.tv_nsec) / 1.e6;
    }

private:
    timespec tp;
};
#endif


//-----------------------------------------------------------
//
//  Static memory allocators for libtess2
//
//-----------------------------------------------------------
//#define USE_POOL 1

void* stdAlloc(void* userData, unsigned int size)
{
    int* allocated = ( int*)userData;
    TESS_NOTUSED(userData);
    *allocated += (int)size;
    return malloc(size);
}

void stdFree(void* userData, void* ptr)
{
    TESS_NOTUSED(userData);
    free(ptr);
}

struct MemPool
{
    unsigned char* buf;
    unsigned int cap;
    unsigned int size;
};

void* poolAlloc( void* userData, unsigned int size )
{
    struct MemPool* pool = (struct MemPool*)userData;
    size = (size+0x7) & ~0x7;
    if (pool->size + size < pool->cap)
    {
        unsigned char* ptr = pool->buf + pool->size;
        pool->size += size;
        return ptr;
    }
    printf("out of mem: %d < %d!\n", pool->size + size, pool->cap);
    return 0;
}

void poolFree( void* userData, void* ptr )
{
    // empty
    TESS_NOTUSED(userData);
    TESS_NOTUSED(ptr);
}








/**
 * Returns TRUE if the ring has clockwise winding.
 *
 * @return TRUE if clockwise otherwise FALSE.
 */

bool isRingClockwise(wxPoint2DDouble *pp, int nPointCount)

{
    double dfSum = 0.0;
    
    for( int iVert = 0; iVert < nPointCount-1; iVert++ )
    {
        dfSum += pp[iVert].m_x * pp[iVert+1].m_y
        - pp[iVert].m_y * pp[iVert+1].m_x;
    }
    
    dfSum += pp[nPointCount-1].m_x * pp[0].m_y
    - pp[nPointCount-1].m_y * pp[0].m_x;
    
    return dfSum < 0.0;
}

//------------------------------------------------------------------------------
//          Extended_Geometry Implementation
//------------------------------------------------------------------------------
Extended_Geometry::Extended_Geometry()
{
      vertex_array = NULL;
      contour_array = NULL;
}

Extended_Geometry::~Extended_Geometry()
{
      free(vertex_array);
      free(contour_array);
}


//------------------------------------------------------------------------------
//          PolyTessGeo Implementation
//------------------------------------------------------------------------------
PolyTessGeo::PolyTessGeo()
{
    m_pxgeom = NULL;
    m_printStats = false;
    m_bstripify = false;
    m_LOD_meters = 0;
}

//      Build PolyTessGeo Object from Extended_Geometry
PolyTessGeo::PolyTessGeo(Extended_Geometry *pxGeom)
{
      m_ppg_head = NULL;
      m_bOK = false;

      m_pxgeom = pxGeom;
      m_printStats = false;
      m_bstripify = false;
      m_LOD_meters= 0;
}

//      Build PolyTessGeo Object from OGR Polygon
PolyTessGeo::PolyTessGeo(OGRPolygon *poly, bool bSENC_SM, double ref_lat, double ref_lon,
                         double LOD_meters)
{
    m_printStats = false;
    
    ErrorCode = 0;
    m_ppg_head = NULL;
    m_pxgeom = NULL;
    m_tess_orient = TESS_HORZ;
    
    m_ref_lat = ref_lat;
    m_ref_lon = ref_lon;
    m_LOD_meters = LOD_meters;
    m_b_senc_sm = bSENC_SM;
    m_bmerc_transform = false;
    
    //    PolyGeo BBox as lat/lon
    OGREnvelope Envelope;
    poly->getEnvelope(&Envelope);
    xmin = Envelope.MinX;
    ymin = Envelope.MinY;
    xmax = Envelope.MaxX;
    ymax = Envelope.MaxY;

    m_feature_ref_lat = ymin + (ymax - ymin)/2;
    m_feature_ref_lon = xmin + (xmax - xmin)/2;
    
    toSM(m_feature_ref_lat, m_feature_ref_lon, ref_lat, ref_lon, &m_feature_easting, &m_feature_northing);

    //  Build the array of contour point counts
    //      Get total number of contours
    m_ncnt = poly->getNumInteriorRings() + 1;

      // build the contour point countarray
    m_cntr = (int *)malloc(m_ncnt * sizeof(int));

    m_cntr[0] = poly->getExteriorRing()->getNumPoints();

    for(int i = 1; i < m_ncnt ; i++){
          m_cntr[i] = poly->getInteriorRing( i-1 )->getNumPoints();
    }

    //  Build the point array index table
    
    OGRPoint p;
    m_vertexPtrArray = (double **)malloc(m_ncnt * sizeof(double *));
    
    m_vertexPtrArray[0] = (double *)malloc(m_cntr[0] * sizeof(double) * 2);
    double *pp = m_vertexPtrArray[0];
    for(int i = 0 ; i < m_cntr[0] ; i++){
        poly->getExteriorRing()->getPoint(i, &p);
        
        //  Calculate SM from feature reference point
        double easting, northing;
        toSM(p.getY(), p.getX(), m_feature_ref_lat, m_feature_ref_lon, &easting, &northing);
        *pp++ = easting;              // x
        *pp++ = northing;             // y
    }

    for(int i = 1; i < m_ncnt ; i++){
        m_vertexPtrArray[i] = (double *)malloc(m_cntr[i] * sizeof(double) * 2);
        double *pp = m_vertexPtrArray[i];
        for(int j = 0 ; j < m_cntr[i] ; j++){
            poly->getInteriorRing(i-1)->getPoint(j, &p);

            double easting, northing;
            toSM(p.getY(), p.getX(), m_feature_ref_lat, m_feature_ref_lon, &easting, &northing);
            *pp++ = easting;              // x
            *pp++ = northing;             // y
        }
    }
       

  
    mx_rate = 1.0;
    mx_offset = 0.0;
    my_rate = 1.0;
    my_offset = 0.0;

    m_bstripify = true;

    earthAxis = CM93_semimajor_axis_meters * mercator_k0;

    m_bcm93 = false;
    
    BuildTessGLU();
    
    // Free the working memory
    for(int i = 0; i < m_ncnt ; i++)
        free (m_vertexPtrArray[i]);
    
    free( m_vertexPtrArray );
    m_vertexPtrArray = nullptr;
    
}


void PolyTessGeo::SetExtents(double x_left, double y_bot, double x_right, double y_top)
{
    xmin = x_left;
    ymin = y_bot;
    xmax = x_right;
    ymax = y_top;
}



PolyTessGeo::~PolyTessGeo()
{
    delete  m_ppg_head;
    delete  m_pxgeom;
}

int PolyTessGeo::BuildDeferredTess(void)
{
    if(m_pxgeom){
        
      // For cm93
      m_b_senc_sm =  false;
      m_bmerc_transform = true;
      m_tess_orient = TESS_HORZ;                    // prefer horizontal tristrips

      //      Get total number of contours
      m_ncnt = m_pxgeom->n_contours;

      // build the contour point countarray
      m_cntr = (int *)malloc(m_ncnt * sizeof(int));

      for(int i = 0; i < m_ncnt ; i++){
          m_cntr[i] = m_pxgeom->contour_array[i];
      }
      

      //  Build the point array index table
    
      m_vertexPtrArray = (double **)malloc(m_ncnt * sizeof(double *));
    
      double *vertex_pointer = (double *)m_pxgeom->vertex_array;
      vertex_pointer += 2;      // skip 0
      
      for(int k = 0 ; k < m_ncnt ; k++){
            m_vertexPtrArray[k] = vertex_pointer;
            vertex_pointer += 2 * m_cntr[k];
      }
  
      mx_rate = m_pxgeom->x_rate;
      mx_offset = m_pxgeom->x_offset;
      my_rate = m_pxgeom->y_rate;
      my_offset = m_pxgeom->y_offset;

      m_feature_easting = 0;
      m_feature_northing = 0;
      
      m_ref_lat = m_pxgeom->ref_lat;
      m_ref_lon = m_pxgeom->ref_lon;
      
      //  Note Bene...  cm93 assumes no flattening constant
      earthAxis = CM93_semimajor_axis_meters;

      
      m_bcm93 = true;

      // For cm93, we prefer TessGLU, since it produces more TriangleStrips, so renders faster with less storage.
      int rv = BuildTessGLU();
      
      // All done with this geometry
      delete m_pxgeom;
      m_pxgeom = NULL;
      
      return rv;

    }
    else
        return 0;
}



void  beginCallback(GLenum which, void *polyData);
void  errorCallback(GLenum errorCode, void *polyData);
void  endCallback(void *polyData);
void  vertexCallback(GLvoid *vertex, void *polyData);
void  combineCallback(GLdouble coords[3],
                     GLdouble *vertex_data[4],
                     GLfloat weight[4], GLdouble **dataOut, void *polyData );


//      Build PolyTessGeo Object from OGR Polygon
//      Using OpenGL/GLU tesselator
int PolyTessGeo::BuildTessGLU()
{
    
    unsigned int iir, ip;
    GLdouble *geoPt;


#if 0    
    //  Make a quick sanity check of the polygon coherence
    bool b_ok = true;
    OGRLineString *tls = poly->getExteriorRing();
    if(!tls) {
        b_ok = false;
    }
    else {
        int tnpta  = poly->getExteriorRing()->getNumPoints();
        if(tnpta < 3 )
            b_ok = false;
    }

    
    for( iir=0 ; iir < poly->getNumInteriorRings() ; iir++)
    {
        int tnptr = poly->getInteriorRing(iir)->getNumPoints();
        if( tnptr < 3 )
            b_ok = false;
    }
    
    if( !b_ok )
        return ERROR_BAD_OGRPOLY;
    
#endif
    


    //  Allocate a work buffer, which will be grown as needed
#define NINIT_BUFFER_LEN 10000
    m_pwork_buf = (GLdouble *)malloc(NINIT_BUFFER_LEN * 2 * sizeof(GLdouble));
    m_buf_len = NINIT_BUFFER_LEN * 2;
    m_buf_idx = 0;

      //    Create an array to hold pointers to allocated vertices created by "combine" callback,
      //    so that they may be deleted after tesselation.
    m_pCombineVertexArray = new wxArrayPtrVoid;

    //  Create tesselator
    GLUtessobj = gluNewTess();

    //  Register the callbacks
    gluTessCallback(GLUtessobj, GLU_TESS_BEGIN_DATA,   (GLvoid (*) ())&beginCallback);
    gluTessCallback(GLUtessobj, GLU_TESS_BEGIN_DATA,   (GLvoid (*) ())&beginCallback);
    gluTessCallback(GLUtessobj, GLU_TESS_VERTEX_DATA,  (GLvoid (*) ())&vertexCallback);
    gluTessCallback(GLUtessobj, GLU_TESS_END_DATA,     (GLvoid (*) ())&endCallback);
    gluTessCallback(GLUtessobj, GLU_TESS_COMBINE_DATA, (GLvoid (*) ())&combineCallback);
    gluTessCallback(GLUtessobj, GLU_TESS_ERROR_DATA,   (GLvoid (*) ())&errorCallback);

    gluTessProperty(GLUtessobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
    gluTessNormal(GLUtessobj, 0, 0, 1);

//      Get total number of points(vertices)  to build a buffer
    int npta  = 0;

    for( int i=0 ; i < m_ncnt ; i++)
        npta += m_cntr[i];


    geoPt = (GLdouble *)malloc((npta + 6) * 3 * sizeof(GLdouble));     // vertex array



   //  Grow the work buffer if necessary

    if((npta * 4) > m_buf_len)
    {
        m_pwork_buf = (GLdouble *)realloc(m_pwork_buf, npta * 4 * sizeof(GLdouble));
        m_buf_len = npta * 4;
    }


//  Define the polygon
    gluTessBeginPolygon(GLUtessobj, this);


    
    //  Check and account for winding direction of ring


      double x0, y0, x, y;
//      OGRPoint p;

      wxPoint2DDouble *pp = (wxPoint2DDouble *)m_vertexPtrArray[0];
      bool cw = !isRingClockwise(pp, m_cntr[0]);
      
      //pp++;       // skip 0?

      if(cw)
      {
            x0 = pp->m_x;
            y0 = pp->m_y;
      }
      else
      {
            x0 = pp[m_cntr[0]-1].m_x;
            y0 = pp[m_cntr[0]-1].m_y;
      }



      unsigned int ptValid = m_cntr[0];
      
//  Transcribe points to vertex array, in proper order with no duplicates
//   also, accounting for tess_orient
      GLdouble *ppt = geoPt;
      
      for(ip = 0 ; ip < (unsigned int)m_cntr[0] ; ip++)
      {
            int pidx;
            if(cw)
                  pidx = m_cntr[0] - ip - 1;

            else
                  pidx = ip;

            x = pp[pidx].m_x;
            y = pp[pidx].m_y;

            if((fabs(x-x0) > EQUAL_EPS) || (fabs(y-y0) > EQUAL_EPS))
            {
                  if(m_tess_orient == TESS_VERT)
                  {
                        *ppt++ = x;
                        *ppt++ = y;
                        *ppt++ = 0;
                  }
                  else
                  {
                        *ppt++ = y;
                        *ppt++ = x;
                        *ppt++ = 0;
                  }
            }
            else
                  ptValid--;

            x0 = x;
            y0 = y;
      }

      //  Apply LOD reduction
 
    if(ptValid > 20 && (m_LOD_meters > .01)) {
        std::vector<bool> bool_keep(ptValid, false);

        // Keep a few key points
        bool_keep[0] = true;
        bool_keep[1] = true;
        bool_keep[ptValid-1] = true;
        bool_keep[ptValid-2] = true;
        
        DouglasPeuckerDI(geoPt, 1, ptValid-2, m_LOD_meters, bool_keep);
            
        // Create a new buffer
        double *LOD_result = (double *)malloc((m_cntr[0]) * 3 * sizeof(double));     
        double *plod = LOD_result;
        int kept_LOD =0;
        
        for(unsigned int i=0 ; i < ptValid ; i++){
            if(bool_keep[i]){
                double x = geoPt[i*3];
                double y = geoPt[(i*3) + 1];
                *plod++ = x;
                *plod++ = y;
                *plod++ = 0;
                kept_LOD++;
            }
        }

        // Copy the lod points back into the vertex buffer
        memcpy(geoPt, LOD_result, kept_LOD * 3 * sizeof(double));
        
        free(LOD_result);
        ptValid = kept_LOD;
    }

    //  Declare the gluContour and copy the points
    gluTessBeginContour(GLUtessobj);
    
    double *DPrun = geoPt;
    for(ip = 0 ; ip < ptValid ; ip++) {
        gluTessVertex( GLUtessobj, DPrun, DPrun ) ;
        DPrun += 3;
    }
  
    gluTessEndContour(GLUtessobj);

    //  Now the interior contours
#if 1    
    int gpIndex = m_cntr[0];
    
    for(iir=0; iir < (unsigned int)m_ncnt-1; iir++){
        
            wxPoint2DDouble *pp = (wxPoint2DDouble *)m_vertexPtrArray[iir + 1];

            int npti  = m_cntr[iir+1];
            ptValid = npti;
            
            double *ppt = &geoPt[gpIndex * 3]; // next available location in geoPT
            double *DPStart = ppt;
            
      //  Check and account for winding direction of ring
            bool cw = isRingClockwise(pp, m_cntr[iir+1]);

            if(cw)
            {
                  x0 = pp[0].m_x;
                  y0 = pp[0].m_y;
            }
            else
            {
                  x0 = pp[npti-1].m_x;
                  y0 = pp[npti-1].m_y;
            }

//  Transcribe points to vertex array, in proper order with no duplicates
//   also, accounting for tess_orient
            for(int ip = 0 ; ip < npti ; ip++)
            {
                  int pidx;
                  if(cw)                               // interior contours must be cw
                        pidx = npti - ip - 1;
                  else
                        pidx = ip;


                  x = pp[pidx].m_x;
                  y = pp[pidx].m_y;

                  if((fabs(x-x0) > EQUAL_EPS) || (fabs(y-y0) > EQUAL_EPS))
                  {
                        if(m_tess_orient == TESS_VERT)
                        {
                              *ppt++ = x;
                              *ppt++ = y;
                              *ppt++ = 0;
                        }
                        else
                        {
                              *ppt++ = y;
                              *ppt++ = x;
                              *ppt++ = 0;
                        }
                  }
                  else
                        ptValid--;

                  x0 = x;
                  y0 = y;

            }
            
                       //  Declare the gluContour and reference the points
            gluTessBeginContour(GLUtessobj);
    
            double *DPruni = DPStart;
            for(ip = 0 ; ip < ptValid ; ip++)
            {
                gluTessVertex( GLUtessobj, DPruni, DPruni ) ;
                DPruni += 3;
            }
        
            gluTessEndContour(GLUtessobj);

            gpIndex +=  m_cntr[iir+1];
      }
#endif
        
        
        
        

    m_pTPG_Last = NULL;
    m_pTPG_Head = NULL;
    m_nvmax = 0;

    //      Ready to kick off the tesselator
    gluTessEndPolygon(GLUtessobj);          // here it goes

    m_nvertex_max = m_nvmax;               // record largest vertex count, updates in callback


    //  Tesselation all done, so...

    //  Create the data structures

    m_ppg_head = new PolyTriGroup;
    m_ppg_head->m_bSMSENC = m_b_senc_sm;

    m_ppg_head->nContours = m_ncnt;

    m_ppg_head->pn_vertex = m_cntr;             // pointer to array of poly vertex counts
    m_ppg_head->data_type = DATA_TYPE_DOUBLE;
    

//  Transcribe the raw geometry buffer
//  Converting to float as we go, and
//  allowing for tess_orient
//  Also, convert to SM if requested

// Recalculate the size of the geometry buffer
    unsigned int nptfinal = m_cntr[0] + 2;
//     for(int i=0 ; i < nint ; i++)
//         nptfinal += cntr[i+1] + 2;
    
    //  No longer need the full geometry in the SENC,
    nptfinal = 1;
    
    m_nwkb = (nptfinal + 1) * 2 * sizeof(float);
    m_ppg_head->pgroup_geom = (float *)calloc(sizeof(float), (nptfinal + 1) * 2);
    float *vro = m_ppg_head->pgroup_geom;
    ppt = geoPt;
    float tx,ty;

    for(ip = 0 ; ip < nptfinal ; ip++)
    {
        if(TESS_HORZ == m_tess_orient)
        {
            ty = *ppt++;
            tx = *ppt++;
        }
        else
        {
            tx = *ppt++;
            ty = *ppt++;
        }

        if(m_b_senc_sm)
        {
            //  Calculate SM from chart common reference point
            double easting, northing;
            toSM(ty, tx, m_ref_lat, m_ref_lon, &easting, &northing);
            *vro++ = easting;              // x
            *vro++ = northing;             // y
        }
        else
        {
            *vro++ = tx;                  // lon
            *vro++ = ty;                  // lat
        }

        ppt++;                      // skip z
    }

    m_ppg_head->tri_prim_head = m_pTPG_Head;         // head of linked list of TriPrims


    //  Convert the Triangle vertex arrays into a single memory allocation of floats
    //  to reduce SENC size and enable efficient access later
    
    //  First calculate the total byte size
    int total_byte_size = 2 * sizeof(float);
    TriPrim *p_tp = m_ppg_head->tri_prim_head;
    while( p_tp ) {
        total_byte_size += p_tp->nVert * 2 * sizeof(float);
        p_tp = p_tp->p_next; // pick up the next in chain
    }
    
    float *vbuf = (float *)malloc(total_byte_size);
    p_tp = m_ppg_head->tri_prim_head;
    float *p_run = vbuf;
    while( p_tp ) {
        float *pfbuf = p_run;
        GLdouble *pdouble_buf = (GLdouble *)p_tp->p_vertex;
        
        for( int i=0 ; i < p_tp->nVert * 2 ; ++i){
            float x = (float)( *((GLdouble *)pdouble_buf) );
            pdouble_buf++;
            *p_run++ = x;
        }
        
        free(p_tp->p_vertex);
        p_tp->p_vertex = (double *)pfbuf;
        p_tp = p_tp->p_next; // pick up the next in chain
    }
    m_ppg_head->bsingle_alloc = true;
    m_ppg_head->single_buffer = (unsigned char *)vbuf;
    m_ppg_head->single_buffer_size = total_byte_size;
    m_ppg_head->data_type = DATA_TYPE_FLOAT;
    
    
    
    
    
    
    gluDeleteTess(GLUtessobj);

    free( m_pwork_buf );
    m_pwork_buf = NULL;

    free (geoPt);

    //      Free up any "Combine" vertices created
    for(unsigned int i = 0; i < m_pCombineVertexArray->GetCount() ; i++)
          free (m_pCombineVertexArray->Item(i));
    delete m_pCombineVertexArray;

    m_bOK = true;

    
    return 0;
}



int PolyTessGeo::BuildTess(void)
{
    //  Setup the tesselator
    
    TESSalloc ma;
    TESStesselator* tess = 0;
    const int nvp = 3;
//    unsigned char* vflags = 0;
#ifdef USE_POOL
    struct MemPool pool;
    unsigned char mem[4024*1024];
//    int nvflags = 0;
#else
    int allocated = 0;
#endif

#ifdef USE_POOL

    pool.size = 0;
    pool.cap = sizeof(mem);
    pool.buf = mem;
    memset(&ma, 0, sizeof(ma));
    ma.memalloc = poolAlloc;
    ma.memfree = poolFree;
    ma.userData = (void*)&pool;
    ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices.

#else
 
    memset(&ma, 0, sizeof(ma));
    ma.memalloc = stdAlloc;
    ma.memfree = stdFree;
    ma.userData = (void*)&allocated;
    ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices.
#endif

    tess = tessNewTess(&ma);
    if (!tess)
        return -1;

    //tessSetOption(tess, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1);

    
    //  Create the contour vertex arrays
    
      int iir, ip;

//      Get max number number of points(vertices) in any contour
      int npta  = m_cntr[0];
      npta += 2;                            // fluff

      for( iir=0 ; iir < m_ncnt-1 ; iir++){
            int nptr = m_cntr[iir+1];
            npta = wxMax(npta, nptr); 
      }


      TESSreal *geoPt = (TESSreal *)malloc((npta) * 2 * sizeof(TESSreal));     // tess input vertex array
      TESSreal *ppt = geoPt;


//      Create input structures

//    Exterior Ring
      int npte = m_cntr[0];
      unsigned int ptValid = npte;

//  Check and account for winding direction of ring


      double x0, y0, x, y;
      OGRPoint p;

      wxPoint2DDouble *pp = (wxPoint2DDouble *)m_vertexPtrArray[0];
      bool cw = isRingClockwise(pp, m_cntr[0]);
      
      //pp++;       // skip 0?

      if(cw)
      {
            x0 = pp->m_x;
            y0 = pp->m_y;
      }
      else
      {
            x0 = pp[npte-1].m_x;
            y0 = pp[npte-1].m_y;
      }



//  Transcribe points to vertex array, in proper order with no duplicates
//   also, accounting for tess_orient
      for(ip = 0 ; ip < npte ; ip++)
      {
            int pidx;
            if(cw)
                  pidx = npte - ip - 1;

            else
                  pidx = ip;

            x = pp[pidx].m_x;
            y = pp[pidx].m_y;

            if((fabs(x-x0) > EQUAL_EPS) || (fabs(y-y0) > EQUAL_EPS))
            {
                  if(m_tess_orient == TESS_VERT)
                  {
                        *ppt++ = x;
                        *ppt++ = y;
                  }
                  else
                  {
                        *ppt++ = y;
                        *ppt++ = x;
                  }
            }
            else
                  ptValid--;

            x0 = x;
            y0 = y;
      }

      
      //  Apply LOD reduction
    int beforeLOD = ptValid;
    int afterLOD = beforeLOD;
   
    std::vector<bool> bool_keep;
    if(ptValid > 5 && (m_LOD_meters > .01)){
        
        for(unsigned int i = 0 ; i < ptValid ; i++)
            bool_keep.push_back(false);

        // Keep a few key points
        bool_keep[0] = true;
        bool_keep[1] = true;
        bool_keep[ptValid-1] = true;
        bool_keep[ptValid-2] = true;
        
        DouglasPeuckerFI(geoPt, 1, ptValid-2, m_LOD_meters, bool_keep);
            
        // Create a new buffer
        float *LOD_result = (float *)malloc((npte) * 2 * sizeof(float));     
        float *plod = LOD_result;
        int kept_LOD =0;
        
        for(unsigned int i=0 ; i < ptValid ; i++){
            if(bool_keep[i]){
                float x = geoPt[i*2];
                float y = geoPt[(i*2) + 1];
                *plod++ = x;
                *plod++ = y;
                kept_LOD++;
            }
        }

        beforeLOD = ptValid;
        afterLOD = kept_LOD;
    
        tessAddContour(tess, 2, LOD_result, sizeof(float)*2, kept_LOD);
        
        free(LOD_result);
    }
    else {
        tessAddContour(tess, 2, geoPt, sizeof(float)*2, ptValid);
    }
    
    

#if 1
//  Now the interior contours
      for(iir=0; iir < m_ncnt-1; iir++)
      {
            ppt = geoPt;
            wxPoint2DDouble *pp = (wxPoint2DDouble *)m_vertexPtrArray[iir + 1];

            int npti  = m_cntr[iir+1];
            ptValid = npti;
            
      //  Check and account for winding direction of ring
            bool cw = isRingClockwise(pp, m_cntr[iir+1]);

            if(!cw)
            {
                  x0 = pp[0].m_x;
                  y0 = pp[0].m_y;
            }
            else
            {
                  x0 = pp[npti-1].m_x;
                  y0 = pp[npti-1].m_y;
            }

//  Transcribe points to vertex array, in proper order with no duplicates
//   also, accounting for tess_orient
            for(int ip = 0 ; ip < npti ; ip++)
            {
                  OGRPoint p;
                  int pidx;
                  if(!cw)                               // interior contours must be cw
                        pidx = npti - ip - 1;
                  else
                        pidx = ip;


                  x = pp[pidx].m_x;
                  y = pp[pidx].m_y;

                  if((fabs(x-x0) > EQUAL_EPS) || (fabs(y-y0) > EQUAL_EPS))
                  {
                        if(m_tess_orient == TESS_VERT)
                        {
                              *ppt++ = x;
                              *ppt++ = y;
                        }
                        else
                        {
                              *ppt++ = y;
                              *ppt++ = x;
                        }
                  }
                  else
                        ptValid--;

                  x0 = x;
                  y0 = y;

            }
            
            tessAddContour(tess, 2, geoPt, sizeof(float)*2, ptValid);

      }
#endif


      //      Ready to kick off the tesselator

      TriPrim *pTPG_Last = NULL;
      TriPrim *pTPG_Head = NULL;

      //s_nvmax = 0;

      //OCPNStopWatchTess tt0;
      
      if (!tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, nvp, 2, 0))
        return -1;
      double tessTime = 0; //tt0.GetTime();


    //  Tesselation all done, so...
    //  Create the data structures
    
    //  Make a linked list of TriPrims from tess output arrays

    const TESSreal* verts = tessGetVertices(tess);
    const int* vinds = tessGetVertexIndices(tess);
    const int* elems = tessGetElements(tess);
    const int nverts = tessGetVertexCount(tess);
    int nelems = tessGetElementCount(tess);

      
    bool skip = false;
    //skip = true;
    
    double stripTime = 0;
    //tt0.Reset();

    int bytes_needed_vbo = 0;
    float *vbo = 0;
    
    if(m_bstripify && nelems && !skip){
        
         
        STRIPERCREATE sc;
        sc.DFaces                       = (udword *)elems;
        sc.NbFaces                      = nelems;
        sc.AskForWords          = false;
        sc.ConnectAllStrips     = false;
        sc.OneSided             = false;
        sc.SGIAlgorithm         = false;

       
        Striper Strip;
        Strip.Init(sc);
        
        STRIPERRESULT sr;
        Strip.Compute(sr);

        stripTime = 0; //tt0.GetTime();

/*        
        fprintf(stdout, "Number of strips: %d\n", sr.NbStrips);
        fprintf(stdout, "Number of points: %d\n", nelems);
        uword* Refs = (uword*)sr.StripRuns;
        for(udword i=0;i<sr.NbStrips;i++)
        {
                fprintf(stdout, "Strip %d:   ", i);
                udword NbRefs = sr.StripLengths[i];
                for(udword j=0;j<NbRefs;j++)
                {
                        fprintf(stdout, "%d ", *Refs++);
                }
                fprintf(stdout, "\n");
        }
*/
        // calculate and allocate the final (float) VBO-like buffer for this entire feature
        
        int *Refs = (int *)sr.StripRuns;
        for(unsigned int i=0;i<sr.NbStrips;i++){
            unsigned int NbRefs = sr.StripLengths[i];         //  vertices per strip
            bytes_needed_vbo += NbRefs * 2 * sizeof(float);
        }
        
        vbo = (float *)malloc(bytes_needed_vbo);
        float *vbo_run = vbo;
        
        for(unsigned int i=0;i<sr.NbStrips;i++){
            unsigned int NbRefs = sr.StripLengths[i];         //  vertices per strip
 
            if(NbRefs >= 3){                              // this is a valid primitive

                TriPrim *pTPG = new TriPrim;
                if(NULL == pTPG_Last){
                    pTPG_Head = pTPG;
                    pTPG_Last = pTPG;
                }
                else{
                    pTPG_Last->p_next = pTPG;
                    pTPG_Last = pTPG;
                }

                pTPG->p_next = NULL;
                pTPG->nVert = NbRefs;
            
//                pTPG->p_vertex = (double *)malloc(NbRefs * 2 * sizeof(double));
//                GLdouble *pdd = (GLdouble*)pTPG->p_vertex;

                pTPG->p_vertex = (double *)vbo_run;

            //  Preset LLBox bounding box limits
                double sxmax = -1e8;
                double sxmin = 1e8;
                double symax = -1e8;
                double symin = 1e8;

                if(NbRefs >3)
                    pTPG->type = GL_TRIANGLE_STRIP;
                else
                    pTPG->type = GL_TRIANGLES;

                // Add the first two points
                int vindex[3];
                vindex[0] = *Refs++;
                vindex[1] = *Refs++;
                unsigned int np = 2;         // for the first triangle
                    
                for(unsigned int i = 2; i < NbRefs ; i++){    
                    vindex[np] = *Refs++;
                    np++;
                    
                    for (unsigned int j = 0; j < np; ++j){
                        double yd = verts[vindex[j]*2];
                        double xd = verts[vindex[j]*2+1];
                    
                    // Calculate LLBox bounding box for each Tri-prim
                        if(m_bmerc_transform){
                            double valx = ( xd * mx_rate ) + mx_offset;
                            double valy = ( yd * my_rate ) + my_offset;
            
                                //    quickly convert to lat/lon
                            double lat = ( 2.0 * atan ( exp ( valy/CM93_semimajor_axis_meters ) ) - PI/2. ) / DEGREE;
                            double lon= ( valx / ( DEGREE * CM93_semimajor_axis_meters ) );


                            sxmax = wxMax(lon, sxmax);
                            sxmin = wxMin(lon, sxmin);
                            symax = wxMax(lat, symax);
                            symin = wxMin(lat, symin);
                        }
                        else{
                            sxmax = wxMax(xd, sxmax);
                            sxmin = wxMin(xd, sxmin);
                            symax = wxMax(yd, symax);
                            symin = wxMin(yd, symin);
                        }
                        
                    //  Append this point to TriPrim vbo

                        *vbo_run++ = (xd) + m_feature_easting;    // adjust to chart ref coordinates
                        *vbo_run++ = (yd) + m_feature_northing;
                    }               // For

                    // Compute the final LLbbox for this TriPrim chain
                    double minlat, minlon, maxlat, maxlon;
                    fromSM(sxmin, symin, m_feature_ref_lat, m_feature_ref_lon, &minlat, &minlon);
                    fromSM(sxmax, symax, m_feature_ref_lat, m_feature_ref_lon, &maxlat, &maxlon);

                    pTPG->tri_box.Set(minlat, minlon, maxlat, maxlon);

                    // set for next single point
                    np = 0;

                }
            }
            else
                Refs += sr.StripLengths[i];
           
        }                       // for strips
    }
    else{       // not stripified
        
        m_nvertex_max = nverts;               // record largest vertex count
        
        bytes_needed_vbo = nelems * nvp * 2 * sizeof(float);
        vbo = (float *)malloc(bytes_needed_vbo);
        float *vbo_run = vbo;

        for (int i = 0; i < nelems; ++i){
            const int* p = &elems[i*nvp];

            TriPrim *pTPG = new TriPrim;
            if(NULL == pTPG_Last){
                    pTPG_Head = pTPG;
                    pTPG_Last = pTPG;
            }
            else{
                    pTPG_Last->p_next = pTPG;
                    pTPG_Last = pTPG;
            }

            pTPG->p_next = NULL;
            pTPG->type = GL_TRIANGLES;
            pTPG->nVert = nvp;
            
//            pTPG->p_vertex = (double *)malloc(nvp * 2 * sizeof(double));
//            GLdouble *pdd = (GLdouble*)pTPG->p_vertex;

            pTPG->p_vertex = (double *)vbo_run;

            //  Preset LLBox bounding box limits
            double sxmax = -1e8;
            double sxmin = 1e8;
            double symax = -1e8;
            double symin = 1e8;

            for (size_t j = 0; j < nvp && p[j] != TESS_UNDEF; ++j){
                double yd = verts[p[j]*2];
                double xd = verts[p[j]*2+1];
                
                // Calculate LLBox bounding box for each Tri-prim
                if(m_bmerc_transform){
                    double valx = ( xd * mx_rate ) + mx_offset;
                    double valy = ( yd * my_rate ) + my_offset;
    
                        //    quickly convert to lat/lon
                    double lat = ( 2.0 * atan ( exp ( valy/CM93_semimajor_axis_meters ) ) - PI/2. ) / DEGREE;
                    double lon= ( valx / ( DEGREE * CM93_semimajor_axis_meters ) );


                    sxmax = wxMax(lon, sxmax);
                    sxmin = wxMin(lon, sxmin);
                    symax = wxMax(lat, symax);
                    symin = wxMin(lat, symin);
                }
                else{
                    sxmax = wxMax(xd, sxmax);
                    sxmin = wxMin(xd, sxmin);
                    symax = wxMax(yd, symax);
                    symin = wxMin(yd, symin);
                }
                
                //  Append this point to TriPrim, converting to SM if called for

                if(m_b_senc_sm){
                    double easting, northing;
                    toSM(yd, xd, m_ref_lat, m_ref_lon, &easting, &northing);
                    *vbo_run++ = easting;
                    *vbo_run++ = northing;
                }
                else{
                    *vbo_run++ = xd;
                    *vbo_run++ = yd;
                }
            }
        
        pTPG->tri_box.Set(symin, sxmin, symax, sxmax);
        }
    }           // stripify

    if(m_printStats){
        int nTri = 0;
        int nStrip = 0;
        
        TriPrim *p_tp = pTPG_Head;
        while( p_tp ) {
            if(p_tp->type == GL_TRIANGLES)
                nTri++;
            if(p_tp->type == GL_TRIANGLE_STRIP)
                nStrip++;
            
          p_tp = p_tp->p_next; // pick up the next in chain
        }
        
        if((nTri + nStrip) > 10000){
            printf("LOD:  %d/%d\n", afterLOD, beforeLOD);

            printf("Tess time(ms): %f\n", tessTime);
            printf("Strip time(ms): %f\n", stripTime);

            printf("Primitives:   Tri: %5d  Strip: %5d  Total: %5d\n", nTri, nStrip, nTri + nStrip);
            printf("\n");
        }
    }
 
   
    
      m_ppg_head = new PolyTriGroup;
      m_ppg_head->m_bSMSENC = m_b_senc_sm;

      m_ppg_head->nContours = m_ncnt;
      m_ppg_head->pn_vertex = m_cntr;             // pointer to array of poly vertex counts
      m_ppg_head->tri_prim_head = pTPG_Head;         // head of linked list of TriPrims
      //m_ppg_head->data_type = DATA_TYPE_DOUBLE;
      

//  Transcribe the raw geometry buffer
//  Converting to float as we go, and
//  allowing for tess_orient
//  Also, convert to SM if requested

      int nptfinal = npta;
      
      //  No longer need the full geometry in the SENC,
      nptfinal = 1;
      
      m_nwkb = (nptfinal +1) * 2 * sizeof(float);
      m_ppg_head->pgroup_geom = (float *)malloc(m_nwkb);
      float *vro = m_ppg_head->pgroup_geom;
      ppt = geoPt;
      float tx,ty;

      for(ip = 0 ; ip < nptfinal ; ip++)
      {
            if(TESS_HORZ == m_tess_orient)
            {
                  ty = *ppt++;
                  tx = *ppt++;
            }
            else
            {
                  tx = *ppt++;
                  ty = *ppt++;
            }

            if(m_b_senc_sm)
            {
            //  Calculate SM from chart common reference point
                  double easting, northing;
                  toSM(ty, tx, 0/*ref_lat*/, 0/*ref_lon*/, &easting, &northing);
                  *vro++ = easting;              // x
                  *vro++ = northing;             // y
            }
            else
            {
                  *vro++ = tx;                  // lon
                  *vro++ = ty;                  // lat
            }

            ppt++;                      // skip z
      }


      m_ppg_head->bsingle_alloc = true;
      m_ppg_head->single_buffer = (unsigned char *)vbo;
      m_ppg_head->single_buffer_size = bytes_needed_vbo;
      m_ppg_head->data_type = DATA_TYPE_FLOAT;
      

      free (geoPt);

      //    All allocated buffers are owned now by the m_ppg_head
      //    And will be freed on dtor of this object
 
      if (tess) tessDeleteTess(tess);

      m_bOK = true;
      

      return 0;
      

}





// GLU tesselation support functions
void  beginCallback(GLenum which, void *polyData)
{
    PolyTessGeo *pThis = (PolyTessGeo *)polyData;

    pThis->m_buf_idx = 0;
    pThis->m_nvcall = 0;
    pThis->m_gltri_type = which;
}

void errorCallback(GLenum errorCode, void *polyData )
{
    const GLubyte *estring;

    estring = gluErrorString(errorCode);
    printf("Tessellation Error: %s\n", estring);
    exit(0);
}


void  endCallback(void *polyData)
{
    PolyTessGeo *pThis = (PolyTessGeo *)polyData;

    //      Create a TriPrim
    if(pThis->m_nvcall > pThis->m_nvmax)                            // keep track of largest number of triangle vertices
          pThis->m_nvmax = pThis->m_nvcall;

    switch(pThis->m_gltri_type)
    {
        case GL_TRIANGLE_FAN:
        case GL_TRIANGLE_STRIP:
        case GL_TRIANGLES:
        {
            TriPrim *pTPG = new TriPrim;
            if(NULL == pThis->m_pTPG_Last)
            {
                pThis->m_pTPG_Head = pTPG;
                pThis->m_pTPG_Last = pTPG;
            }
            else
            {
                pThis->m_pTPG_Last->p_next = pTPG;
                pThis->m_pTPG_Last = pTPG;
            }

            pTPG->p_next = NULL;
            pTPG->type = pThis->m_gltri_type;
            pTPG->nVert = pThis->m_nvcall;

        //  Calculate bounding box
            double sxmax = -1e8;                   // this poly BBox
            double sxmin =  1e8;
            double symax = -1e8;
            double symin =  1e8;

            GLdouble *pvr = pThis->m_pwork_buf;
            for(int iv=0 ; iv < pThis->m_nvcall ; iv++)
            {
                GLdouble xd, yd;
                xd = *pvr++;
                yd = *pvr++;

                if(pThis->m_bcm93)
                {
                     // cm93 hits here
                       double valx = ( xd * pThis->mx_rate ) + pThis->mx_offset + pThis->m_feature_easting;
                       double valy = ( yd * pThis->my_rate ) + pThis->my_offset + pThis->m_feature_northing;
 
                       //    Convert to lat/lon
                       double lat = ( 2.0 * atan ( exp ( valy/CM93_semimajor_axis_meters ) ) - PI/2. ) / DEGREE;
                       double lon = ( valx / ( DEGREE * CM93_semimajor_axis_meters ) );
 
                       sxmax = wxMax(lon, sxmax);
                       sxmin = wxMin(lon, sxmin);
                       symax = wxMax(lat, symax);
                       symin = wxMin(lat, symin);
                }
                else
                {
                        // ENC hits here, values are SM measures from feature reference point
                       double valx = ( xd * pThis->mx_rate ) + pThis->mx_offset + pThis->m_feature_easting;
                       double valy = ( yd * pThis->my_rate ) + pThis->my_offset + pThis->m_feature_northing;
 
                       sxmax = wxMax(valx, sxmax);
                       sxmin = wxMin(valx, sxmin);
                       symax = wxMax(valy, symax);
                       symin = wxMin(valy, symin);
                }
            }
            

            // Compute the final LLbbox for this TriPrim chain
            if(pThis->m_bcm93)
                pTPG->tri_box.Set(symin, sxmin, symax, sxmax);
            else{
                double minlat, minlon, maxlat, maxlon;
                fromSM(sxmin, symin, pThis->m_ref_lat, pThis->m_ref_lon, &minlat, &minlon);
                fromSM(sxmax, symax, pThis->m_ref_lat, pThis->m_ref_lon, &maxlat, &maxlon);
                pTPG->tri_box.Set(minlat, minlon, maxlat, maxlon);
            }


            //  Transcribe this geometry to TriPrim,
            {
                GLdouble *pds = pThis->m_pwork_buf;
                pTPG->p_vertex = (double *)malloc(pThis->m_nvcall * 2 * sizeof(double));
                GLdouble *pdd = (GLdouble*)pTPG->p_vertex;

                for(int ip = 0 ; ip < pThis->m_nvcall ; ip++)
                {
                    double dlon = *pds++;
                    double dlat = *pds++;
                    *pdd++ = dlon + pThis->m_feature_easting;    // adjust to feature ref coordinates
                    *pdd++ = dlat + pThis->m_feature_northing;
                }
            }

            break;
        }
        default:
        {
//            sprintf(buf, "....begin Callback  unknown\n");
            break;
        }
    }
}

void vertexCallback(GLvoid *vertex, void *polyData)
{
    PolyTessGeo *pThis = (PolyTessGeo *)polyData;

    GLdouble *pointer;

    pointer = (GLdouble *) vertex;

    if(pThis->m_buf_idx > pThis->m_buf_len - 4)
    {
        int new_buf_len = pThis->m_buf_len + 100;
        GLdouble * tmp = pThis->m_pwork_buf;

        pThis->m_pwork_buf = (GLdouble *)realloc(pThis->m_pwork_buf, new_buf_len * sizeof(GLdouble));
        if (NULL == pThis->m_pwork_buf)
        {
            free(tmp);
            tmp = NULL;
        }
        else
            pThis->m_buf_len = new_buf_len;
    }

    if(pThis->m_tess_orient == TESS_VERT)
    {
        pThis->m_pwork_buf[pThis->m_buf_idx++] = pointer[0];
        pThis->m_pwork_buf[pThis->m_buf_idx++] = pointer[1];
    }
    else
    {
        pThis->m_pwork_buf[pThis->m_buf_idx++] = pointer[1];
        pThis->m_pwork_buf[pThis->m_buf_idx++] = pointer[0];
    }


    pThis->m_nvcall++;

}

/*  combineCallback is used to create a new vertex when edges
 *  intersect.
 */
void  combineCallback(GLdouble coords[3],
                     GLdouble *vertex_data[4],
                     GLfloat weight[4], GLdouble **dataOut, void *polyData )
{
    PolyTessGeo *pThis = (PolyTessGeo *)polyData;
    
    GLdouble *vertex = (GLdouble *)malloc(6 * sizeof(GLdouble));

    vertex[0] = coords[0];
    vertex[1] = coords[1];
    vertex[2] = coords[2];
    vertex[3] = vertex[4] = vertex[5] = 0. ; //01/13/05 bugfix

    *dataOut = vertex;

    pThis->m_pCombineVertexArray->Add(vertex);
}



wxStopWatch *s_stwt;


//------------------------------------------------------------------------------
//          PolyTriGroup Implementation
//------------------------------------------------------------------------------
PolyTriGroup::PolyTriGroup()
{
    pn_vertex = NULL;             // pointer to array of poly vertex counts
    pgroup_geom = NULL;           // pointer to Raw geometry, used for contour line drawing
    tri_prim_head = NULL;         // head of linked list of TriPrims
    m_bSMSENC = false;
    bsingle_alloc = false;
    single_buffer = NULL;
    single_buffer_size = 0;
    data_type = DATA_TYPE_DOUBLE;
    sfactor = 1.0;
    

}

PolyTriGroup::~PolyTriGroup()
{
    free(pn_vertex);
    free(pgroup_geom);
    //Walk the list of TriPrims, deleting as we go
    TriPrim *tp_next;
    TriPrim *tp = tri_prim_head;
    
    if(bsingle_alloc){
        free(single_buffer);
        while(tp) {
            tp_next = tp->p_next;
            delete tp;
            tp = tp_next;
        }
    }
    else {
        while(tp) {
            tp_next = tp->p_next;
            tp->FreeMem();
            delete tp;
            tp = tp_next;
        }
    }
}

//------------------------------------------------------------------------------
//          TriPrim Implementation
//------------------------------------------------------------------------------
TriPrim::TriPrim()
{
}

TriPrim::~TriPrim()
{
}

void TriPrim::FreeMem()
{
    free(p_vertex);
}






