#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 "config.h"
#include "wdbii.h"
#include "mapgen.h"
#include "e00.h"
#include "dcw.h"

char datadir[1024];

LOCATION *locs;
int nlocs;

MAPARG *mapargs = NULL;
static MAP *maps = NULL;

int dogrid;
int dolabels;
double griddeg;
double movedeg;

char bgcolor_s[80] = "#f8f0e4";
unsigned long bgcolor;
char label_bgcolor_s[80] = "#f8f0e0";
unsigned long label_bgcolor;

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

double iminlat, imaxlat, iminlon, imaxlon;
double minlat, maxlat, minlon, maxlon;
double bminlat, bmaxlat, bminlon, bmaxlon;

double clat, clon;
int cx, cy;

unsigned int scale, bscale;
double iscale;

static XFontStruct *fs, *gfs, *lfs;

static char ls[512];


void ftrans(double lon, double lat, double *xm, double *ym)
{
   if (fortrans[PROJECT](lon * D2R, lat * D2R, xm, ym))
       exit(1);
}

void itrans(double xm, double ym, double *lon, double *lat)
{
   if (invtrans[PROJECT](xm, ym, lon, lat))
       exit(1);
   *lon *= R2D; *lat *= R2D;
}

void ll2xy(double lon, double lat, short *x, short *y)
{
   double xm, ym;
   if (fortrans[PROJECT](lon * D2R, lat * D2R, &xm, &ym))
       exit(1);
   *x = XM2X(xm);
   *y = YM2Y(ym);
}

static void setscale(void)
{
   iscale = PPCM / ((double)scale / 100.);
   movedeg = MOVEDEG;
}

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->showscale < scale) {
           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, BLACK);
       loc = loc->next;
   }

   free(xy);
}

static void drawgrid(void)
{
   XPoint pts[1000];
   double lon, lat = 0.;
//    char s[80];
   int i;

   setfont(gfs);
   setlinewidth(GRID_WIDTH);

   for (lon = -180.; lon <= 180.; lon += griddeg) {
       if (lon < minlon || lon > maxlon)
           continue;
       for (lat = minlat, i = 0; lat <= maxlat; lat++, i++)
           ll2xy(lon, lat, &(pts[i].x), &(pts[i].y));
       poly(pts, i, GRID_COLOR);
//      sprintf(s, "%g", lon);
//      string(x + GS_LONX, GS_LONY, s, GTEXT_COLOR);
   }

   for (lat = -90.; lat <= 90.; lat += griddeg) {
       if (lat < minlat || lat > maxlat)
           continue;
       for (lon = minlon, i = 0; lon <= maxlon; lon++, i++)
           ll2xy(lon, lat, &(pts[i].x), &(pts[i].y));
       poly(pts, i, GRID_COLOR);
//      sprintf(s, "%g", lon);
//      string(x + GS_LONX, GS_LONY, s, GTEXT_COLOR);
   }
}

void drawmaps(void)
{
   double xm, ym;
   char cs[40], ss[40], rs[50];
   MAP *map;

   ftrans(clon, clat, &xm, &ym);
   cx = (int)(xm * iscale);
   cy = (int)(ym * iscale);

   cls(bgcolor);

   for (map = maps; map; map = map->next)  {
       switch (map->type) {
           case MAPGEN:
               drawmapgen((MGMAP *)map);
               break;
           case WDBII:
               drawwdbii((WDBMAP *)map);
               break;
           case E00:
               drawe00((E00MAP *)map, NULL, NULL);
               break;
           case DCW:
               drawdcw((E00MAP *)map);
               break;
       }
   }

   if (dogrid)
       drawgrid();

   if (dolabels)
       drawlabels();

   fillrect(0, 0, WIN_W, TEXT_HEIGHT, TEXT_BGCOLOR);
   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);

   sprintf(ss, "Scale: 1:%u", scale);
   string(SS_X, SRS_Y, ss, TEXT_COLOR);

   sprintf(rs, "Region: %g %g %g %g", minlat, maxlat, minlon, maxlon);
   string(RS_X, SRS_Y, rs, TEXT_COLOR);

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

static void loadmaps(void)
{
   if (mapargs) {
       MAPARG *arg;
       MAP *map = NULL, *prevmap = NULL;

       for (arg = mapargs; arg; arg = arg->next) {
           char tmp[1024], fname[512], color_s[30], fillcolor_s[30];
           unsigned long color, fillcolor;
           int type, width, r;

           strcpy(tmp, arg->data);
           r = sscanf(tmp, "%[^:]:%d:%d:%[^:]:%[^:]", fname,  &type,
                   &width, color_s, fillcolor_s);

           color = getnamedcolor(color_s, NULL);
           fillcolor = -1;
           if (r == 5)
               fillcolor = getnamedcolor(fillcolor_s, NULL);

           switch (type) {
               case MAPGEN:
                   map = (MAP *)loadmapgen(fname, color, fillcolor, width);
                   break;
               case WDBII:
                   map = (MAP *)loadwdbii(fname, color, fillcolor, width);
                   break;
               case E00:
                   map = (MAP *)loade00(fname, color, fillcolor, width,
                           NULL, NULL);
                   break;
               case DCW:
                   map = (MAP *)loaddcw(fname, color, fillcolor, width);
                   break;
           }

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

static void drawis(char *s)
{
   fillrect(LS_X, WIN_H - TEXT_HEIGHT, WIN_W - LS_X, TEXT_HEIGHT,
           TEXT_BGCOLOR);
   string(LS_X, LCS_Y, s, TEXT_COLOR);
   update(LS_X, WIN_H - TEXT_HEIGHT, WIN_W, 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 || ks == XK_s || ks == XK_r) {
           memset(is, 0, sizeof(is));
           p = is;
           fillrect(LS_X, WIN_H - TEXT_HEIGHT, WIN_W - LS_X, TEXT_HEIGHT,
                   TEXT_BGCOLOR);
           update(LS_X, WIN_H - TEXT_HEIGHT, WIN_W - LS_X, TEXT_HEIGHT);
           switch (ks) {
               case XK_c: mode = 0; break;
               case XK_d: mode = 1; break;
               case XK_s: mode = 2; break;
               case XK_r: mode = 3; break;
           }
           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:
           switch (mode) {
               case 0:
                   if (sscanf(is, "%lf %lf", &tlat, &tlon) == 2) {
                       if (tlat >= -89.5 && tlat <= 89.5 &&
                               tlon >= -180. && tlon <= 180.) {
                           clat = tlat; clon = tlon;
                       }
                   }
                   break;
               case 1: {
                   double tmp = atof(is);
                   if (tmp > 0.) griddeg = tmp;
                   }
                   break;
               case 2: {
                   double tmp = atof(is);
                   if (tmp > 0.) { scale = tmp; setscale(); }
                   }
                   break;
               case 3: {
                   double tminlat, tmaxlat, tminlon, tmaxlon;
                   if (sscanf(is, "%lf %lf %lf %lf", &tminlat, &tmaxlat,
                           &tminlon, &tmaxlon) == 4) {
                       minlat = tminlat; maxlat = tmaxlat;
                       minlon = tminlon; maxlon = tmaxlon;
                       clat = minlat + (maxlat - minlat) / 2;
                       clon = minlon + (maxlon - minlon) / 2;
                   }}
                   break;
           }
           drawmaps();
           p = NULL;
           return 1;
       default:
           return 0;
   }
}

void mapinit(int argc, char **argv)
{
   char fname[512];
   FILE *f;
   int i;
   LOCATION *loc;

   if (argc > 2 && (! strcmp(argv[1], "-c"))) {
       if (! (f = fopen(argv[2], "r"))) {
           fprintf(stderr, "Can't open configuration file `%s'\n", argv[2]);
           exit(1);
       }
       argc -= 2; argv += 2;
   } else {
       strcpy(fname, ".mapviewrc");
       if (! (f = fopen(fname, "r"))) {
           sprintf(fname, "%s/.mapviewrc", getenv("HOME"));
           if (! (f = fopen(fname, "r"))) {
               fprintf(stderr, "No .mapviewrc found\n");
               exit(1);
           }
       }
   }

   readconfig(f, argc, argv);
   if (! mapargs) {
       fprintf(stderr, "No maps defined\n");
       exit(1);
   }

   setscale();

   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);

   bgcolor = getnamedcolor(bgcolor_s, NULL);
   label_bgcolor = getnamedcolor(label_bgcolor_s, NULL);

   loadmaps();

   for (loc = locs; loc; loc = loc->next)
       ftrans(loc->lon, loc->lat, &(loc->xm), &(loc->ym));

   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 >= (minlon + movedeg)) {
                               clon -= movedeg;
                               drawmaps();
                           }
                           break;
                       case XK_Right:
                           if (clon <= (maxlon - movedeg)) {
                               clon += movedeg;
                               drawmaps();
                           }
                           break;
                       case XK_Up:
                           if (clat <= (maxlat - movedeg)) {
                               clat += movedeg;
                               drawmaps();
                           }
                           break;
                       case XK_Down:
                           if (clat >= (minlat + movedeg)) {
                               clat -= movedeg;
                               drawmaps();
                           }
                           break;
                       case XK_Prior:
                           /* Overflows at some point */
                           scale /= 2.;
                           setscale();
                           drawmaps();
                           break;
                       case XK_Next:
                           if (scale < 2000000000) {
                               scale *= 2;
                               setscale();
                               drawmaps();
                           }
                           break;
                       case XK_Home:
                           minlat = bminlat; maxlat = bmaxlat;
                           minlon = bminlon; maxlon = bmaxlon;
                           clat = minlat + (maxlat - minlat) / 2;
                           clon = minlon + (maxlon - minlon) / 2;
                           scale = bscale;
                           setscale();
                           drawmaps();
                           break;
                       case XK_F5:
                           dogrid ^= 1;
                           drawmaps();
                           break;
                       case XK_F6:
                           dolabels ^= 1;
                           drawmaps();
                           break;
                       case XK_q:
                           exit(0);
                           break;
                   }
                   break;
           }
       }
       usleep(1000);
   }
}