#include <xsimple.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <math.h>
#include <geolib/cproj.h>
#include "mapview.h"
#include "wdbii.h"

static POINT *pts;
static int npts = 0;
static int penup = TRUE;

static WDBMAP *map;
static WDBMAPREC *recs, *prevrec;


void drawwdbii(WDBMAP *map)
{
   static XPoint p[MAXPOINTS];
   WDBMAPREC *rec;
   int np;

   setlinewidth(map->width);

   for (rec = map->recs; rec; rec = rec->next) {
       int i;

       np = 0;
       for (i = 0; i < rec->npts; i++) {
           if (rec->pts[i].lat < minlat || rec->pts[i].lat > maxlat ||
                   rec->pts[i].lon < minlon || rec->pts[i].lon > maxlon) {
               if (np) {
                   if (np > 1)
                       poly(p, np, map->color);
                   else
                       pset(p[0].x, p[0].y, map->color);
               }
               np = 0;
               continue;
           }
           ll2xy(rec->pts[i].lon, rec->pts[i].lat, &(p[np].x), &(p[np].y));
           np++;
       }

       if (np) {
           if (np > 1)
               poly(p, np, map->color);
           else
               pset(p[0].x, p[0].y, map->color);
       }
   }
}

static void addrec(SEGDICT *sd)
{
   WDBMAPREC *rec = (WDBMAPREC *)malloc(sizeof(WDBMAPREC));
   rec->rank = (char)sd->rank;
   rec->pts = (POINT *)malloc(npts * sizeof(POINT));
   rec->npts = npts;
   rec->minlat = sd->minlat / 3600.;
   rec->maxlat = sd->maxlat / 3600.;
   rec->minlon = sd->minlon / 3600.;
   rec->maxlon = sd->maxlon / 3600.;
   rec->next = NULL;

   memcpy(rec->pts, pts, npts * sizeof(POINT));

   if (! recs)
       recs = rec;
   if (prevrec)
       prevrec->next = rec;
   prevrec = rec;
}

static void pendn(double lon, double lat, SEGDICT *sd)
{
   static SEGDICT *prevsd;

   if (lat < iminlat || lat > imaxlat || lon < iminlon || lon > imaxlon) {
       if (npts)
           addrec(prevsd);
       npts = 0;
       return;
   }

   if (penup) {
       if (npts)
           addrec(prevsd);
       npts = 0;
       penup = FALSE;
   }

   pts[npts].lat = lat; pts[npts].lon = lon;

   if (++npts == MAXPOINTS) {
       addrec(sd);
       npts = 0;
   }

   prevsd = sd;
}

WDBMAP *loadwdbii(char *fname, unsigned long color, unsigned long fillcolor,
       int width)
{
   int fd, segcount, idx, idy, segbufsize, olt, oln, k, stroke, iseg;
   char *databuf;
   BIT32 i32;
   BIT16 *segbuf;
   double lastlon = 0.;
   double lon, lat;
   struct stat st;
   int pos;

   CBDHEAD *header;
   SEGDICT *sd, *sdbuf;
   SEGBUF sb;

   recs = prevrec = NULL;

   if ((fd = open(fname, O_RDONLY)) < 0) {
       perror("open");
       exit(1);
   }
   if (fstat(fd, &st) < 0) {
       perror("fstat");
       exit(1);
   }

   pts = (POINT *)malloc(MAXPOINTS * sizeof(POINT));

   databuf = (char*)malloc(st.st_size);
   read(fd, databuf, st.st_size);
   close(fd);

/*
* Check the file header for the correct magic number,
* and learn the address of the segment dictionary
*/
   header = (CBDHEAD *)databuf;

   if (header->magic != CBD_MAGIC) {
       fprintf(stderr, "File has bad magic number %X != %X\n",
               header->magic, CBD_MAGIC);
       exit(1);
   }

/* allocate space for the segment buffer */
   segbufsize = 2 * header->segmax;
   segbuf = (BIT16 *) malloc (50 + segbufsize);

/* allocate space for the segment dictionary */
   sdbuf = (SEGDICT *) malloc (100 + (header->segsize));
   sd = sdbuf;
   sd++;

/* Get the segment dictionary (it's at the end of the file) */
   memcpy(sd, databuf + header->dictaddr, header->segsize);

/*
* Now look at each segment and decide if we're
* going to keep it or not
*/
   segcount = header->segcount;
   npts = 0;

   sd = sdbuf;
   for (iseg = 1; iseg <= segcount; iseg++) {
       sd++;           /* does this really work? wow! */

       pos = sd->absaddr;
       memcpy(&sb, databuf + pos, sizeof(sb));
       pos += sizeof(sb);

       if (sd->nbytes > segbufsize) {
           fprintf(stderr,
               "Segment %d needs %d bytes; buffer limit is %d.\n",
               iseg, sd->nbytes, segbufsize);
           exit(1);
       }

       memcpy(segbuf, databuf + pos, sd->nbytes);

       k = 0;

       oln = sb.orgx;
       olt = sb.orgy;
       lon = oln / 3600.;
       lat = olt / 3600.;
       penup = TRUE;
       pendn(lon, lat, sd);
       lastlon = lon;

       for (stroke = 1; stroke <= sb.nstrokes; stroke++) {
           if (segbuf[k] & SHORTFLAG) {
/* Flag bit on: unpack a 16-bit field into dx and dy */
               i32 = segbuf[k++];
               if (i32 > 0)
                   i32 &= ~SHORTFLAG;
               idy = i32 & 0xFF;
               if (idy & 0x80)
                   idy |= ~0xFF;       /* extend sign */
               idx = i32 >> 8;
               if (idx & 0x80)
                   idx |= ~0xBF;       /* extend sign */
           } else {
/* Flag bit off: take dx and dy from 32-bit fields. */
               idx = segbuf[k++];
               if (idx < 0)
                   idx |= SHORTFLAG;
               idx = (idx << 16) | (unsigned short) segbuf[k];
               k++;

               idy = segbuf[k];
               k++;
               if (idy < 0)
                   idy |= SHORTFLAG;
               idy = (idy << 16) | segbuf[k];
               k++;
           }

           oln = (oln + idx);
           olt = (olt + idy);
           lon = oln / 3600.;
           lat = olt / 3600.;

           if (fabs(lastlon - lon) > 180.)
               penup = TRUE;

           pendn(lon, lat, sd);
           lastlon = lon;
       }
   }

   if (npts)
       addrec(sd);

   free(sdbuf);
   free(segbuf);
   free(databuf);
   free(pts);

   map = (WDBMAP *)malloc(sizeof(WDBMAP));
   map->type = WDBII;
   map->color = color;
   map->fillcolor = fillcolor;
   map->width = width;
   map->recs = recs;
   map->next = NULL;
   return map;
}