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

static int do_points = DO_COAST | DO_ISLAND | DO_LAKE | DO_COUNTRY | DO_STATE;

LOCATION loc[] = {
   {"Norrk�ping", 58.5934, 16.1946},
   {"London", 51.533, 0.083},
   {"Rome", 41.9, 12.45},
   {"Moscow", 55.75, 37.6},
   {"L.A.", 34.05, -118.25},
   {"New York", 40.783, -73.966},
   {"Kingston", 44.25, -76.5},
   {"Sydney", -34, 151},
   {"Kuala Lumpur", 3.133, 101.7},
   {"Tokyo", 35.666, 139.75}
};

static int nloc = sizeof(loc) / sizeof(LOCATION);

static MAP *coast, *island, *lake, *river, *country, *state;

static int detail = DETAIL;

long (*fortrans[MAXPROJ + 1])(double, double, double *, double *);

static double clat = 0., clon = 0.;
static short cx, cy;
static double scale;
static double zoom = 1;

static char ls[512];


static void drawmap(MAP *map, unsigned long color)
{
   static XPoint p[35000];
   int i, np = 0;
   int inside = 0;

   for (i = 0; i < map->npts; i++) {
       if (map->pts[i].hdr > 1000) {
           if (np && inside) {
               if (np > 1)
                   poly(p, np, color);
               else
                   pset(p[0].x, p[0].y, color);
           }
           inside = 0;
           np = 0;

           p[np].x = XM2X(map->pts[i].x);
           p[np].y = YM2Y(map->pts[i].y);

           if (p[np].x >= 0 && p[np].x < WIN_W && p[np].y >= 0 &&
                   p[np].y < WIN_H)
               inside++;
           np++;
       } else {
           if (map->pts[i].hdr >= detail) {
               short xtmp, ytmp;

               xtmp = XM2X(map->pts[i].x);
               ytmp = YM2Y(map->pts[i].y);

               if (abs(xtmp - p[np - 1].x) > WIN_W/2) {
                   if (inside) {
                       if (np > 1)
                           poly(p, np, color);
                       else
                           pset(p[0].x, p[0].y, color);
                   }
                   inside = 0;
                   np = 0;
               }

               p[np].x = xtmp;
               p[np].y = ytmp;
               if (p[np].x >= 0 && p[np].x < WIN_W && p[np].y >= 0 &&
                       p[np].y < WIN_H)
                   inside++;
               np++;
           }
       }
   }

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

static void drawmaps(void)
{
   double x, y;
   char cs[40];

   if (fortrans[MERCAT](clon * D2R, clat * D2R, &x, &y))
       exit(1);
   cx = (short)(x * scale * zoom);
   cy = (short)(y * scale * zoom);

   cls(BGCOLOR);

   if (do_points & DO_COAST)
       drawmap(coast, COAST_COLOR);
   if (do_points & DO_ISLAND)
       drawmap(island, ISLAND_COLOR);
   if (do_points & DO_LAKE)
       drawmap(lake, LAKE_COLOR);
   if (do_points & DO_RIVER)
       drawmap(river, RIVER_COLOR);
   if (do_points & DO_COUNTRY)
       drawmap(country, COUNTRY_COLOR);
   if (do_points & DO_STATE)
       drawmap(state, STATE_COLOR);

   fillrect(0, WIN_H - TEXT_HEIGHT, WIN_W, TEXT_HEIGHT, TEXT_BGCOLOR);

   string(LS_X, LS_Y, ls, TEXT_COLOR);
   sprintf(cs, "%8.3f %8.3f", clat, clon);
   string(CS_X, CS_Y, cs, TEXT_COLOR);

   update(0, 0, 0, 0);
}

static MAP *loadmap(char *name)
{
   char fname[1024];
   int fd;
   struct stat sb;
   MAP *map;
   PNTREC *tmp;
   int i;

   sprintf(fname, "%s/%s.PNT", DATADIR, name);

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

   tmp = (PNTREC *)malloc(sb.st_size);
   read(fd, tmp, sb.st_size);

   map = (MAP *)malloc(sizeof(MAP));
   map->npts = sb.st_size / sizeof(PNTREC);
   map->pts = (COORDREC *)malloc(map->npts * sizeof(COORDREC));

   for (i = 0; i < map->npts; i++) {
       map->pts[i].hdr = tmp[i].hdr;

       if (fortrans[MERCAT](((double)tmp[i].lon / 60.) * D2R,
               ((double)tmp[i].lat / 60.) * D2R,
               &(map->pts[i].x), &(map->pts[i].y)))
           exit(1);
   }

   free(tmp);
   close(fd);
   return map;
}

static void loadmaps(void)
{
   char *names[] = {"COAST", "ISLAND", "LAKE", "RIVER", "COUNTRY",
           "STATE", NULL};
   MAP **maps[] = {&coast, &island, &lake, &river, &country, &state};
   char *name;
   int i;

   for (i = 0; (name = names[i]); i++)
       *(maps[i]) = loadmap(names[i]);
}

static void drawcs(char *s)
{
   fillrect(CS_X, WIN_H - TEXT_HEIGHT, WIN_W - CS_X, TEXT_HEIGHT,
           TEXT_BGCOLOR);
   string(CS_X, CS_Y, s, TEXT_COLOR);
   update(CS_X, WIN_H - TEXT_HEIGHT, WIN_W - CS_X, TEXT_HEIGHT);
}

static int parsecs(KeySym ks)
{
   static char cs[80], *p = NULL;
   double tlat, tlon;

   if (! p) {
       if (ks == XK_c) {
           memset(cs, 0, sizeof(cs));
           p = cs;
           fillrect(CS_X, WIN_H - TEXT_HEIGHT, WIN_W - CS_X, TEXT_HEIGHT,
                   TEXT_BGCOLOR);
           update(CS_X, WIN_H - TEXT_HEIGHT, WIN_W - CS_X, TEXT_HEIGHT);
           return 1;
       } else
           return 0;
   }

   if (ks >= XK_0 && ks <= XK_9) {
       *(p++) = '0' + (ks - XK_0); drawcs(cs);
       return 1;
   }

   switch (ks) {
       case XK_period:
           *(p++) = '.'; drawcs(cs);
           return 1;
       case XK_minus:
           *(p++) = '-'; drawcs(cs);
           return 1;
       case XK_space:
           *(p++) = ' '; drawcs(cs);
           return 1;
       case XK_BackSpace:
           if (p > cs) {
               *(--p) = '\0'; drawcs(cs);
           }
           return 1;
       case XK_Return:
           if (sscanf(cs, "%lf %lf", &tlat, &tlon) == 2) {
               if (tlat >= -89.5 && tlat <= 89.5 &&
                       tlon >= -180. && tlon <= 180.) {
                   clat = tlat; clon = tlon;
               }
           }
           drawmaps();
           p = NULL;
           return 1;
       default:
           return 0;
   }
}

void mapinit(void)
{
   double outparm[15];
   long iflag;
   double x, y;
   int i;
   XFontStruct *fs;

   for (i = 0; i < 15; i++)
       outparm[i] = 0.;
   for_init(MERCAT, 0, outparm, 0, NULL, NULL, &iflag, fortrans);
   if (iflag)
       exit(1);

   if (fortrans[MERCAT](180. * D2R, 89.5 * D2R, &x, &y))
       exit(1);
   scale = ((double)WIN_W / 2) / x;

   strcpy(ls, "");
   for (i = 0; i < nloc; i++) {
       int n = (i == nloc - 1) ? 0 : i + 1;
       sprintf(ls, "%s%d %s  ", ls, n, loc[i].name);
   }

   xinit("mapview", "Mapview", "MapView", WIN_X, WIN_Y, WIN_W, WIN_H, 0,
           "black", "black", 0);

   fs = getfont(FONT);
   setfont(fs);

   loadmaps();
   drawmaps();
}

int main(int argc, char **argv)
{
   mapinit();

   for (;;) {
       XEvent e;

       while (getevent(&e)) {
           switch (e.type) {
               KeySym ks;
               case Expose:
                   doexpose(&e);
                   break;
               case KeyPress:
                   ks = XLookupKeysym((XKeyEvent *)&e, 0);
                   if (parsecs(ks))
                       break;
                   if (ks >= XK_0 && ks <= XK_9) {
                       int n = ks - XK_0;
                       n = (n > 0) ? n - 1 : nloc - 1;
                       clat = loc[n].lat;
                       clon = loc[n].lon;
                       drawmaps();
                       break;
                   }
                   switch (ks) {
                       case XK_Left:
                           if (clon >= (-180. + (10. / zoom))) {
                               clon -= 10. / zoom;
                               drawmaps();
                           }
                           break;
                       case XK_Right:
                           if (clon <= (180. - (10. / zoom))) {
                               clon += 10. / zoom;
                               drawmaps();
                           }
                           break;
                       case XK_Up:
                           if (clat <= (89.5 - (10. / zoom))) {
                               clat += 10. / zoom;
                               drawmaps();
                           }
                           break;
                       case XK_Down:
                           if (clat >= (-89.5 + (10. / zoom))) {
                               clat -= 10. / zoom;
                               drawmaps();
                           }
                           break;
                       case XK_Prior:
                           if (zoom < 64) {
                               zoom *= 2;
                               drawmaps();
                           }
                           break;
                       case XK_Next:
                           if (zoom > 1) {
                               zoom /= 2;
                               drawmaps();
                           }
                           break;
                       case XK_Home:
                           clat = 0.;
                           clon = 0.;
                           zoom = 1.;
                           drawmaps();
                           break;
                       case XK_F1:
                           do_points ^= DO_LAKE;
                           drawmaps();
                           break;
                       case XK_F2:
                           do_points ^= DO_RIVER;
                           drawmaps();
                           break;
                       case XK_F3:
                           do_points ^= DO_COUNTRY;
                           drawmaps();
                           break;
                       case XK_F4:
                           do_points ^= DO_STATE;
                           drawmaps();
                           break;
                       case XK_minus:
                           if (detail < 5) {
                               detail++;
                               drawmaps();
                           }
                           break;
                       case XK_equal:
                           if (detail > 1) {
                               detail--;
                               drawmaps();
                           }
                           break;
                       case XK_q:
                           exit(0);
                           break;
                   }
                   break;
           }
       }
       usleep(1000);
   }
}