#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <sys\stat.h>
#include <winlib/winlib.h>
#include <geolib/cproj.h>
#include <geolib/proj.h>

#include "mapplot.h"
#include "wdb-ii.h"
#include "map.h"


BOOL SetupOtherWorld(VARS *v, HDC hdc)
{
       RECT rect;
       POINT origPt;
       int i;
       double outParm[15];
       long iFlag;
       double lonRad, latRad, x, x2, y;
       double extX, extY;

       if (v->minMax.minLat < -89.5)
               v->minMax.minLat = -89.5;
       if (v->minMax.maxLat > 89.5)
               v->minMax.maxLat = 89.5;

       /* Report errors */
       init(0, -1, NULL, NULL);

       for(i = 0; i < 15; i++)
               outParm[i] = 0.;
/*
       Mercator:

   0, 1        Semi-major/semi-minor axis of ellipsoid
       4               Longitude of the central meridian
       5               Latitude of true scale
   6           False easting in the same units as the semi-major axis
   7           False northing in the same units as the semi-major axis

       Robinson, Hammer:

       0               Sphere
       4               Central meridian
   6           False easting in the same units as the semi-major axis
   7           False northing in the same units as the semi-major axis

       Orthographic;

       0               Sphere
       4               Central longitude
       5               Central latitude
   6           False easting in the same units as the semi-major axis
   7           False northing in the same units as the semi-major axis
*/
       for_init(v->project, 0, outParm, 0, NULL, NULL, &iFlag, v->ForTrans);
       if (iFlag)
               return FALSE;
       inv_init(v->project, 0, outParm, 0, NULL, NULL, &iFlag, v->InvTrans);
       if (iFlag)
               return FALSE;

       switch (v->project) {
               case MERCAT:
                       lonRad = 180. * D2R;
                       latRad = 89.5 * D2R;
                       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                               return FALSE;
                       break;

               case ROBIN:
               case HAMMER:
                       lonRad = 180. * D2R;
                       latRad = 0.;
                       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                               return FALSE;
                       lonRad = 0.;
                       latRad = 89.5 * D2R;
                       if (v->ForTrans[v->project](lonRad, latRad, &x2, &y))
                               return FALSE;
                       break;

               case ORTHO:
                       lonRad = 90. * D2R;
                       latRad = 0.;
                       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                               return FALSE;
                       lonRad = 0. * D2R;
                       latRad = 89.5 * D2R;
                       if (v->ForTrans[v->project](lonRad, latRad, &x2, &y))
                               return FALSE;
                       break;
       }

       extX = (y > x) ? 32767. * (x / y) : 32767.;
       extY = (y > x) ? 32767. : 32767. * (y / x);
       v->scale = 32767. / max(x, y);

       GetClientRect(v->hwndCont, &rect);
       SetMapMode(hdc, MM_ISOTROPIC);
       SetWindowExtEx(hdc, (int)extX, (int)extY, NULL);
       SetViewportExtEx(hdc, (rect.right / 2) * v->zoom,
                       (-rect.bottom / 2) * v->zoom, NULL);

       lonRad = v->origLon * D2R;
       latRad = v->origLat * D2R;
       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
               return FALSE;

       origPt.x = (LONG)(x * v->scale);
       origPt.y = (LONG)(y * v->scale);
       LPtoDP(hdc, &origPt, 1);
       SetViewportOrgEx(hdc, rect.right / 2 - origPt.x,
                       rect.bottom / 2 - origPt.y, NULL);

       return TRUE;
}


BOOL SetupWorld(VARS *v, HDC hdc)
{
       RECT rect;
       POINT origPt;

       if (v->project != PLATE_CARREE)
               return SetupOtherWorld(v, hdc);

       if (v->minMax.minLat == -89.5)
               v->minMax.minLat = -90.;
       if (v->minMax.maxLat == 89.5)
               v->minMax.maxLat = 90.;

       GetClientRect(v->hwndCont, &rect);
       SetMapMode(hdc, MM_ISOTROPIC);
       SetWindowExtEx(hdc, 18000, 9000, NULL);
       SetViewportExtEx(hdc, (rect.right / 2) * v->zoom,
                       (-rect.bottom / 2) * v->zoom, NULL);
       origPt.x = (LONG)(v->origLon * 100.);
       origPt.y = (LONG)(v->origLat * 100.);
       LPtoDP(hdc, &origPt, 1);
       SetViewportOrgEx(hdc, rect.right / 2 - origPt.x,
                       rect.bottom / 2 - origPt.y, NULL);

       return TRUE;
}


BOOL ReadMap(VARS *v, char *name)
{
       int fd, numRead, toRead;
       DPOINT p;
       MAP_POLY *newPoly;
       char tmp[1024];
       int i, np = 0;
       int maxNp = 0;
       struct stat st;
       MAP_REC *dataBuf;

       sprintf(tmp, "%s\\%s.PNT", v->dataDir, name);
       if ((fd = open(tmp, O_RDONLY | O_BINARY)) == -1) {
               MBPrintf("MapPlot", "Couldn't open data file '%s'", tmp);
               return FALSE;
       }

       stat(tmp, &st);
       dataBuf = (MAP_REC *)malloc(st.st_size);
       read(fd, dataBuf, st.st_size);
       close(fd);

       for (i = 0; i < (st.st_size / sizeof(MAP_REC)); i++) {
               if (dataBuf[i].code > 5) {
                       if (np) {
                               newPoly->nPoints = np;
                               if (! v->mapData)
                                       v->mapData = newPoly;
                               if (v->prevPoly)
                                       v->prevPoly->next = newPoly;
                               v->prevPoly = newPoly;
                       }

                       newPoly = (MAP_POLY *)malloc(sizeof(MAP_POLY));

                       if (dataBuf[i].code < 2001)
                               newPoly->feature = 1;
                       else if (dataBuf[i].code < 4001)
                               newPoly->feature = 2;
                       else if (dataBuf[i].code < 5001)
                               newPoly->feature = 4;
                       else if (dataBuf[i].code < 6001)
                               newPoly->feature = 5;
                       else if (dataBuf[i].code < 7001)
                               newPoly->feature = 6;
                       else
                               newPoly->feature = 7;

                       newPoly->points = (MAP_POINT *)malloc(sizeof(MAP_POINT));
                       newPoly->next = NULL;

                       np = 0;
                       newPoly->points[np].lon = (double)dataBuf[i].lon / 60.;
                       newPoly->points[np].lat = (double)dataBuf[i].lat / 60.;
                       newPoly->points[np].level = 100;
                       np++;
               } else {
                       newPoly->points = (MAP_POINT *)realloc(newPoly->points,
                               sizeof(MAP_POINT) * (np + 1));
                       newPoly->points[np].lon = (double)dataBuf[i].lon / 60.;
                       newPoly->points[np].lat = (double)dataBuf[i].lat / 60.;
                       newPoly->points[np].level = (char)dataBuf[i].code;
                       np++;
               }
       }

       if (np) {
               newPoly->nPoints = np;
               if (! v->mapData)
                       v->mapData = newPoly;
               if (v->prevPoly)
                       v->prevPoly->next = newPoly;
               v->prevPoly = newPoly;
       }

       close(fd);
       free(dataBuf);
       return TRUE;
}


BOOL ReadMaps(VARS *v)
{
       char *names[] = {"PCOAST", "PISLAND", "PLAKE", "RIVER", "PUSA48",
                       NULL};
       char *name;
       int i;

       for (i = 0; name = names[i]; i++) {
               if (! ReadMap(v, name))
                       return FALSE;
       }
       return TRUE;
}


BOOL DrawOtherGrid(VARS *v, HDC hdc)
{
       HFONT hFnt = NULL;
       HBRUSH hBrush;
       HPEN hPen;
       POINT oldPt;
       POINT pts[800];
       RECT rect;
       int i, gridDeg;
       double lonRad, latRad, x, x2, y;
       char tmp[30];
       double deg, deg2;

       hBrush = SelectObject(hdc, CreateSolidBrush(v->colors[CLR_GRID]));
       hPen = SelectObject(hdc, CreatePen(PS_SOLID, 0, v->colors[CLR_GRID]));

       for (deg = v->minMax.minLon; deg <= v->minMax.maxLon;
                       deg += (double)v->gridDeg) {

               for (deg2 = v->minMax.minLat, i = 0; deg2 <= v->minMax.maxLat;
                               deg2++, i++) {
                       lonRad = deg * D2R;
                       latRad = deg2 * D2R;
                       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                               goto error;
                       pts[i].x = (int)(x * v->scale);
                       pts[i].y = (int)(y * v->scale);
               }
               Polyline(hdc, pts, i);
       }

       for (deg = v->minMax.minLat; deg <= ((v->minMax.maxLat == 89.5) ? 90. : v->minMax.maxLat);
                       deg += (deg == -89.5) ? (double)v->gridDeg - .5: (double)v->gridDeg) {

               for (deg2 = v->minMax.minLon, i = 0; deg2 <= v->minMax.maxLon;
                               deg2++, i++) {
                       lonRad = deg2 * D2R;
                       latRad = (deg == 90.) ? 89.5 * D2R: deg * D2R;
                       if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                               goto error;
                       pts[i].x = (int)(x * v->scale);
                       pts[i].y = (int)(y * v->scale);
               }
               Polyline(hdc, pts, i);
       }

       DeleteObject(SelectObject(hdc, hBrush));
       DeleteObject(SelectObject(hdc, hPen));

       if (! (v->shwFeatures & SHW_DEGREES))
               return TRUE;

       GetClientRect(v->hwndCont, (LPRECT) &rect);
       DPtoLP(hdc, (LPPOINT) &rect, 2);

       hFnt = SelectObject(hdc, EzCreateFont(hdc, "Arial", 65, 0, 0, TRUE));
       hBrush = SelectObject(hdc, CreateSolidBrush(0x00000000));
       hPen = SelectObject(hdc, CreatePen(PS_SOLID, 0, 0x00000000));
       SetBkMode(hdc, TRANSPARENT);

       for (deg = v->minMax.minLon; deg <= v->minMax.maxLon; deg += (double)v->gridDeg) {

               if ((deg == v->minMax.minLon) || (deg == v->minMax.maxLon))
                       continue;

               if (v->ForTrans[v->project](deg * D2R, v->minMax.maxLat * D2R, &x, &y))
                       goto error;

               if ((double)(rect.top / v->scale) < y) {
                       if (v->InvTrans[v->project](x, (double)(rect.top / v->scale),
                                       &lonRad, &latRad))
                               goto error;

                       if (v->ForTrans[v->project](deg * D2R, latRad, &x, &y))
                               goto error;
               }

               sprintf(tmp, "%d", (int)deg);
               TextOut(hdc,
                       (int)(x * v->scale), (int)(y * v->scale), tmp, strlen(tmp));
       }

       for (deg = v->minMax.minLat; deg <= v->minMax.maxLat;
                       deg += (deg == -89.5) ? (double)v->gridDeg - .5: (double)v->gridDeg) {

               if ((deg == v->minMax.minLat) || (deg == v->minMax.maxLat))
                       continue;

               if (v->ForTrans[v->project](v->minMax.minLon * D2R, deg * D2R, &x, &y))
                       goto error;

               if ((double)(rect.left / v->scale) > x) {
                       if (v->InvTrans[v->project]((double)rect.left / v->scale, y,
                                       &lonRad, &latRad))
                               goto error;

                       if (v->ForTrans[v->project](lonRad, deg * D2R, &x, &y))
                               goto error;
               }

               sprintf(tmp, "%d", (int)deg);
               TextOut(hdc, (int)(x * v->scale), (int)(y * v->scale),
                               tmp, strlen(tmp));
       }

       DeleteObject(SelectObject(hdc, hBrush));
       DeleteObject(SelectObject(hdc, hPen));
       DeleteObject(SelectObject(hdc, hFnt));
       return TRUE;

error:
       DeleteObject(SelectObject(hdc, hBrush));
       DeleteObject(SelectObject(hdc, hPen));
       if (hFnt)
               DeleteObject(SelectObject(hdc, hFnt));
       return FALSE;
}


BOOL DrawGrid(VARS *v, HDC hdc)
{
       HFONT hFnt;
       HBRUSH hBrush;
       HPEN hPen;
       POINT oldPt;
       RECT rect;
       char tmp[30];
       double deg;

       if (v->project != PLATE_CARREE)
               return DrawOtherGrid(v, hdc);

       hBrush = SelectObject(hdc, CreateSolidBrush(v->colors[CLR_GRID]));
       hPen = SelectObject(hdc, CreatePen(PS_SOLID, 0, v->colors[CLR_GRID]));

       for (deg = v->minMax.minLon; deg <= v->minMax.maxLon; deg += (double)v->gridDeg) {
               MoveToEx(hdc, (int)(deg * 100.), (int)(v->minMax.maxLat * 100.), &oldPt);
               LineTo(hdc, (int)(deg * 100.), (int)(v->minMax.minLat * 100.));
       }

       for (deg = v->minMax.minLat; deg <= v->minMax.maxLat; deg += (double)v->gridDeg) {
               MoveToEx(hdc, (int)(v->minMax.minLon * 100.), (int)(deg * 100.), &oldPt);
               LineTo(hdc, (int)(v->minMax.maxLon * 100.), (int)(deg * 100.));
       }

       DeleteObject(SelectObject(hdc, hBrush));
       DeleteObject(SelectObject(hdc, hPen));

       if (! (v->shwFeatures & SHW_DEGREES))
               return TRUE;

       GetClientRect(v->hwndCont, (LPRECT) &rect);
       DPtoLP(hdc, (LPPOINT) &rect, 2);

       hFnt = SelectObject(hdc, EzCreateFont(hdc, "Arial", 65, 0, 0, TRUE));
       hBrush = SelectObject(hdc, CreateSolidBrush(0x00000000));
       hPen = SelectObject(hdc, CreatePen(PS_SOLID, 0, 0x00000000));
       SetBkMode(hdc, TRANSPARENT);

       for (deg = v->minMax.minLon; deg <= v->minMax.maxLon; deg += (double)v->gridDeg) {
               if ((deg == v->minMax.minLon) || (deg == v->minMax.maxLon))
                       continue;
               sprintf(tmp, "%d", (int)deg);
               TextOut(hdc, (int)(deg * 100.),
                               (rect.top <= (int)(v->minMax.maxLat * 100.)) ? rect.top :
                                               (int)(v->minMax.maxLat * 100.),
                               tmp, strlen(tmp));
       }

       for (deg = v->minMax.minLat; deg <= v->minMax.maxLat; deg += (double)v->gridDeg) {
               if ((deg == v->minMax.minLat) || (deg == v->minMax.maxLat))
                       continue;
               sprintf(tmp, "%d", (int)deg);
               TextOut(hdc,
                               (rect.left >= (int)(v->minMax.minLon * 100.)) ? rect.left :
                                               (int)(v->minMax.minLon * 100.),
                               (int)(deg * 100.), tmp, strlen(tmp));
       }

       DeleteObject(SelectObject(hdc, hBrush));
       DeleteObject(SelectObject(hdc, hPen));
       DeleteObject(SelectObject(hdc, hFnt));
       return TRUE;
}


BOOL DoDrawMap(VARS *v, HDC hdc)
{
       MAP_POLY *mapPoly;
       POINT *pts;
       RECT rect;
       HBRUSH hBrush;
       HPEN hPen = NULL;
       COLORREF color;
       int i, np;
       double lonRad, latRad, x, y;
       double lastLon = 0.;

       ShowStatus(v->hwndStatus, 0, "Processing...");

       GetClientRect(v->hwndCont, &rect);

       hBrush = CreateSolidBrush(v->colors[CLR_BACKGR]);
       FillRect (hdc, &rect, hBrush);
       DeleteObject(hBrush);

       if (! SetupWorld(v, hdc))
               goto error;

       for (mapPoly = v->mapData; mapPoly; mapPoly = mapPoly->next) {
               switch (mapPoly->feature) {
                       case FEAT_COAST:
                               if (! (v->shwFeatures & SHW_COAST))
                                       continue;
                               color = v->colors[CLR_COAST];
                               break;

                       case FEAT_COUNTRY:
                               if (! (v->shwFeatures & SHW_COUNTRY))
                                       continue;
                               color = v->colors[CLR_COUNTRY];
                               break;

                       case FEAT_STATE:
                               if (! (v->shwFeatures & SHW_STATE))
                                       continue;
                               color = v->colors[CLR_STATE];
                               break;

                       case FEAT_ISLAND:
                               if (! (v->shwFeatures & SHW_ISLAND))
                                       continue;
                               color = v->colors[CLR_ISLAND];
                               break;

                       case FEAT_LAKE:
                               if (! (v->shwFeatures & SHW_LAKE))
                                       continue;
                               color = v->colors[CLR_LAKE];
                               break;

                       case FEAT_RIVER:
                               if (! (v->shwFeatures & SHW_RIVER))
                                       continue;
                               color = v->colors[CLR_RIVER];
                               break;

                       default:
                               exit(1);
                               continue;
               }

               pts = (POINT *)malloc(sizeof(POINT) * (int)mapPoly->nPoints);

               hBrush = SelectObject(hdc, CreateSolidBrush(color));
               hPen = SelectObject(hdc, CreatePen(PS_SOLID, 0, color));

               np = 0;
               for (i = 0; i < (int)mapPoly->nPoints; i++) {

                       if (mapPoly->points[i].level < 1)
                               continue;

                       if ((mapPoly->points[i].lon > v->minMax.maxLon) ||
                                       (mapPoly->points[i].lon < v->minMax.minLon) ||
                                       (mapPoly->points[i].lat > v->minMax.maxLat) ||
                                       (mapPoly->points[i].lat < v->minMax.minLat)) {
                               if (np) {
                                       if (np == 1)
                                               SetPixel(hdc, pts[0].x, pts[0].y, color);
                                       else
                                               Polyline(hdc, pts, np);
                                       np = 0;
                               }
                               continue;
                       }

                       if (fabs(lastLon - mapPoly->points[i].lon) > 180.) {
                               if (np) {
                                       if (np == 1)
                                               SetPixel(hdc, pts[0].x, pts[0].y, color);
                                       else
                                               Polyline(hdc, pts, np);
                                       np = 0;
                               }
                       }

                       if (np == 16000) {
                               Polyline(hdc, pts, 16000);
                               np = 0;
                       }

                       if (v->project == PLATE_CARREE) {
                               pts[np].x = (int)(mapPoly->points[i].lon * 100.);
                               pts[np].y = (int)(mapPoly->points[i].lat * 100.);
                       } else {
                               lonRad = mapPoly->points[i].lon * D2R;
                               latRad = mapPoly->points[i].lat * D2R;
                               if (v->ForTrans[v->project](lonRad, latRad, &x, &y))
                                       goto error;
                               pts[np].x = (int)(x * v->scale);
                               pts[np].y = (int)(y * v->scale);
                       }

                       np++;
                       lastLon = mapPoly->points[i].lon;
               }

               if (np) {
                       if (np == 1)
                               SetPixel(hdc, pts[0].x, pts[0].y, color);
                       else
                               Polyline(hdc, pts, np);
               }

               DeleteObject(SelectObject(hdc, hBrush));
               DeleteObject(SelectObject(hdc, hPen));
               hPen = NULL;
               free(pts);
               pts = NULL;
       }

       if (v->shwFeatures & SHW_GRID) {
               if (! DrawGrid(v, hdc))
                       goto error;
       }

       ShowStatus(v->hwndStatus, 0, "Done");
       return TRUE;

error:
       if (pts)
               free(pts);
       if (hPen) {
               DeleteObject(SelectObject(hdc, hBrush));
               DeleteObject(SelectObject(hdc, hPen));
       }
       ShowStatus(v->hwndStatus, 0, "");
       return FALSE;
}


void DrawMap(VARS *v, HDC hdc)
{
       if (v->wdbIIMode)
               DrawWDBIIMaps(v, hdc);
       else
               DoDrawMap(v, hdc);
}