#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 <geolib/cproj.h>
#include "mapview.h"
#include "wdbii.h"
#include "mapgen.h"

LOCATION *locs;
static int nlocs;

static MAP *namer_cil, *namer_riv, *namer_bdy, *namer_pby,
       *samer_cil, *samer_riv, *samer_bdy,
       *europe_cil, *europe_riv, *europe_bdy,
       *africa_cil, *africa_riv, *africa_bdy,
       *asia_cil, *asia_riv, *asia_bdy;

static EXTMAP *extmaps = NULL;

static int feat = 1;

static int do_points = DO_POINTS;
static char do_grid = DO_GRID;
static double grid_deg;

static char do_labels = DO_LABELS;

static unsigned long bgcolor;
static unsigned long color[4][15];
static int width[4][15];

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

double clat = 0, clon = 0;
int cx, cy;
double zoom = 1;
double scale;

static double minlat, maxlat, minlon, maxlon;

static XFontStruct *fs, *gfs, *lfs;

static char ls[512];


static void setfeat(void)
{
   if (zoom == 1)
       feat = 1;
   else if (zoom == 2)
       feat = 1;
   else if (zoom == 4)
       feat = 2;
   else if (zoom == 8)
       feat = 2;
   else if (zoom == 16)
       feat = 3;
   else if (zoom == 32)
       feat = 4;
   else
       feat = 15;
}

static void getminmax(void)
{
   double xm, ym, maxxm, maxym;

   if (fortrans[PROJECT](180. * D2R, 89.5 * D2R, &maxxm, &maxym))
       exit(1);

   xm = X2XM(0);
   ym = Y2YM(WIN_H - TEXT_HEIGHT);
   if (fabs(xm) > maxxm) xm = (xm < 0) ? -maxxm : maxxm;
   if (fabs(ym) > maxym) ym = (ym < 0) ? -maxym : maxym;
   if (invtrans[PROJECT](xm, ym, &minlon, &minlat))
       exit(1);
   minlon = (float)(minlon * R2D);
   minlat = (float)(minlat * R2D);

   xm = X2XM(WIN_W);
   ym = Y2YM(0);
   if (fabs(xm) > maxxm) xm = (xm < 0) ? -maxxm : maxxm;
   if (fabs(ym) > maxym) ym = (ym < 0) ? -maxym : maxym;
   if (invtrans[PROJECT](xm, ym, &maxlon, &maxlat))
       exit(1);
   maxlon = (float)(maxlon * R2D);
   maxlat = (float)(maxlat * R2D);
}

static void drawlabels(void)
{
   LOCATION *loc;
   struct xy { int x, y; } *xy;
   int i;

   xy = (struct xy *)malloc(nlocs * sizeof(struct xy));

   setfont(lfs);

   loc = locs;
   for (i = 0; i < nlocs; i++) {
       if (loc->lat < minlat || loc->lat > maxlat ||
               loc->lon < minlon || loc->lon > maxlon ||
               loc->showzoom > zoom) {
           xy[i].x = -1000;
           loc = loc->next;
           continue;
       }
       xy[i].x = XM2X(loc->xm);
       xy[i].y = YM2Y(loc->ym);
       loc = loc->next;
   }

   for (i = 0; i < nlocs; i++) {
       if (xy[i].x == -1000)
           continue;
       fillellipse(xy[i].x - 3, xy[i].y - 3, 6, 6, LABEL_DOTCOLOR);
       ellipse(xy[i].x - 3, xy[i].y - 3, 6, 6, BLACK);
   }

   loc = locs;
   for (i = 0; i < nlocs; i++) {
       if (xy[i].x == -1000) { loc = loc->next; continue; }
       imgstring(xy[i].x + LABEL_X, xy[i].y + LABEL_Y, loc->name,
               LABEL_BGCOLOR, LABEL_FGCOLOR);
       loc = loc->next;
   }

   free(xy);
}

static void drawgrid(void)
{
   double lon, lat = 0., xm, ym;
   int x, y;
   char s[80];

   setfont(gfs);
   setlinewidth(GRID_WIDTH);

   for (lon = -180.; lon <= 180.; lon += grid_deg) {
       if (lon < minlon)
           continue;
       if (lon > maxlon)
           break;
       if (fortrans[PROJECT](lon * D2R, lat * D2R, &xm, &ym))
           exit(1);
       x = XM2X(xm);
       line(x, 0, x, (WIN_H - 1) - TEXT_HEIGHT, GRID_COLOR);
       sprintf(s, "%g", lon);
       string(x + GS_LONX, GS_LONY, s, GTEXT_COLOR);
   }

   for (lat = -89.5; lat <= 89.5; lat += (lat == -89.5) ?
           grid_deg - (double)(grid_deg >= 1.) * .5 : grid_deg) {
       if (lat == -89.5 || lat < minlat)
           continue;
       if (lat > maxlat)
           break;
       if (fortrans[PROJECT](lon * D2R, lat * D2R, &xm, &ym))
           exit(1);
       y = YM2Y(ym);
       line(0, y, WIN_W - 1, y, GRID_COLOR);
       sprintf(s, "%g", lat);
       string(GS_LATX, y + GS_LATY, s, GTEXT_COLOR);
   }
}

static void drawextmap(EXTMAP *map)
{
   static XPoint p[MAXPOINTS];
   MAPREC *rec;

   setlinewidth(map->width);

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

       if (rec->minlat > maxlat || rec->maxlat < minlat ||
               rec->minlon > maxlon || rec->maxlon < minlon)
           continue;

       for (i = 0; i < rec->npts; i++) {
           p[i].x = XM2X(rec->pts[i].xm);
           p[i].y = YM2Y(rec->pts[i].ym);
       }

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

static void drawmap(MAP *map)
{
   static XPoint p[MAXPOINTS];
   MAPREC *rec;
   int forcewidth = -1;
   int type = map->type;

   switch (map->type) {
       case CIL:
           if (zoom <= CIL_FWZOOM) forcewidth = 1; break;
       case RIV:
           if (zoom <= RIV_FWZOOM) forcewidth = 1; break;
       case BDY:
           if (zoom <= BDY_FWZOOM) forcewidth = 1; break;
       case PBY:
           if (zoom <= PBY_FWZOOM) forcewidth = 1; break;
   }
   if (forcewidth != -1)
       setlinewidth(forcewidth);

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

       if (rec->rank > feat)
           continue;

       if (rec->minlat > maxlat || rec->maxlat < minlat ||
               rec->minlon > maxlon || rec->maxlon < minlon)
           continue;

       for (i = 0; i < rec->npts; i++) {
           p[i].x = XM2X(rec->pts[i].xm);
           p[i].y = YM2Y(rec->pts[i].ym);
       }

       if (rec->npts > 1) {
           if (forcewidth == -1)
               setlinewidth(width[type][(int)(rec->rank - 1)]);
           poly(p, rec->npts, color[type][(int)(rec->rank - 1)]);
       } else
           pset(p[0].x, p[0].y, color[type][(int)(rec->rank - 1)]);
   }
}

void drawmaps(void)
{
   double xm, ym;
   char cs[40];

   if (fortrans[PROJECT](clon * D2R, clat * D2R, &xm, &ym))
       exit(1);
   cx = (int)(xm * scale * zoom);
   cy = (int)(ym * scale * zoom);

   getminmax();

   cls(bgcolor);

   if (extmaps) {
       EXTMAP *map;
       for (map = extmaps; map; map = map->next)
           drawextmap(map);
       goto grid;
   }

   if (do_points & DO_NAMER) {
       if (do_points & DO_CIL)
           drawmap(namer_cil);
       if (do_points & DO_RIV)
           drawmap(namer_riv);
       if (do_points & DO_BDY) {
           drawmap(namer_bdy);
           drawmap(namer_pby);
       }
   }
   if (do_points & DO_SAMER) {
       if (do_points & DO_CIL)
           drawmap(samer_cil);
       if (do_points & DO_RIV)
           drawmap(samer_riv);
       if (do_points & DO_BDY)
           drawmap(samer_bdy);
   }
   if (do_points & DO_EUROPE) {
       if (do_points & DO_CIL)
           drawmap(europe_cil);
       if (do_points & DO_RIV)
           drawmap(europe_riv);
       if (do_points & DO_BDY)
           drawmap(europe_bdy);
   }
   if (do_points & DO_AFRICA) {
       if (do_points & DO_CIL)
           drawmap(africa_cil);
       if (do_points & DO_RIV)
           drawmap(africa_riv);
       if (do_points & DO_BDY)
           drawmap(africa_bdy);
   }
   if (do_points & DO_ASIA) {
       if (do_points & DO_CIL)
           drawmap(asia_cil);
       if (do_points & DO_RIV)
           drawmap(asia_riv);
       if (do_points & DO_BDY)
           drawmap(asia_bdy);
   }

grid:
   if (do_grid)
       drawgrid();

   if (do_labels)
       drawlabels();

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

   setfont(fs);
   string(LS_X, LCS_Y, ls, TEXT_COLOR);
   sprintf(cs, "%8.3f %8.3f", clat, clon);
   string(CS_X, LCS_Y, cs, TEXT_COLOR);

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

static void loadmaps(int argc, char **argv)
{
   char *names[] = {
       "namer/cil", "namer/riv", "namer/bdy", "namer/pby",
       "samer/cil", "samer/riv", "samer/bdy",
       "europe/cil", "europe/riv", "europe/bdy",
       "africa/cil", "africa/riv", "africa/bdy",
       "asia/cil", "asia/riv", "asia/bdy",
       NULL
   };
   MAP **maps[] = {
       &namer_cil, &namer_riv, &namer_bdy, &namer_pby,
       &samer_cil, &samer_riv, &samer_bdy,
       &europe_cil, &europe_riv, &europe_bdy,
       &africa_cil, &africa_riv, &africa_bdy,
       &asia_cil, &asia_riv, &asia_bdy
   };
   char *name;
   int i;

   if (argc > 1) {
       EXTMAP *map, *prevmap = NULL;

       for (i = 1; i < argc; i++) {
           char fname[512], *p1, *p2;
           unsigned long color;
           int width;

           strcpy(fname, argv[i]);
           if (! (p1 = strchr(fname, ':'))) {
               fprintf(stderr, "Invalid argument\n");
               exit(1);
           }
           *(p1++) = '\0';
           if (! (p2 = strchr(p1, ':'))) {
               fprintf(stderr, "Invalid argument\n");
               exit(1);
           }
           *(p2++) = '\0';

           color = getnamedcolor(p1, NULL);
           width = atoi(p2);
           map = loadmapgen(fname, color, width);

           if (! extmaps)
               extmaps = map;
           if (prevmap)
               prevmap->next = map;
           prevmap = map;
       }
       return;
   }

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

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

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

   if (! p) {
       if (ks == XK_c || ks == XK_d) {
           memset(is, 0, sizeof(is));
           p = is;
           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);
           mode = (ks == XK_c) ? 0 : 1;
           return 1;
       } else
           return 0;
   }

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

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

static void parselocs(char *s)
{
   char *tmp, *p;
   LOCATION *loc, *prevloc = NULL;

   tmp = malloc(strlen(s) + 1);
   strcpy(tmp, s);

   if (! (p = strtok(tmp, ":")))
       goto error;

   for (;;) {
       loc = (LOCATION *)malloc(sizeof(LOCATION));

       loc->name = (char *)malloc(strlen(p) + 1);
       strcpy(loc->name, p);
       if (! (p = strtok(NULL, ":")))
           goto error;
       loc->lat = atof(p);
       if (! (p = strtok(NULL, ":")))
           goto error;
       loc->lon = atof(p);
       if (! (p = strtok(NULL, ":")))
           goto error;
       loc->showzoom = atoi(p);

       if (fortrans[PROJECT](loc->lon * D2R, loc->lat * D2R,
               &(loc->xm), &(loc->ym)))
           exit(1);

       loc->next = NULL;
       if (! locs)
           locs = loc;
       if (prevloc)
           prevloc->next = loc;
       prevloc = loc;

       nlocs++;
       if (! (p = strtok(NULL, ":")))
           break;
   }

   free(tmp);
   return;

error:
   fprintf(stderr, "Invalid location string format\n");
   exit(1);
}

void mapinit(int argc, char **argv)
{
   double outparm[15];
   long iflag;
   double x, y;
   int i;
   LOCATION *loc;

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

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

   parselocs(LOCATIONS);

   loc = locs;
   strcpy(ls, "");
   for (i = 0; i < ((nlocs >= 10) ? 10 : nlocs); i++) {
       int n = (i == 9) ? 0 : i + 1;
       sprintf(ls, "%s%d %s  ", ls, n, loc->name);
       loc = loc->next;
   }

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

   fs = getfont(FONT);
   gfs = getfont(GRID_FONT);
   lfs = getfont(LABEL_FONT);
   setfont(fs);

   grid_deg = GRID_DEGREES;

   bgcolor = getnamedcolor("#f8f0e4", NULL);

   color[CIL][0] = CIL1_COLOR; color[CIL][1] = CIL2_COLOR;
   color[CIL][2] = CIL3_COLOR; color[CIL][3] = CIL4_COLOR;
   color[CIL][4] = CIL4_COLOR; color[CIL][5] = CIL5_COLOR;
   color[CIL][6] = CIL7_COLOR; color[CIL][7] = CIL8_COLOR;
   color[CIL][8] = CIL9_COLOR; color[CIL][9] = CIL10_COLOR;
   color[CIL][10] = CIL11_COLOR; color[CIL][11] = CIL12_COLOR;
   color[CIL][12] = CIL13_COLOR; color[CIL][13] = CIL14_COLOR;
   color[CIL][14] = CIL15_COLOR;

   color[RIV][0] = RIV1_COLOR; color[RIV][1] = RIV2_COLOR;
   color[RIV][2] = RIV3_COLOR; color[RIV][3] = RIV4_COLOR;
   color[RIV][4] = RIV5_COLOR; color[RIV][5] = RIV6_COLOR;
   color[RIV][6] = RIV7_COLOR; color[RIV][7] = RIV8_COLOR;
   color[RIV][8] = RIV9_COLOR; color[RIV][9] = RIV10_COLOR;
   color[RIV][10] = RIV11_COLOR; color[RIV][11] = RIV12_COLOR;

   color[BDY][0] = BDY1_COLOR; color[BDY][1] = BDY2_COLOR;
   color[BDY][2] = BDY3_COLOR;

   color[PBY][0] = PBY1_COLOR; color[PBY][1] = PBY2_COLOR;
   color[PBY][2] = PBY3_COLOR;

   width[CIL][0] = CIL1_WIDTH; width[CIL][1] = CIL2_WIDTH;
   width[CIL][2] = CIL3_WIDTH; width[CIL][3] = CIL4_WIDTH;
   width[CIL][4] = CIL4_WIDTH; width[CIL][5] = CIL5_WIDTH;
   width[CIL][6] = CIL7_WIDTH; width[CIL][7] = CIL8_WIDTH;
   width[CIL][8] = CIL9_WIDTH; width[CIL][9] = CIL10_WIDTH;
   width[CIL][10] = CIL11_WIDTH; width[CIL][11] = CIL12_WIDTH;
   width[CIL][12] = CIL13_WIDTH; width[CIL][13] = CIL14_WIDTH;
   width[CIL][14] = CIL15_WIDTH;

   width[RIV][0] = RIV1_WIDTH; width[RIV][1] = RIV2_WIDTH;
   width[RIV][2] = RIV3_WIDTH; width[RIV][3] = RIV4_WIDTH;
   width[RIV][4] = RIV5_WIDTH; width[RIV][5] = RIV6_WIDTH;
   width[RIV][6] = RIV7_WIDTH; width[RIV][7] = RIV8_WIDTH;
   width[RIV][8] = RIV9_WIDTH; width[RIV][9] = RIV10_WIDTH;
   width[RIV][10] = RIV11_WIDTH; width[RIV][11] = RIV12_WIDTH;

   width[BDY][0] = BDY1_WIDTH; width[BDY][1] = BDY2_WIDTH;
   width[BDY][2] = BDY3_WIDTH;

   width[PBY][0] = PBY1_WIDTH; width[PBY][1] = PBY2_WIDTH;
   width[PBY][2] = PBY3_WIDTH;

   loadmaps(argc, argv);
   drawmaps();
}

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

   for (;;) {
       XEvent e;

       while (getevent(&e)) {
           switch (e.type) {
               KeySym ks;
               case Expose:
                   doexpose(&e);
                   break;
               case KeyPress:
                   ks = XLookupKeysym((XKeyEvent *)&e, 0);
                   if (parseis(ks))
                       break;
                   if (ks >= XK_0 && ks <= XK_9) {
                       int n = ks - XK_0, i;
                       LOCATION *loc;
                       if (n > nlocs)
                           break;
                       n = (n > 0) ? n - 1 : ((nlocs >= 10) ? 9 : nlocs - 1);
                       loc = locs;
                       for (i = 0; i < n; i++)
                           loc = loc->next;
                       clat = loc->lat;
                       clon = loc->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:
                           /* Overflows at some point */
                           zoom *= 2;
                           setfeat();
                           drawmaps();
                           break;
                       case XK_Next:
                           if (zoom > 1) {
                               zoom /= 2;
                               setfeat();
                               drawmaps();
                           }
                           break;
                       case XK_Home:
                           clat = 0.;
                           clon = 0.;
                           zoom = 1.;
                           setfeat();
                           drawmaps();
                           break;
                       case XK_F1:
                           do_points ^= DO_NAMER;
                           drawmaps();
                           break;
                       case XK_F2:
                           do_points ^= DO_SAMER;
                           drawmaps();
                           break;
                       case XK_F3:
                           do_points ^= DO_EUROPE;
                           drawmaps();
                           break;
                       case XK_F4:
                           do_points ^= DO_AFRICA;
                           drawmaps();
                           break;
                       case XK_F5:
                           do_points ^= DO_ASIA;
                           drawmaps();
                           break;
                       case XK_F7:
                           do_grid ^= 1;
                           drawmaps();
                           break;
                       case XK_F8:
                           do_labels ^= 1;
                           drawmaps();
                           break;
                       case XK_F9:
                           do_points ^= DO_CIL;
                           drawmaps();
                           break;
                       case XK_F10:
                           do_points ^= DO_RIV;
                           drawmaps();
                           break;
                       case XK_F11:
                           do_points ^= DO_BDY;
                           drawmaps();
                           break;
                       case XK_minus:
                           if (feat > 1) {
                               feat--;
                               drawmaps();
                           }
                           break;
                       case XK_equal:
                           if (feat < 15) {
                               feat++;
                               drawmaps();
                           }
                           break;
                       case XK_q:
                           exit(0);
                           break;
                   }
                   break;
           }
       }
       usleep(1000);
   }
}