#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <winlib/winlib.h>

#include "gshhs.h"

#define LEVEL_LAND                                              1
#define LEVEL_LAKE                                              2
#define LEVEL_ISLAND_IN_LAKE                    3
#define LEVEL_POND_IN_ISLAND_IN_LAKE    4

typedef struct {
       double lon;
       double lat;
} MAP_POINT;

typedef struct mapPoly {
       char level;
       unsigned short nPoints;
       MAP_POINT *points;
       struct mapPoly *next;
} MAP_POLY;

MAP_POLY *mapData = NULL;

HINSTANCE hInst;
HWND hwndStatus;
int statusHeight;


BOOL ReadMapData(void)
{
       FILE *f;
       double w, e, s, n, area, lon, lat;
       char source;
       int     i;
       int max_east = 270000000;
       struct GSHHS h;
       struct GSHHS_POINT p;
       MAP_POLY *newPoly, *prevPoly = NULL;

       if (! (f = fopen("d:/Graphics/GIS/GSHHS/gshhs_l.b", "rb"))) {
               MessageBox(NULL, "Missing map data file", "DrawMap",
                               MB_OK | MB_ICONERROR);
               return FALSE;
       }

       while (fread((void *)&h, (size_t)sizeof (struct GSHHS), (size_t)1, f) == 1) {
               h.id = swabi4 ((unsigned int)h.id);
               h.n = swabi4 ((unsigned int)h.n);
               h.level = swabi4 ((unsigned int)h.level);
               h.west = swabi4 ((unsigned int)h.west);
               h.east = swabi4 ((unsigned int)h.east);
               h.south = swabi4 ((unsigned int)h.south);
               h.north = swabi4 ((unsigned int)h.north);
               h.area = swabi4 ((unsigned int)h.area);
               h.greenwich = swabi2 ((unsigned int)h.greenwich);
               h.source = swabi2 ((unsigned int)h.source);

               newPoly = (MAP_POLY *)malloc(sizeof(MAP_POLY));
               newPoly->level = (char)h.level;
               newPoly->nPoints = (unsigned short)h.n;
               newPoly->points = (MAP_POINT *)malloc(sizeof(MAP_POINT) * h.n);
               newPoly->next = NULL;

               w = h.west  * 1.0e-6;
               e = h.east  * 1.0e-6;
               s = h.south * 1.0e-6;
               n = h.north * 1.0e-6;
               source = (h.source == 1) ? 'W' : 'C';
               area = 0.1 * h.area;

               for (i = 0; i < h.n; i++) {
                       if (fread ((void *)&p, (size_t)sizeof(struct GSHHS_POINT),
                                       (size_t)1, f) != 1) {
                               MBPrintf("DrawMap", "Error reading file for polygon %d, point %d.\n", h.id, i);
                               return FALSE;
                       }

                       p.x = swabi4 ((unsigned int)p.x);
                       p.y = swabi4 ((unsigned int)p.y);

                       newPoly->points[i].lon = (h.greenwich && p.x > max_east) ?
                                       p.x * 1.0e-6 - 360.0 : p.x * 1.0e-6;

                       newPoly->points[i].lat = p.y * 1.0e-6;
               }

               if (! mapData)
                       mapData = newPoly;
               if (prevPoly)
                       prevPoly->next = newPoly;
               prevPoly = newPoly;

               max_east = 180000000;
       }

       fclose(f);
       return TRUE;
}


BOOL DrawMap(HWND hwnd, HDC hdc)
{
       MAP_POLY *mapPoly;
       POINT *dPoints;
       RECT rect;
       HBRUSH hBrush;
       HPEN hPen;
       double xScaleFact, yScaleFact;
       int i;
       BOOL retVal = TRUE;
       COLORREF color;

       ShowStatus(hwndStatus, 0, "Processing...");

       GetClientRect(hwnd, &rect);

       xScaleFact = (double)rect.right / 360.0;
       yScaleFact = (300.0 * ((double)rect.right / 600.0)) / 180.0;

       rect.bottom -= statusHeight;
       hBrush = CreateSolidBrush(0x00ffd0d0);
       FillRect(hdc, &rect, hBrush);
       DeleteObject(hBrush);

       for (mapPoly = mapData; mapPoly; mapPoly = mapPoly->next) {
               dPoints = (POINT *)malloc(sizeof(POINT) * (int)(mapPoly->nPoints));

               for (i = 0; i < (int)mapPoly->nPoints; i++) {
                       dPoints[i].x = (mapPoly->points[i].lon * xScaleFact);
                       dPoints[i].y = (rect.bottom / 2) -
                                       (mapPoly->points[i].lat * yScaleFact);
               }

               switch (mapPoly->level) {
                       case LEVEL_LAND:
                               color = 0x00209020;
                               break;

                       case LEVEL_LAKE:
                               color = 0x00b00000;
                               break;

                       case LEVEL_ISLAND_IN_LAKE:
                               color = 0x00209020;
                               break;

                       case LEVEL_POND_IN_ISLAND_IN_LAKE:
                               color = 0x00b00000;
                               break;
               }

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

               if (! Polygon(hdc, dPoints, (int)mapPoly->nPoints)) {
                       DeleteObject(SelectObject(hdc, hBrush));
                       DeleteObject(SelectObject(hdc, hPen));
                       free(dPoints);
                       MBPrintf("DrawMap", "Polygon overflow: %d points",
                                       mapPoly->nPoints);
                       retVal = FALSE;
                       goto end;
               }

               DeleteObject(SelectObject(hdc, hBrush));
               DeleteObject(SelectObject(hdc, hPen));
               free(dPoints);
       }

end:
       ShowStatus(hwndStatus, 0, "Done");
       return retVal;
}


LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam,
               LPARAM lParam)
{
       HDC hdc;
       PAINTSTRUCT ps;
       RECT rect;
       BOOL r;
       int x, y;

       switch (msg) {
               case WM_CREATE:
                       hwndStatus = CreateStatusWnd(hInst, hwnd, 2);
                       GetWindowRect(hwndStatus, &rect);
                       statusHeight = rect.bottom - rect.top;

                       x = (int)(((LPCREATESTRUCT)lParam)->x);
                       y = (int)(((LPCREATESTRUCT)lParam)->y);
                       MoveWindow(hwnd, x, y,
                                       600 + (GetSystemMetrics(SM_CXSIZEFRAME) * 2),
                                       300 + statusHeight + GetSystemMetrics(SM_CYCAPTION) +
                                               (GetSystemMetrics(SM_CYSIZEFRAME) * 2),
                                       TRUE);
                       return 0;

               case WM_SIZE:
                       SendMessage(hwndStatus, WM_SIZE, wParam, lParam);
                       SetStatusParts(hwndStatus, 2);
                       return 0;

               case WM_PAINT:
                       hdc = BeginPaint(hwnd, &ps);
                       r = DrawMap(hwnd, hdc);
                       EndPaint(hwnd, &ps);
                       if (! r)
                               exit(1);
                       return 0;

               case WM_DESTROY:
                       PostQuitMessage(0);
                       return 0;
       }
       return DefWindowProc(hwnd,msg,wParam,lParam);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
               LPSTR lpCmdLine, INT nCmdShow)
{
       WNDCLASS wc;
       HWND hwnd;
       MSG msg;

       if (! ReadMapData())
               return 0;

       hInst = hInstance;

       wc.style = CS_HREDRAW | CS_VREDRAW;
       wc.lpfnWndProc = (WNDPROC)MainWndProc;
       wc.cbClsExtra = 0;
       wc.cbWndExtra = 0;
       wc.hInstance = hInstance;
       wc.hIcon = LoadIcon(hInstance, IDI_WINLOGO);
       wc.hCursor = LoadCursor(NULL, IDC_ARROW);
       wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
       wc.lpszMenuName = NULL;
       wc.lpszClassName = "MainWndClass";
       if (! RegisterClass(&wc))
               return 0;

       hwnd = CreateWindow(
               "MainWndClass",
               "DrawMap",
               WS_OVERLAPPEDWINDOW,
               CW_USEDEFAULT, 0, 600, 340,
               NULL, NULL, hInstance, NULL);

       ShowWindow(hwnd, nCmdShow);

       while (GetMessage (&msg, NULL, 0, 0)) {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
       }

       return msg.wParam;
}