const char * UsageLines [] = {
       "Usage: llhist (latitude increment) (longitude increment)",
       "Displays a quick-and-dirty ascii \"map\" (actually more like a",
       "spacial histogram) intended for viewing on fixed-width terminals",
       "through 'less -S'.",
       "Each input line must begin with (latitude),(longitude).",
       "N and E are +, S and W are -.",
       "If followed by a space, the rest of the input line is ignored.",
       "Latitude, longitude, and increment are in degrees.",
       "Each location from the input is grouped to the nearest spot whose",
       "latitude and longitude are multiples of the increment.",
       "North=up.  Each cell shows how many inputs entries are",
       "at that location:",
       "\t(space) means no occurrences,",
       "\t* means more than 9 occurrences.",
       "Compiled with support for up to 1201x1201 map.  If input range exceeds",
       "this, terminates with \"increment too small for data\" message.",
       "March 10, 2013.",
       "The latest version can be found at: gopher -p users/julianbr sdf.org",
       "or: gopher://sdf.org/1/users/julianbr",
       };
const int NumUsageLines = sizeof (UsageLines)/sizeof (UsageLines [0] );

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


void WriteLatitudeLabel (
               long int latitude,
               int numDecimalPlaces)
       {
       unsigned long int i, j;
       int k;

       if (latitude < 0)
               i= - latitude;
       else
               i = latitude;
       j = 1;
       for (k = 0; k < numDecimalPlaces; k++)
               j *= 10;
       putchar ('0' + i/j/10%10);
       putchar ('0' + i/j%10);
       if (numDecimalPlaces > 0)
               putchar ('.');
       for (k = 0; k < numDecimalPlaces; k++) {
               j /= 10;
               putchar ('0' + i/j%10);
               }
       }


void WriteLongitudeLabel (
               long int longitude,
               int position,
               int numDecimalPlaces)
       {
       unsigned long int i, j;
       int k;

       if (longitude < 0)
               i = - longitude;
       else
               i = longitude;
       j = 1;
       for (k = 0; k < numDecimalPlaces; k++)
               j *= 10;
       if (position == 0)
               putchar ('0' + i/j/100%10);
       else if (position == 1)
               putchar ('0' + i/j/10%10);
       else if (position == 2)
               putchar ('0' + i/j%10);
       else if (numDecimalPlaces > 0 && position == 3)
               putchar ('.');
       else if (numDecimalPlaces > 0 && position < numDecimalPlaces + 5) {
               for (k = 3; k < position; k++)
                       j /= 10;
               putchar ('0' + i/j%10);
               }
       }


int ReadLocation (
               long int * latitudePtr,
               long int * longitudePtr,
               int * endOfInputPtr,
               int LatitudeNumDecimalPlaces,
               int LatitudeIncrement,
               int LongitudeNumDecimalPlaces,
               int LongitudeIncrement)
       {
       char sign;
       int decimalPosition, LatitudeAndLongitudeOk, c;
       int foundLatitude, foundLongitude;

       LatitudeAndLongitudeOk = 1;
       c = getchar ();
       foundLatitude = 0;
       sign = '+';
       if (c == '-' || c == '+') {
               sign = c;
               c = getchar ();
               }
       latitudePtr [0] = 0;
       while (c >= '0' && c <= '9') {
               foundLatitude = 1;
               latitudePtr [0] = 10*latitudePtr [0] + (c - '0');
               c = getchar ();
               }
       decimalPosition = 0;
       if (c == '.') {
               c = getchar ();
               while (c >= '0' && c <= '9') {
                       foundLatitude = 1;
                       if (decimalPosition == LatitudeNumDecimalPlaces
                                       && LatitudeIncrement%2 == 1) {
                               if (c > '4')
                                       latitudePtr [0]++;
                               }
                       else if (decimalPosition < LatitudeNumDecimalPlaces)
                               latitudePtr [0]
                                       = 10*latitudePtr [0] + (c - '0');
                       decimalPosition++;
                       c = getchar ();
                       }
               }
       while (decimalPosition < LatitudeNumDecimalPlaces) {
               latitudePtr [0] = 10*latitudePtr [0];
               decimalPosition++;
               }
       if (sign == '-')
               latitudePtr [0] = - latitudePtr [0];
       if (c == ',')
               c = getchar ();
       else
               LatitudeAndLongitudeOk = 0;
       foundLongitude = 0;
       sign = '+';
       if (c == '-' || c == '+') {
               sign = c;
               c = getchar ();
               }
       longitudePtr [0] = 0;
       while (c >= '0' && c <= '9') {
               foundLongitude = 1;
               longitudePtr [0] = 10*longitudePtr [0] + (c - '0');
               c = getchar ();
               }
       decimalPosition = 0;
       if (c == '.') {
               c = getchar ();
               while (c >= '0' && c <= '9') {
                       foundLongitude = 1;
                       if (decimalPosition == LongitudeNumDecimalPlaces
                                       && LongitudeIncrement%2 == 1) {
                               if (c > '4')
                                       longitudePtr [0]++;
                               }
                       else if (decimalPosition < LongitudeNumDecimalPlaces)
                               longitudePtr [0]
                                       = 10*longitudePtr [0] + (c - '0');
                       decimalPosition++;
                       c = getchar ();
                       }
               }
       while (decimalPosition < LongitudeNumDecimalPlaces) {
               longitudePtr [0] = 10*longitudePtr [0];
               decimalPosition++;
               }
       if (sign == '-')
               longitudePtr [0] = - longitudePtr [0];
       if (c != EOF && c != '\n' && c != ' ') {
               fprintf (stderr, "***llhist: Improper '%c'.\n", c);
               LatitudeAndLongitudeOk = 0;
               }
       else if (c != EOF && !LatitudeAndLongitudeOk)
               fprintf (stderr, "***llhist: Comma not found.\n");
       while (c != EOF && c != '\n')
               c = getchar ();
       if (c == EOF)
               endOfInputPtr [0] = 1;
       if (LatitudeAndLongitudeOk) {
               if (!foundLatitude) {
                       fprintf (stderr, "***llhist:");
                       fprintf (stderr, " Latitude not found.\n");
                       LatitudeAndLongitudeOk = 0;
                       }
               if (!foundLongitude) {
                       fprintf (stderr, "***llhist:");
                       fprintf (stderr, " Longitude not found.\n");
                       LatitudeAndLongitudeOk = 0;
                       }
               }
       return LatitudeAndLongitudeOk;
       }


struct Map {
       int width, height;
       char (locations [1201] ) [1201];
       };
/* 3601 x 1801 is just enough for the world at .1 degree increment */


void ExpandMapWest (
               struct Map * Map,
               long int * minLongitudePtr,
               long int longitude,
               int increment)
       {
       unsigned long int westAdjustment, newWidth;
       int i;

       if (longitude >= minLongitudePtr [0] )
               return;
       westAdjustment = (minLongitudePtr [0] - longitude)/increment;
       newWidth = westAdjustment + Map->width;
       if (newWidth > sizeof (Map->locations [0] ) )
               return;
       minLongitudePtr [0] = longitude;
       for (i = 0; i < Map->height; i++) {
               memmove (Map->locations [i] + westAdjustment,
                               Map->locations [i],
                               Map->width);
               memset (Map->locations [i], ' ', westAdjustment);
               }
       Map->width = newWidth;
       }


void ExpandMapNorth (
               struct Map * Map,
               long int * maxLatitudePtr,
               long int latitude,
               int increment)
       {
       unsigned long int northAdjustment, newHeight;
       int i;

       if (latitude <= maxLatitudePtr [0] )
               return;
       northAdjustment = (latitude - maxLatitudePtr [0] )/increment;
       newHeight = Map->height + northAdjustment;
       if (newHeight > sizeof (Map->locations)/sizeof (Map->locations [0] ) )
               return;
       maxLatitudePtr [0] = latitude;
       for (i = Map->height - 1; i >= 0; i--)
               memcpy (Map->locations [i + northAdjustment],
                               Map->locations [i],
                               Map->width);
       for (i = 0; i < northAdjustment; i++)
               memset (Map->locations [i], ' ', Map->width);
       Map->height = newHeight;
       }


void ExpandMapEast (
               struct Map * Map,
               long int minLongitude,
               long int longitude,
               int increment)
       {
       unsigned long int eastAdjustment, newWidth;
       int i;

       if (longitude <= minLongitude)
               return;
       newWidth = (longitude - minLongitude)/increment + 1;
       if (newWidth <= Map->width)
               return;
       if (newWidth > sizeof (Map->locations [0] ) )
               return;
       eastAdjustment = newWidth - Map->width;
       for (i = 0; i < Map->height; i++)
               memset (Map->locations [i] + Map->width, ' ', eastAdjustment);
       Map->width = newWidth;
       }


void ExpandMapSouth (
               struct Map * Map,
               long int maxLatitude,
               long int latitude,
               int increment)
       {
       unsigned long int southAdjustment, newHeight;
       int i;

       if (latitude >= maxLatitude)
               return;
       newHeight = (maxLatitude - latitude)/increment + 1;
       if (newHeight <= Map->height)
               return;
       if (newHeight > sizeof (Map->locations)/sizeof (Map->locations [0] ) )
               return;
       southAdjustment = newHeight - Map->height;
       for (i = 0; i < southAdjustment; i++)
               memset (Map->locations [Map->height + i], ' ',
                               Map->width);
       Map->height = newHeight;
       }


int ReadMap (
               struct Map * Map,
               long int * maxLatitudePtr,
               int LatitudeNumDecimalPlaces,
               int LatitudeIncrement,
               long int * minLongitudePtr,
               int LongitudeNumDecimalPlaces,
               int LongitudeIncrement)
       {
       unsigned long int lineCtr;
       long int latitude, longitude;
       int h, v;
       int endOfInput, ok;

       Map->width = 0;
       Map->height = 0;
       lineCtr = 0;
       ok = 1;
       endOfInput = 0;
       while (!endOfInput) {
               lineCtr++;
               if (ReadLocation (
                               & latitude,
                               & longitude,
                               & endOfInput,
                               LatitudeNumDecimalPlaces,
                               LatitudeIncrement,
                               LongitudeNumDecimalPlaces,
                               LongitudeIncrement) ) {
                       if (latitude < 0)
                               latitude = - (- latitude
                                               + LatitudeIncrement/2)
                                               /LatitudeIncrement
                                               *LatitudeIncrement;
                       else
                               latitude = (latitude
                                               + LatitudeIncrement/2)
                                               /LatitudeIncrement
                                               *LatitudeIncrement;
                       if (longitude < 0)
                               longitude = - (- longitude
                                               + LongitudeIncrement/2)
                                               /LongitudeIncrement
                                               *LongitudeIncrement;
                       else
                               longitude = (longitude
                                               + LongitudeIncrement/2)
                                               /LongitudeIncrement
                                               *LongitudeIncrement;
                       if (Map->width == 0 || Map->height == 0) {
                               Map->width = 1;
                               Map->height = 1;
                               minLongitudePtr [0] = longitude;
                               maxLatitudePtr [0] = latitude;
                               Map->locations [0] [0] = ' ';
                               }
                       ExpandMapWest (
                                       Map,
                                       minLongitudePtr,
                                       longitude,
                                       LongitudeIncrement);
                       ExpandMapNorth (
                                       Map,
                                       maxLatitudePtr,
                                       latitude,
                                       LatitudeIncrement);
                       ExpandMapEast (
                                       Map,
                                       minLongitudePtr [0],
                                       longitude,
                                       LongitudeIncrement);
                       ExpandMapSouth (
                                       Map,
                                       maxLatitudePtr [0],
                                       latitude,
                                       LatitudeIncrement);
                       v = (maxLatitudePtr [0] - latitude)
                                       /LatitudeIncrement;
                       h = (longitude - minLongitudePtr [0] )
                                       /LongitudeIncrement;
                       if (v >= 0 && v < Map->height
                                       && h >= 0 && h < Map->width) {
                               if (Map->locations [v] [h] == ' ')
                                       Map->locations [v] [h] = '1';
                               else if (Map->locations [v] [h] > '0'
                                       && Map->locations [v] [h] < '9')
                                       Map->locations [v] [h]++;
                               else
                                       Map->locations [v] [h] = '*';
                               }
                       else if (ok) {
                               fprintf (stderr, "***llhist: Increment too");
                               fprintf (stderr, " small for input data.\n");
                               ok = 0;
                               }
                       }
               else if (!endOfInput) {
                       fprintf (stderr, "***llhist:  Improper");
                       fprintf (stderr, " input line %lu\n", lineCtr);
                       ok = 0;
                       }
               }
       return ok;
       }


void WriteMap (
               struct Map * Map,
               long int maxLatitude,
               int LatitudeNumDecimalPlaces,
               int LatitudeIncrement,
               long int minLongitude,
               int LongitudeNumDecimalPlaces,
               int LongitudeIncrement)
       {
       long int latitude, longitude;
       int h, v;
       int i, j, latitudeLength, longitudeLength;

       latitudeLength = LatitudeNumDecimalPlaces + 2;
       longitudeLength = LongitudeNumDecimalPlaces + 3;
       if (LatitudeNumDecimalPlaces > 0)
               latitudeLength++;
       if (LongitudeNumDecimalPlaces > 0)
               longitudeLength++;
       for (i = 0; i < longitudeLength; i++) {
               for (j = 0; j < latitudeLength; j++)
                       putchar (' ');
               putchar (' ');
               longitude = minLongitude;
               for (h = 0; h < Map->width; h++) {
                       WriteLongitudeLabel (
                                       longitude,
                                       i,
                                       LongitudeNumDecimalPlaces);
                       longitude += LongitudeIncrement;
                       }
               putchar (' ');
               for (j = 0; j < latitudeLength; j++)
                       putchar (' ');
               putchar ('\n');
               }
       for (j = 0; j < latitudeLength; j++)
               putchar (' ');
       putchar (' ');
       longitude = minLongitude;
       for (h = 0; h < Map->width; h++) {
               if (longitude < 0)
                       putchar ('W');
               else
                       putchar ('E');
               longitude += LongitudeIncrement;
               }
       putchar (' ');
       for (j = 0; j < latitudeLength; j++)
               putchar (' ');
       putchar ('\n');
       latitude = maxLatitude;
       for (v = 0; v < Map->height; v++) {
               WriteLatitudeLabel (
                               latitude,
                               LatitudeNumDecimalPlaces);
               if (latitude < 0)
                       putchar ('S');
               else
                       putchar ('N');
               fwrite (Map->locations [v], Map->width, 1, stdout);
               if (latitude < 0)
                       putchar ('S');
               else
                       putchar ('N');
               WriteLatitudeLabel (
                               latitude,
                               LatitudeNumDecimalPlaces);
               putchar ('\n');
               latitude -= LatitudeIncrement;
               }
       for (j = 0; j < latitudeLength; j++)
               putchar (' ');
       putchar (' ');
       for (h = 0; h < Map->width; h++) {
               if (longitude < 0)
                       putchar ('W');
               else
                       putchar ('E');
               longitude += LongitudeIncrement;
               }
       putchar (' ');
       for (j = 0; j < latitudeLength; j++)
               putchar (' ');
       putchar ('\n');
       for (i = 0; i < longitudeLength; i++) {
               for (j = 0; j < latitudeLength; j++)
                       putchar (' ');
               putchar (' ');
               longitude = minLongitude;
               for (h = 0; h < Map->width; h++) {
                       WriteLongitudeLabel (
                                       longitude,
                                       i,
                                       LongitudeNumDecimalPlaces);
                       longitude += LongitudeIncrement;
                       }
               putchar (' ');
               for (j = 0; j < latitudeLength; j++)
                       putchar (' ');
               putchar ('\n');
               }
       }


int FindIncrement (
               int * numDecimalPlacesPtr,
               int * incrementPtr,
               char * increment)
       {
       char * ptr;

       numDecimalPlacesPtr [0] = 0;
       incrementPtr [0] = 0;
       ptr = increment;
       while (ptr [0] >= '0' && ptr [0] <= '9') {
               incrementPtr [0] = 10*incrementPtr [0] + (ptr [0] - '0');
               ptr++;
               }
       if (ptr [0] == '.') {
               ptr++;
               while (ptr [0] >= '0' && ptr [0] <= '9') {
                       numDecimalPlacesPtr [0]++;
                       incrementPtr [0]
                               = 10*incrementPtr [0] + (ptr [0] - '0');
                       ptr++;
                       }
               }
       if (ptr [0] != '\0')
               return 0;
       if (incrementPtr [0] == 0)
               return 0;
       return 1;
       }


int main (int argc, char * argv [] )
       {
       struct Map Map;
       long int MaxLatitude, MinLongitude;
       int LatitudeNumDecimalPlaces, LongitudeNumDecimalPlaces;
       int LatitudeIncrement, LongitudeIncrement;
       int i;

       if (argc < 2) {
               for (i = 0; i < NumUsageLines; i++)
                       printf ("%s\n", UsageLines [i] );
               }
       else if (argc == 3
                       && FindIncrement (
                               & LatitudeNumDecimalPlaces,
                               & LatitudeIncrement,
                               argv [1] )
                       && FindIncrement (
                               & LongitudeNumDecimalPlaces,
                               & LongitudeIncrement,
                               argv [2] ) ) {
               if (ReadMap (
                                       & Map,
                                       & MaxLatitude,
                                       LatitudeNumDecimalPlaces,
                                       LatitudeIncrement,
                                       & MinLongitude,
                                       LongitudeNumDecimalPlaces,
                                       LongitudeIncrement) )
                       WriteMap (
                                       & Map,
                                       MaxLatitude,
                                       LatitudeNumDecimalPlaces,
                                       LatitudeIncrement,
                                       MinLongitude,
                                       LongitudeNumDecimalPlaces,
                                       LongitudeIncrement);
               }
       else {
               fprintf (stderr, "***Usage: llhist");
               fprintf (stderr, " (latitude increment)");
               fprintf (stderr, " (longitude increment)\n");
               }
       return 0;
       }