#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#define _GNU_SOURCE
#include <string.h>
#include <strings.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/IntrinsicI.h>
#include <lirc/lirc_client.h>
#include <pthread.h>

#include "timezone_map.h"

Display *display;
Window window;
GC gc;
int screen;
int selected_zone;
int selected_place;
Pixmap pixmap;
XColor ocean;
XColor land[8];
XColor border;
XColor selected_border;
XColor selected_land;
XColor name_colour;
XColor location_dot;
XColor lat_lon;
XColor tux_yellow;
Dimension screen_width = 1920;
Dimension screen_height = 1080;
Dimension width = 1920;
Dimension height = 1080;
Position x = 0;
Position y = 0;
XFontStruct *font;
XPoint xpoints[1000];
int radius;
double aspect_correction = 1;

double target_lat;
double target_lon;

double displayed_lat;
double displayed_lon;

double lon_rotate[2] = {1, 0};
double lat_rotate[2] = {1, 0};

double acceleration = 0.01;
char input_keys[200];

int guessing_timezone = FALSE;
time_t guess_timeout = 0;
int guess_failed = FALSE;
time_t guess_failed_timeout = 0;
int show_information = FALSE;
int info_displayed = FALSE;

void select_place (int place)
{
  target_lon = atan2 (zone_data[selected_zone].place_info[place].v.y,
               zone_data[selected_zone].place_info[place].v.x);
  target_lat = acos (zone_data[selected_zone].place_info[place].v.z /2);

  selected_place = place;
}

void default_zone (char *arg_zone)
{
  int found_zone = FALSE;

  for (int zone = 0; zone < NUM_ZONES; zone++)
  {
    for (int place = 0; place < zone_data[zone].num_places; place++)
    {
      if (strstr (zone_data[zone].place_info[place].zonename, 
                  arg_zone[0] ? arg_zone : "Los_Angeles"))
      {
        selected_zone = zone;
        select_place (place);
        found_zone = TRUE;
      }
    }
  }

  if (!found_zone)
  {
    default_zone ("");
  }
}

int translate_point (vect_type *v, XPoint *p)
{
  vect_type v2;
  vect_type v3;

  v2.x = v->x * lon_rotate[0] + v->y * lon_rotate[1];
  v2.y = v->x * -1.0 * lon_rotate[1] + v->y * lon_rotate[0];
  v2.z = v->z;
  
  v3.x = v2.x * lat_rotate[0] + v2.z * lat_rotate[1];
  v3.y = v2.y * radius; 
  v3.z = v2.x * -1.0 * lat_rotate[1] + v2.z * lat_rotate[0];
    

  // If the point is behind the face of the globe, project it to the edge.
  if (v3.x < 0)
  {
    p->x = width /2 + radius * sin (atan2 (v3.y,v3.z)) * aspect_correction;
    p->y = height/2 - radius * cos (atan2 (v3.y,v3.z));
  }
  else
  {
    p->x = width /2 + lrint (v3.y) * aspect_correction;
    p->y = height/2 - lrint (v3.z);
  }
  
  // Return a value indicating if it is on the front of the world.
  return (v3.x >= 0);
}

void draw_zone (
      Drawable d,
      int zone,
      int fill,
      int selected)
{
  int boundary = 0;
  int num_points = 0;
  int all_on_back = 1;

  if (selected && fill)
    XSetForeground (display, gc, selected_land.pixel);

  while (boundary < zone_data[zone].num_boundary_points)
  {
    if (zone_data[zone].boundary_points[boundary].x < -9)
    {      

      if ((num_points > 1) && !all_on_back)
      {
        if (fill)
        {
          if (!selected)
          {
            if (zone == NUM_ZONES - 1)
            {
              if (boundary < 380)
                XSetForeground (display, gc, WhitePixel (display, screen));              
              else if (boundary < 470)
                XSetForeground (display, gc, tux_yellow.pixel);              
              else if (boundary < 1120) 
                XSetForeground (display, gc, BlackPixel (display, screen));              
              else
                XSetForeground (display, gc, WhitePixel (display, screen));              
            }
            else
            {
              XSetForeground (display, gc, 
                land[boundary % XtNumber (land)].pixel);
            }
          }

          XFillPolygon (
            display, d, gc, 
            xpoints,
            num_points, Complex, CoordModeOrigin); 
        }
        else
          XDrawLines (
            display, d, gc, 
            xpoints,
            num_points, CoordModeOrigin);
      }
      
      boundary++;
      num_points = 0;
      all_on_back = 1;
    }
    else
    {
      if (translate_point (&zone_data[zone].boundary_points[boundary],
                           &xpoints[num_points]))
        all_on_back = 0;

      num_points++;
      boundary++;
    }
  }
}

void draw_string (int x, int y, const char*s, unsigned long colour)
{
  XSetForeground (display, gc, BlackPixel (display, screen));
  for (int x_pos = x - 3; x_pos <= x + 3; x_pos++)
    for (int y_pos = y - 3; y_pos <= y + 3; y_pos++)
      XDrawString (display, pixmap, gc, x_pos, y_pos, s, strlen (s));

  XSetForeground (display, gc, colour);
  XDrawString (display, pixmap, gc, x, y, s, strlen (s));
}

void draw_point (XPoint p, char *s)
{
  int text_width;

  int dot_size = ceil (sqrt (height) / 6.0);

  if (s) 
    dot_size += 2;
  
  XSetForeground (display, gc, 
                  s ? WhitePixel (display, screen) : location_dot.pixel);
  XFillRectangle (display, pixmap, gc, 
                  p.x - dot_size / 2, p.y - dot_size / 2, dot_size, dot_size);

  XSetForeground (display, gc, BlackPixel (display, screen));
  XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound);
  XDrawRectangle (display, pixmap, gc, 
                  p.x - dot_size / 2, p.y - dot_size / 2, dot_size, dot_size);

  if (s)
  {
    int text_x;
    int text_y;
    
    text_width = XTextWidth (font, s, strlen (s));

    if (p.x + text_width + 30 > width)
    {
      text_x = p.x - text_width - 6;
      text_y = p.y + 10;
    }
    else
    {
      text_x = p.x + 6;
      text_y = p.y + 10;
    }

    draw_string (text_x, text_y, s, name_colour.pixel);
  }
}

void redraw_map ()
{
  XPoint selected_point;
  char selected_name[100];

  lon_rotate[0] = cos (displayed_lon);
  lon_rotate[1] = sin (displayed_lon);

  lat_rotate[1] = cos (displayed_lat) * radius;
  lat_rotate[0] = sin (displayed_lat) * radius;

  /* Draw the map into the pixmap. */

  XSetForeground (display, gc, BlackPixel (display, screen));
  XFillRectangle (display, pixmap, gc, 0, 0, width, height);

  XSetForeground (display, gc, ocean.pixel);
  XFillArc (display, pixmap, gc, 
    width / 2 - radius * aspect_correction, height / 2 - radius,
    radius * 2 * aspect_correction, radius * 2, 0, 360 * 64);

  XSetForeground (display, gc, lat_lon.pixel);
  XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound);
  
  for (int lon = 0; lon < 24; lon++)
    for (int lat = 0; lat < 12 * 3; lat++)
    {
      XPoint line[2];
      vect_type v;
      v.x = cos (lon * 15 * M_PI / 180) * cos ((90 - lat * 5) * M_PI / 180);
      v.y = sin (lon * 15 * M_PI / 180) * cos ((90 - lat * 5) * M_PI / 180);
      v.z = sin ((90 - lat * 5) / 180.0 * M_PI);
      line[0] = line[1];
      if (translate_point (&v, &line[1]) && (lat > 0))
        XDrawLines (display, pixmap, gc, line, 2, CoordModeOrigin);
    }

  for (int lat = 0; lat < 12; lat++)
    for (int lon = 0; lon <= 24 * 3; lon++)
    {
      XPoint line[2];
      vect_type v;
      v.x = cos (lon * 5 * M_PI / 180) * cos ((90 - lat * 15) * M_PI / 180);
      v.y = sin (lon * 5 * M_PI / 180) * cos ((90 - lat * 15) * M_PI / 180);
      v.z = sin ((90 - lat * 15) / 180.0 * M_PI);
      line[0] = line[1];
      if (translate_point (&v, &line[1]) && (lon > 0))
        XDrawLines (display, pixmap, gc, line, 2, CoordModeOrigin);
    }
  
  for (int zone = 0; zone < NUM_ZONES; zone++)
    draw_zone (pixmap, zone, 1, 0);
  
  XSetLineAttributes (display, gc, 2, LineSolid, CapRound, JoinRound);
  XSetForeground (display, gc, border.pixel);
  for (int zone = 0; zone < NUM_ZONES - 1; zone++)
    draw_zone (pixmap, zone, 0, 0);
  
  if (show_information)
  {
    info_displayed = TRUE;
    char *s[4] = {"Left/Right: Change Offset",
                  "Up/Down: Change Location",
                  "Enter: Select Location",
		  "Esc: Abort"};
    int num = XtNumber(s);
    for (int i = 0; i < num; i++)
    {
      int text_width = XTextWidth (font, s[i], strlen (s[i]));
      draw_string (width/2 - text_width/2, 
		   height/2 + (-2 + i) * (font->ascent + font->descent), 
		   s[i], name_colour.pixel);
    }
  }
  else if (guessing_timezone || (info_displayed = FALSE))
  {
    char *s = "Guessing Timezone";
    int text_width = XTextWidth (font, s, strlen (s));
    draw_string (width/2 - text_width/2, height/2, s, name_colour.pixel);
  }
  else if (guess_failed)
  {
    char *s = "Unable to Guess Timezone";
    char *s2 = "Please Select Timezone Manually";
    int text_width = XTextWidth (font, s, strlen (s));
    draw_string (width/2 - text_width/2, height/2, s, name_colour.pixel);
    text_width = XTextWidth (font, s2, strlen (s2));
    draw_string (width/2 - text_width/2, 
                 height/2 + font->ascent + font->descent, 
		 s2, name_colour.pixel);
  }
  else
  {
    draw_zone (pixmap, selected_zone, 1, 1);
    XSetForeground (display, gc, selected_border.pixel);
    draw_zone (pixmap, selected_zone, 0, 1);  

    for (int place = 0; place < zone_data[selected_zone].num_places; place++)
    {
      XPoint point;

      translate_point (&zone_data[selected_zone].place_info[place].v, &point);
    
      draw_point (point, NULL);

      if ((place == selected_place) && (!guessing_timezone))
      {
        char *underscore;
        selected_point = point;
        strcpy (selected_name, strchr (zone_data[selected_zone].place_info[place].zonename, '/') + 1);
        while (underscore = strchr (selected_name, '_'))
          underscore[0] = ' ';
      }
    }

    draw_point (selected_point, selected_name);

    {
      char zone_offset[100];
      sprintf (zone_offset, "UTC%s", zone_data[selected_zone].offset);
      draw_string (
        ((width * 0.95 - XTextWidth (font, "UTC+88", strlen ("UTC+88"))) +
         ((width / 2) + radius / sqrt (2))) / 2, 
        ((lrint (height * 0.05) + font->ascent) +
         (height / 2 - radius / sqrt (2))) / 2, 
        zone_offset, name_colour.pixel);
    }
  }

  if (!guessing_timezone && !guess_failed)
  {
    const char *title = "Select Your Time Zone";
    const char *help = "Help = i";

    draw_string (width / 2 - XTextWidth (font, title, strlen (title)) / 2,
        ((lrint (height * 0.05) + font->ascent) +
         (height / 2 - radius / sqrt (2))) / 2, 
	title, WhitePixel (display,screen));

    draw_string (width * 0.05, height * 0.95 - font->descent - font->ascent, 
		 help, name_colour.pixel);
  }  

  XCopyArea (display, pixmap, window, gc, 0, 0, width, height, 0, 0);
}

int nearest_z (double near_z)
{
  double min_delta = 
    fabs (near_z - zone_data[selected_zone].place_info[0].v.z);
  int min_delta_place = 0;

  for (int place = 1; place < zone_data[selected_zone].num_places; place++)
  {
    double delta = 
      fabs (near_z - zone_data[selected_zone].place_info[place].v.z);

    if (delta < min_delta)
    {
      min_delta_place = place;
      min_delta = delta;
    }
  }

  return min_delta_place;
}

void handle_key (char key)
{
  double selected_z;
  
  selected_z = zone_data[selected_zone].place_info[selected_place].v.z;

  if (show_information)
  {
    show_information = FALSE;
    return;
  }

  switch (key)
  {
    case 'U':
      select_place ((selected_place + 
                     zone_data[selected_zone].num_places - 1) % 
                    zone_data[selected_zone].num_places);            
      break;

    case 'D':
      select_place ((selected_place + 1) % 
                    zone_data[selected_zone].num_places);            
      break;

    case 'L':
      selected_zone = (selected_zone + NUM_ZONES - 1) % NUM_ZONES;
      select_place (nearest_z (selected_z));
      break;

    case 'R':
      selected_zone = (selected_zone + 1) % NUM_ZONES;
      select_place (nearest_z (selected_z));
      break;
            
    case 'I':
      show_information = !show_information;
      break;

    case 'X':
      printf ("%s\n", zone_data[selected_zone].place_info[selected_place].zonename);
      exit (0);

    case 'E':
      exit(1);
  }
}

void handle_event (XEvent *xevent)
{
  switch (xevent->type) 
  {
    case Expose:
      redraw_map ();
      break;

    case KeyPress:
      switch (XLookupKeysym (&xevent->xkey, 0))
      {
        case XK_Up:
          handle_key ('U');
          break;

        case XK_Down:
          handle_key ('D');
          break;

        case XK_Left:
          handle_key ('L');
          break;

        case XK_Right:
          handle_key ('R');
          break;
            
        case XK_Return:
          handle_key ('X');
          break;

	case XK_Escape:
	  handle_key ('E');
	  break;

	case XK_I:
	case XK_i:
	  handle_key ('I');
	  break;
      }

      break;
  }
}

Bool event_predicate (Display *display, XEvent *xevent, XPointer unused)
{
  return (xevent->type == KeyPress) || (xevent->type == Expose);
}

void next_view ()
{
  static double step_size = 0.01;
  double error_total;
  double lat_error = target_lat - displayed_lat;
  double lon_error = target_lon - displayed_lon;

  if (guessing_timezone)
  {
    displayed_lon += 1 / 180.0 * M_PI;
  }
  else
  {
    lat_error = atan2 (sin (lat_error), cos( lat_error));
    lon_error = atan2 (sin (lon_error), cos (lon_error));

    error_total = sqrt (lat_error * lat_error + lon_error * lon_error);
      
    if (error_total < acceleration)
    {
      displayed_lat = target_lat;
      displayed_lon = target_lon;
    }
    else
    {
      if (error_total > (step_size + acceleration) * 
                        (step_size + acceleration) / acceleration / 2)
        step_size += acceleration;
      else
        step_size -= acceleration;
      
      if (step_size < acceleration)
        step_size = acceleration;
    
      displayed_lat += step_size * (lat_error / error_total);
      displayed_lon += step_size * (lon_error / error_total);
    }
  }
}

void *lirc_thread (void *unused)
{
  int lirc_fd;
  struct lirc_config *lirc_config;
  char *lirc_code;

  if ((lirc_fd = lirc_init ("mythtv",0)) == -1)
    fprintf (stderr,"Error initialising lirc\n");
  else 
  {
    int readc_status;

    if (readc_status = lirc_readconfig (NULL, &lirc_config, NULL))
    {
      fprintf (stderr,"Error loading lirc config file %d %p\n", readc_status, lirc_config);
    }
  }

  while ((lirc_nextcode (&lirc_code) == 0) && (lirc_code != NULL))
  {
    char *action;

    while ((lirc_code2char (lirc_config, lirc_code, &action) == 0) &&
           (action != NULL))
    {
      if (strcasecmp (action, "down") == 0)
        strcat (input_keys, "D");

      if (strcasecmp (action, "up") == 0)
        strcat (input_keys, "U");

      if (strcasecmp (action, "left") == 0)
        strcat (input_keys, "L");

      if (strcasecmp (action, "right") == 0)
        strcat (input_keys, "R");

      if (strcasecmp (action, "return") == 0)
        strcat (input_keys, "X");

      if (strcasecmp (action, "Esc") == 0)
        strcat (input_keys, "E");
    }
 
    free (lirc_code);
    lirc_code = NULL;
  }
}

void get_value (FILE *input, char*output, int out_len)
{
  const char *value_pattern = "value=\"";
  char *value;

  output[0] = 0;
  fgets (output, out_len, input);

  if (value = strcasestr (output, value_pattern))
  {
    memmove (output, value + strlen (value_pattern), 
	     strlen (value + strlen (value_pattern)) + 1);
  }

  if (value = strchr (output, '"'))
    value[0] = 0;
}

void *timezone_guess (void *unused)
{
  const double invalid = 99999;
  FILE *guess_data;
  char line_in[1000];
  double guess_latitude = invalid;
  double guess_longitude = invalid;
  char guess_zone[1000] = {0};
  char *timezone_info_command; 

  if (getenv ("FAKE_GEOBYTES"))
  {
    timezone_info_command = 
      "sleep 2 ; "
      "echo Latitude ; "
      "echo \" <td value=\\\"-34.993\\\"\" ;"
      "echo Longitude ; "
      "echo \" <td value=\\\"138.6\\\"\" ;"
      "echo TimeZone ; "
      "echo \" <td value=\\\"+09:30\\\"\"";
  }
  else
  {
    timezone_info_command = 
      "wget http://www.geobytes.com/IpLocator.htm -O - 2> /dev/null |"
      "grep \"^ <td \" |"
      "grep -i -E -A1 \"Latitude|Longitude|TimeZone\" | grep -v \"^--$\"";
  }

  guess_data = popen (timezone_info_command, "r");

  if (!guess_data)
  {
    fprintf (stderr, "error parsing web page for timezone guess\n");
    if (guessing_timezone)
    {
      guessing_timezone = FALSE;
      guess_failed = TRUE;
      guess_failed_timeout = time (NULL) + 3;
    }
    return NULL;
  }

  while (fgets (line_in, sizeof (line_in), guess_data))
  {
    // Look for the entries in the guess data and then the next line 
    // will hold the value.

    if (strcasestr (line_in, "latitude"))
    {
      get_value (guess_data, line_in, sizeof (line_in));
      sscanf (line_in, "%lf", &guess_latitude);
    }
    else if (strcasestr (line_in, "longitude"))
    {
      get_value (guess_data, line_in, sizeof (line_in));
      sscanf (line_in, "%lf", &guess_longitude);
    }
    else if (strcasestr (line_in, "timezone"))
    {
      get_value (guess_data, line_in, sizeof (line_in));
 
      // The geobytes timezone data is of the form "+09:30" whereas the
      // map timezone data is of the form "+6" or "+9.5". Convert
      // to the map timezone data format so we can find the timezone.

      if (strcmp (&line_in[3], ":30") == 0)
        sprintf (&line_in[3], ".5");
      else
	line_in[3] = 0;

      if (line_in[1] == '0')
        memmove (&line_in[1], &line_in[2], strlen (&line_in[2]) + 1);

      strncpy (guess_zone, line_in, sizeof (guess_zone)); 
    }
  }

  pclose (guess_data);

  if ((guess_latitude != invalid) && 
      (guess_longitude != invalid) &&
      guess_zone[0] != 0) 
  {
    int zone;

    fprintf (stderr, "guessed lat lon %f %f %s\n", 
	     guess_latitude, guess_longitude, guess_zone);

    // Look for the guessed zone in the zone_data structure.
    for (zone = 0; zone < NUM_ZONES; zone++)
    {
      if (strcmp (guess_zone, zone_data[zone].offset) == 0)
	break;
    }

    if (zone == NUM_ZONES)
    {
      fprintf (stderr, "couldnt find zone %s\n", guess_zone);
      if (guessing_timezone)
      {
	guessing_timezone = FALSE;
        guess_failed = TRUE;
        guess_failed_timeout = time (NULL) + 3;
      }
    }
    else
    {
      double x,y,z;
      double min_dist_squared;
      int closest_place;

      // Look for the location in the zone nearest to the guess lat/lon.

      // Convert the guessed lat/lon to x,y,z.

      guess_latitude *= M_PI / 180.0;
      guess_longitude *= M_PI / 180.0;

      x = cos (guess_longitude) * cos (guess_latitude);
      y = sin (guess_longitude) * cos (guess_latitude);
      z = sin (guess_latitude);

      min_dist_squared = 
        pow (x - zone_data[zone].place_info[0].v.x, 2) + 
        pow (y - zone_data[zone].place_info[0].v.y, 2) + 
        pow (z - zone_data[zone].place_info[0].v.z, 2);
      closest_place = 0;

      for (int place = 1; place < zone_data[zone].num_places; place++)
      {
        double dist_squared;

	dist_squared = 
          pow (x - zone_data[zone].place_info[place].v.x, 2) + 
          pow (y - zone_data[zone].place_info[place].v.y, 2) + 
          pow (z - zone_data[zone].place_info[place].v.z, 2);

        if (dist_squared < min_dist_squared)
        {
          closest_place = place;
	  min_dist_squared = dist_squared;
        }
      }

      fprintf (stderr, "Guess=%s %s\n", 
	zone_data[zone].place_info[closest_place].zonename,
	zone_data[zone].offset);

      selected_zone = zone;
      select_place (closest_place);
    }
  }
  else
  {
    if (guessing_timezone)
    {
      guessing_timezone = FALSE;
      guess_failed = TRUE;
      guess_failed_timeout = time (NULL) + 3;
    }
  }
  
  guessing_timezone = FALSE;
  return NULL;
}

void remove_titlebar_and_borders ()
{
  XClassHint* classHint;
  char* appname;
  appname="look fo rme";
  XStoreName (display, window, appname);

  /* Set the name and class hints for the window manager to use. */

  classHint = XAllocClassHint ();
  if (classHint) 
  {
     classHint->res_name = appname;
     classHint->res_class = "MoonRoot";
  }

  XSetClassHint (display, window, classHint);
  XFree (classHint);
  
  typedef struct 
  {
    CARD32 flags;
    CARD32 functions;
    CARD32 decorations;
    INT32 input_mode;
    CARD32 status;
  } MotifWmHints, MwmHints;

  #define MWM_HINTS_DECORATIONS (1L << 1)


  Atom XA_MOTIF_WM_HINTS = XInternAtom (display, "_MOTIF_WM_HINTS", False);
  MotifWmHints mwm_hints;

  mwm_hints.flags = MWM_HINTS_DECORATIONS;
  mwm_hints.decorations = 0;

  XChangeProperty (
    display, window,
    XA_MOTIF_WM_HINTS, XA_MOTIF_WM_HINTS,
    32, PropModeReplace,
    (char *) &mwm_hints, 5);
}

void set_window_position (int x, int y)
{
  XSizeHints hints;

  hints.flags = USPosition | PPosition;
  hints.x = x;
  hints.y = y;

  XSetWMNormalHints(display, window, &hints);
}

int main (int argc, char *argv[])
{
  XEvent xevent;
  Colormap cmap;
  XColor color, colorrgb;
  pthread_t tid;
  int opt;
  int arg_width = -1;
  int arg_height = -1;
  char arg_zone[100] = {0};

  if (getenv ("ACCEL"))
    acceleration = atof (getenv ("ACCEL"));

  while ((opt = getopt (argc, argv, "z:w:h:a:")) != -1)
  {
    switch (opt)
    {
      case 'z':
        strncpy (arg_zone, optarg, sizeof (arg_zone) - 1);

	if (strcasecmp (arg_zone, "guess") == 0)
	{
	  guessing_timezone = TRUE;
	  arg_zone[0]=0;
	}

        break;

      case 'w':
        arg_width = atoi (optarg);
        break;

      case 'h':
        arg_height = atoi (optarg);
        break;

      case 'a':
        acceleration = atof (optarg);
        break;

      case '?':
        printf ("usage: %s [-a accelleration] [-z timezone] "
                "[-w width] [-h height]\n"
                "e.g. linhes_timezone -w 1920 -h 1090 -z Australia/Adelaide\n"
		"specify a timezone of 'guess' to determine the initial "
		"timezone from\n"
		"your ip address using geocache.\n",
          argv[0]);
        exit (0);
        break;
    }
  }

  pthread_create (&tid, NULL, lirc_thread, NULL);

  /* Connect to the X server. */
  
  display = XOpenDisplay ("");

  if (display == NULL) 
  {
    fprintf (stderr, "cannot connect to server\n");
    exit (EXIT_FAILURE);
  }

  /* Get default screen. */
  
  screen = DefaultScreen (display);
  screen_width = XWidthOfScreen (DefaultScreenOfDisplay (display));
  screen_height = XHeightOfScreen (DefaultScreenOfDisplay (display));

  if ((arg_width <= 0) && (arg_height > 0))
    arg_width == arg_height;

  if (arg_width > 0)
  {
    width = arg_width;

    if (arg_height <= 0) 
      height = width;
    else
      height = arg_height;

    x = (screen_width - width) / 2;
    y = (screen_height - height) / 2;
  }
  else
  {
    width = screen_width;
    height = screen_height;
    x = 0;
    y = 0;
  }
  
  if (width < height)
    radius = width;
  else
    radius = height;

  radius = 0.96 * radius / 2;

  if ((screen_height == 0) || 
      (XWidthMMOfScreen (DefaultScreenOfDisplay (display)) == 0))
    aspect_correction = 1;
  else
    aspect_correction = 
      sqrt (screen_width * 
            XHeightMMOfScreen (DefaultScreenOfDisplay (display)) * 1.0 /
            screen_height /
            XWidthMMOfScreen (DefaultScreenOfDisplay (display)));

  // Protect against very wierd aspect corrections from bogus 
  // screen dimensions.
  if ((aspect_correction < 0.5) || (aspect_correction > 2))
    aspect_correction = 1;

  window = XCreateSimpleWindow (display,
             DefaultRootWindow(display), x, y, width, height, 0,
             land[0].pixel, ocean.pixel);

  remove_titlebar_and_borders ();
  set_window_position (x, y);

  if (!window) 
  {
    fprintf (stderr, "cannot open window\n");            
    exit (EXIT_FAILURE);
  }
  
  /* set graphics context of rectangle to red */
  gc= XCreateGC (display, window, 0, 0);
  cmap = DefaultColormap (display, screen);

  // Load the font.
  
  font = XLoadQueryFont (display, (width > 800) ? 
                         "-*-lucida-bold-r-*-*-34-*-*-*-*-*-*-*" : 
                         "-*-lucida-bold-r-*-*-20-*-*-*-*-*-*-*");
  if (!font) 
    fprintf (stderr,"error loading font\n");

  XSetFont (display, gc, font->fid);

  pixmap = 
    XCreatePixmap (display, window, width, height, 
                   DefaultDepth (display, DefaultScreen (display)));

  ocean.flags = DoRed | DoGreen | DoBlue;
  ocean.red = 40 * 256;
  ocean.green = 41 * 256;
  ocean.blue = 72 * 256;
  if (XAllocColor (display, cmap, &ocean) == 0)
    printf ("Cant allocate color\n");

  lat_lon.flags = DoRed | DoGreen | DoBlue;
  lat_lon.red = 0 * 256;
  lat_lon.green = 0 * 256;
  lat_lon.blue = 86 * 256;
  if (XAllocColor (display, cmap, &lat_lon) == 0)
    printf ("Cant allocate color\n");

  tux_yellow.flags = DoRed | DoGreen | DoBlue;
  tux_yellow.red = 248 * 256;
  tux_yellow.green = 191 * 256;
  tux_yellow.blue = 17 * 256;
  if (XAllocColor (display, cmap, &tux_yellow) == 0)
    printf ("Cant allocate color\n");

  for (int land_col = 0; land_col < XtNumber (land); land_col++)
  {
    land[land_col].flags = DoRed | DoGreen | DoBlue;
    land[land_col].red = (120 + 9 * land_col) * 256;
    land[land_col].green = (40 + 3 * land_col) * 256;
    land[land_col].blue = 0 * 256;
    if (XAllocColor (display, cmap, &land[land_col]) == 0)
      printf ("Cant allocate color\n");
  }

  selected_land.flags = DoRed | DoGreen | DoBlue;
  selected_land.red = 3 * 256;
  selected_land.green = 40 * 256;
  selected_land.blue = 13 * 256;
  if (XAllocColor (display, cmap, &selected_land) == 0)
    printf ("Cant allocate color\n");

  border.flags = DoRed | DoGreen | DoBlue;
  border.red   = 0 * 256;
  border.green = 0 * 256;
  border.blue  = 0 * 256;
  if (XAllocColor (display, cmap, &border) == 0)
    printf ("Cant allocate color\n");

  selected_border.flags = DoRed | DoGreen | DoBlue;
  selected_border.red   = 80 * 256;
  selected_border.green = 255 * 256;
  selected_border.blue  = 80 * 256;
  if (XAllocColor (display, cmap, &selected_border) == 0)
    printf ("Cant allocate color\n");

  name_colour.flags = DoRed | DoGreen | DoBlue;
  name_colour.red   = 255 * 256;
  name_colour.green = 255 * 256;
  name_colour.blue  = 0 * 256;
  if (XAllocColor (display, cmap, &name_colour) == 0)
    printf ("Cant allocate color\n");

  location_dot.flags = DoRed | DoGreen | DoBlue;
  location_dot.red   = 255 * 256;
  location_dot.green = 55 * 256;
  location_dot.blue  = 200 * 256;
  if (XAllocColor (display, cmap, &location_dot) == 0)
    printf ("Cant allocate color\n");

  // Find the selected timezone (or LA if a timezone was not selected) and 
  // make that the selected zone and place.

  default_zone (arg_zone);

  if (guessing_timezone)
  {
    pthread_create (&tid, NULL, timezone_guess, NULL);
    guess_timeout = time (NULL) + 10;
    displayed_lat = M_PI/2;
    displayed_lon = 0;
  }
  else
  {
    displayed_lat = target_lat;
    displayed_lon = target_lon;
  }

  /* ask for exposure event and keyboard events */
  XSelectInput(display, window, KeymapNotify | ExposureMask);

  /* pop this window up on the screen */
  XMapRaised (display, window);

  redraw_map ();
    
  while (1)
  {
    if (guessing_timezone && (time (NULL) > guess_timeout))
    {
      guessing_timezone = FALSE;
      guess_failed_timeout = time (NULL) + 3;
      guess_failed = TRUE;
    }

    if ((displayed_lat != target_lat) || (displayed_lon != target_lon) || 
        guessing_timezone || guess_failed ||
  	(info_displayed ^ show_information))
    {
      if (guess_failed && (time (NULL) > guess_failed_timeout))
      {
        guess_failed = FALSE;
      }

      next_view ();
      redraw_map ();
    }
    else
    {
      usleep (1000);
    }

    while (strlen (input_keys))
    {
      handle_key (input_keys[0]);
      memmove (&input_keys[0], &input_keys[1], strlen (input_keys));
    }
      
    // If there is an event pending, go on to process it.

    if (XCheckIfEvent (display, &xevent, event_predicate, NULL))
      handle_event (&xevent); 
  }

  return 0;
}