#include <malloc.h>
#include <math.h>
#include <float.h>

#include "log.h"
#include "lephisto.h"
#include "toolkit.h"
#include "space.h"
#include "opengl.h"
#include "mission.h"
#include "map.h"

#define WINDOW_WIDTH    650
#define WINDOW_HEIGHT   540

#define MAP_WIDTH       (WINDOW_WIDTH-150)
#define MAP_HEIGHT      (WINDOW_HEIGHT-100)

#define BUTTON_WIDTH  60
#define BUTTON_HEIGHT  40

static int   map_wid  = 0;
static double map_zoom = 1.;          /* Zoom of the map. */
static double map_xpos  = 0.;         /* Map position. */
static double map_ypos  = 0.;
static int map_selected = -1;
static StarSystem** map_path = NULL;  /* The path to current selected system. */
static int map_npath = 0;

static int map_drag = 0;              /* Is the user dragging the map? */

/* Extern. */
/* space.c */
extern StarSystem* systems_stack;
extern int systems_nstack;
/* player.c */
extern int planet_target;
extern int hyperspace_target;

static void map_close(char* str);
static void map_update(void);
static int  map_inPath(StarSystem* sys);
static void map_render(double bx, double by, double w, double h);
static void map_mouse(SDL_Event* event, double mx, double my);
static void map_buttonZoom(char* str);
static void map_selectCur(void);

/* Open the map window. */
void map_open(void) {
  if(map_wid) {
    map_close(NULL);
    return;
  }

  /* Set the position to focus on current system. */
  map_xpos = cur_system->pos.x;
  map_ypos = cur_system->pos.y;

  /* Mark systems as needed. */
  mission_sysMark();

  /* Attempt to select current map if none is selected. */
  if(map_selected == -1)
    map_selectCur();

  map_wid = window_create("Star Map", -1, -1, WINDOW_WIDTH, WINDOW_HEIGHT);

  /*
   * SIDE TEXT
   *
   * $System
   *
   * Faction:
   *   $Faction (or Multiple)
   *
   * Status:
   *   $Status
   *
   * Planets:
   *   $Planet1, $Planet2, ...
   *
   * Services:
   *   $Services
   *
   * ...
   *
   * [Close]
   */

  /* System name. */
  window_addText(map_wid, -20, -20, 100, 20, 1, "txtSysname",
                 &gl_defFont, &cDConsole, systems_stack[map_selected].name);

  /* Faction. */
  window_addText(map_wid, -20, -60, 90, 20, 0, "txtSFaction",
                 &gl_smallFont, &cDConsole, "Faction:");

  window_addText(map_wid, -20, -60-gl_smallFont.h-5, 80, 100, 0, "txtFaction",
                 &gl_smallFont, &cBlack, NULL);

  /* Standing. */
  window_addText(map_wid, -20, -100, 90, 20, 0, "txtSStanding",
      &gl_smallFont, &cDConsole, "Standing:");

  window_addText(map_wid, -20, -100-gl_smallFont.h-5, 80, 100, 0, "txtStanding",
      &gl_smallFont, &cBlack, NULL);

  /* Planets. */
  window_addText(map_wid, -20, -140, 90, 20, 0, "txtSPlanets",
                 &gl_smallFont, &cDConsole, "Planets:");

  window_addText(map_wid, -20, -140-gl_smallFont.h-5, 80, 100, 0, "txtPlanets",
                 &gl_smallFont, &cBlack, NULL);

  /* Services. */
  window_addText(map_wid, -20, -180, 90, 20, 0, "txtSServices",
                 &gl_smallFont, &cDConsole, "Services:");

  window_addText(map_wid, -20, -180-gl_smallFont.h-5, 80, 100, 0, "txtServices",
                 &gl_smallFont, &cBlack, NULL);

  /* Close button. */
  window_addButton(map_wid, -20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
      "btnClose", "Close", map_close);

  /* The map itself. */
  window_addCust(map_wid, 20, -40, MAP_WIDTH, MAP_HEIGHT,
                 "cstMap", 1, map_render, map_mouse);

  /* 
   * Bottom stuff.
   * 
   * [+] [-] Nebulae, Asteroids, Interference.
   */
  /* Zoom buttons. */
  window_addButton(map_wid, 40, 20, 30, 30, "btnZoomIn",  "+", map_buttonZoom);
  window_addButton(map_wid, 80, 20, 30, 30, "btnZoomOut", "-", map_buttonZoom);
  /* Situation text. */
  window_addText(map_wid, 140, 10, WINDOW_WIDTH-80-30-30, 30, 0,
      "txtSystemStatus", &gl_smallFont, &cBlack, NULL);

  map_update();
}

static void map_close(char* str) {
  (void)str;
  if(map_wid) {
    window_destroy(map_wid);
    map_wid = 0;
  }
}

static void map_update(void) {
  int i;
  StarSystem* sys;
  int f, y, h, standing, nstanding;
  unsigned int services;
  char buf[128];

  sys = &systems_stack[map_selected];

  /* Right text. */
  if(!sys_isKnown(sys)) { /* System isn't known. Erase all. */
    /* Right text. */
    window_modifyText(map_wid, "txtSysname",      "Unknown");
    window_modifyText(map_wid, "txtFaction",      "Unknown");
    /* Standing. */
    window_moveWidget(map_wid, "txtSStanding",    -20, -100);
    window_moveWidget(map_wid, "txtStanding",     -20, -100-gl_smallFont.h-5);
    window_modifyText(map_wid, "txtStanding",     "Unknown");
    /* Planets. */
    window_moveWidget(map_wid, "txtSPlanets",     -20, -140);
    window_moveWidget(map_wid, "txtPlanets",      -20, -140-gl_smallFont.h-5);
    window_modifyText(map_wid, "txtPlanets",      "Unknown");
    /* Services. */
    window_moveWidget(map_wid, "txtSServices",    -20, -180);
    window_moveWidget(map_wid, "txtServices",     -20, -180-gl_smallFont.h-5);
    window_modifyText(map_wid, "txtServices",     "Unknown");

    /* Bottom text. */
    window_modifyText(map_wid, "txtSystemStatus", NULL);

    return;
  }

  /* System is known. */
  window_modifyText(map_wid, "txtSysname", sys->name);

  if(sys->nplanets == 0) { /* No planets -> no factions. */
    window_modifyText(map_wid, "txtFaction",    "NA");
    window_moveWidget(map_wid, "txtSStanding",  -20, -100);
    window_moveWidget(map_wid, "txtStanding",   -20, -100-gl_smallFont.h-5);
    window_modifyText(map_wid, "txtStanding",   "NA");
    y = -100;
  } else {
    standing = 0;
    nstanding = 0;
    f = -1;
    for(i = 0; i < sys->nplanets; i++) {
      if((f == -1) && (sys->planets[i].faction > 0)) {
        f = sys->planets[i].faction;
        standing += faction_getPlayer(f);
        nstanding++;
      }
      else if(f != sys->planets[i].faction &&
          (sys->planets[i].faction>0)) {
        /* @todo More verbosity. */
        snprintf(buf, 100, "Multiple");
        break;
      }
    }
    if(i == sys->nplanets)
      /* Saw them all, and all the same. */
      snprintf(buf, 100, "%s", faction_longname(f));

    /* Modify the text. */
    window_modifyText(map_wid, "txtFaction", buf);
    window_modifyText(map_wid, "txtStanding",
        faction_getStanding(standing / nstanding));

    /* Lower text if needed. */
    h = gl_printHeight(&gl_smallFont, 80, buf);
    y = -100 - (h - gl_smallFont.h);
    window_moveWidget(map_wid, "txtSStanding", -20, y);
    window_moveWidget(map_wid, "txtStanding",  -20, y-gl_smallFont.h-5);
  }

  /* Get planets. */
  if(sys->nplanets == 0)
    window_modifyText(map_wid, "txtPlanets", "None");
  else {
    buf[0] = '\0';
    if(sys->nplanets > 0)
      strcat(buf, sys->planets[0].name);
    for(i = 1; i < sys->nplanets; i++) {
      strcat(buf, ",\n");
      strcat(buf, sys->planets[i].name);
    }
    window_modifyText(map_wid, "txtPlanets", buf);
  }
  y -= 40;
  window_moveWidget(map_wid, "txtSPlanets", -20, y);
  window_moveWidget(map_wid, "txtPlanets", -20, y-gl_smallFont.h-5);

  /* Get the services. */
  h = gl_printHeight(&gl_smallFont, 80, buf);
  y -= 40 +(h - gl_smallFont.h);
  window_moveWidget(map_wid, "txtSServices", -20, y);
  window_moveWidget(map_wid, "txtServices", -20, y-gl_smallFont.h-5);
  services = 0;
  for(i = 0; i < sys->nplanets; i++)
    services |= sys->planets[i].services;
  buf[0] = '\0';
  if(services & PLANET_SERVICE_COMMODITY)
    strcat(buf, "Commodity\n");
  if(services & PLANET_SERVICE_OUTFITS)
    strcat(buf, "Outfits\n");
  if(services & PLANET_SERVICE_SHIPYARD)
    strcat(buf, "Shipyard\n");
  if(buf[0] == '\0')
    strcat(buf, "None");
  window_modifyText(map_wid, "txtServices", buf);

  /* System status. */
  buf[0] = '\0';
  if(sys->nebu_density > 0.) { /* Has nebulae. */
    /* Volatility. */
    if(sys->nebu_volatility > 700.)
      strcat(buf, " Volatile");
    else if(sys->nebu_volatility > 300.)
      strcat(buf, " Dangerous");
    else if(sys->nebu_volatility > 0.)
      strcat(buf, " Unstable");
    /* Density */
    if(sys->nebu_density > 700.)
      strcat(buf, " Dense");
    else if(sys->nebu_density < 300.)
      strcat(buf, " Light");
    strcat(buf, " Nebulae");
  }
  window_modifyText(map_wid, "txtSystemStatus", buf);
}

/* Return 1 if sys is part of the map_path. */
static int map_inPath(StarSystem* sys) {
  int i, f;

  f = pilot_getJumps(player) - 1;
  for(i = 0; i < map_npath; i++)
    if(map_path[i] == sys) {
      if(i > f) {
        return 2;
      } else return 1;
    }
  return 0;
}

/* Render the map as a custom widget. */
static void map_render(double bx, double by, double w, double h) {
  int i, j, n, m;
  double x, y, r, tx, ty;
  StarSystem* sys;
  glColour* col;

  r = 5.;
  x = (bx - map_xpos + w/2) * 1.;
  y = (by - map_ypos + h/2) * 1.;
  /* Background */
  COLOUR(cBlack);
  glBegin(GL_QUADS);
  glVertex2d(bx,   by);
  glVertex2d(bx,   by+h);
  glVertex2d(bx+w,  by+h);
  glVertex2d(bx+w, by);
  glEnd();

  /* Render the star systems. */
  for(i = 0; i < systems_nstack; i++) {
    sys = &systems_stack[i];

    /* Check to make sure system is known of adjacent to known (or marked). */
    if(!sys_isMarked(sys) && !space_sysReachable(sys)) continue;

    /* System Colours. */
    if(sys == cur_system) col = &cRadar_targ;
    else if(!sys_isKnown(sys) || (sys->nplanets == 0)) col = &cInert;
    else col = faction_getColour(sys->faction);
    COLOUR(*col);

    /* Draw the system. */
    tx = x + sys->pos.x * map_zoom;
    ty = y + sys->pos.y * map_zoom;
    gl_drawCircleInRect(tx, ty, r, bx, by, w, h);

    /* Mark the system if needed. */
    if(sys_isMarked(sys)) {
      COLOUR(cRed);
      glBegin(GL_TRIANGLES);
        glVertex2d(tx+r+9, ty+r+3);
        glVertex2d(tx+r+3, ty+r+3);
        glVertex2d(tx+r+3, ty+r+9);
      glEnd();
    }

    /* Draw the system name. */
    if(sys_isKnown(sys)) {
      tx = x + 7. + sys->pos.x * map_zoom;
      ty = y - 5. + sys->pos.y * map_zoom;
      gl_print(&gl_smallFont,
          tx + SCREEN_W/2., ty + SCREEN_H/2.,
          &cWhite, sys->name);
    }

    if(!sys_isKnown(sys)) continue; /* We dont't draw hyperspace lines. */

    /* Draw the hyperspace paths. */
    glShadeModel(GL_SMOOTH);
    /* Cheaply use transparency instead of actually */
    /* calculating from x to y the line must go. :) */
    for(j = 0; j < sys->njumps; j++) {
      n = map_inPath(&systems_stack[sys->jumps[j]]);
      m = map_inPath(sys);
      /* Set the colours, is the route the current one? */
      if((hyperspace_target != -1) &&
          (((cur_system == sys) && (j == hyperspace_target)) ||
           ((cur_system == &systems_stack[sys->jumps[j]]) &&
            (sys == &systems_stack[cur_system->jumps[hyperspace_target]]))))
        col = &cGreen;
      else if((n > 0) && (m > 0)) {
        if((n == 2) || (m == 2)) /* Out of fuel. */
          col = &cRed;
        else
          col = &cYellow;
      }
      else col = &cDarkBlue;
      glBegin(GL_LINE_STRIP);
        ACOLOUR(*col, 0.);
        tx = x + sys->pos.x * map_zoom;
        ty = y + sys->pos.y * map_zoom;
        glVertex2d(tx, ty);
        COLOUR(*col);
        tx += (systems_stack[sys->jumps[j]].pos.x - sys->pos.x)/2. * map_zoom;
        ty += (systems_stack[sys->jumps[j]].pos.y - sys->pos.y)/2. * map_zoom;
        glVertex2d(tx, ty);
        ACOLOUR(*col, 0.);
        tx = x + systems_stack[sys->jumps[j]].pos.x * map_zoom;
        ty = y + systems_stack[sys->jumps[j]].pos.y * map_zoom;
        glVertex2d(tx, ty);
      glEnd();
    }
    glShadeModel(GL_FLAT);
  }
  /* Selected planet. */
  if(map_selected != -1) {
    sys = &systems_stack[map_selected];
    COLOUR(cRed);
    gl_drawCircleInRect(x + sys->pos.x*map_zoom, y + sys->pos.y*map_zoom,
                        r+3., bx, by, w, h);
  }
}

/* Map event handling. */
static void map_mouse(SDL_Event* event, double mx, double my) {
  int i, j;
  double x, y, t;
  StarSystem* sys;

  t = 13.*15.; /* Threshold. */

  mx -= MAP_WIDTH/2 - map_xpos;
  my -= MAP_HEIGHT/2 - map_ypos;

  switch(event->type) {
  case SDL_MOUSEBUTTONDOWN:
    if(event->button.button == SDL_BUTTON_WHEELUP)
      map_buttonZoom("btnZoomOut");
    else if(event->button.button == SDL_BUTTON_WHEELDOWN)
      map_buttonZoom("btnZoomIn");

    /* Selecting star system. */
    else {
      for(i = 0; i < systems_nstack; i++) {
        sys = &systems_stack[i];

        /* Must be reachable. */
        if(!space_sysReachable(sys))
          continue;

        /* Get position. */
        x = systems_stack[i].pos.x * map_zoom;
        y = systems_stack[i].pos.y * map_zoom;

        if((pow2(mx-x)+pow2(my-y)) < t) {
          /* Select the current system and make a path to it. */
          map_selected = i;
          if(map_path)
            free(map_path);
          map_path = map_getJumpPath(&map_npath,
              cur_system->name, sys->name, 0);

          if(map_npath == 0)
            hyperspace_target = -1;
          else
            /* See if it a valid hyperspace target. */
            for(j = 0; j < cur_system->njumps; j++) {
              if(map_path[0] == &systems_stack[cur_system->jumps[j]]) {
                planet_target = -1; /* Override planet_target. */
                hyperspace_target = j;
                break;
              }
            }
          map_update();
          break;
        }
      }
      map_drag = 1;
    }
    break;

  case SDL_MOUSEBUTTONUP:
    if(map_drag) map_drag = 0;
    break;

  case SDL_MOUSEMOTION:
    if(map_drag) {
      /* Axis is inverted. */
      map_xpos -= event->motion.xrel;
      map_ypos += event->motion.yrel;
    }
    break;
  }
}

static void map_buttonZoom(char* str) {
  if(strcmp(str, "btnZoomIn")==0) {
    map_zoom += (map_zoom >= 1.) ? 0.5 : 0.25;
    map_zoom = MIN(2.5, map_zoom);
  }

  else if(strcmp(str, "btnZoomOut")==0) {
    map_zoom -= (map_zoom > 1.) ? 0.5 : 0.25;
    map_zoom = MAX(0.5, map_zoom);
  }
}

/* Set the map to sane defaults. */
void map_clear(void) {
  map_zoom = 1.;
  if(cur_system != NULL) {
    map_xpos = cur_system->pos.x;
    map_ypos = cur_system->pos.y;
  } else {
    map_xpos = 0.;
    map_ypos = 0.;
  }
  if(map_path != NULL) {
    free(map_path);
    map_path = NULL;
    map_npath = 0;
  }

  /* Default system is current system. */
  map_selectCur();
}

static void map_selectCur(void) {
  int i;
  if(cur_system != NULL) {
    for(i = 0; i < systems_nstack; i++) {
      if(&systems_stack[i] == cur_system) {
        map_selected = i;
        break;
      }
    }
  } else {
    /* Probably going to seg fault now.. */
    map_selected = -1;
  }
}

/* Update the map after a jump. */
void map_jump(void) {
  int j;

  /* Set selected system to self. */
  map_selectCur();

  map_xpos = cur_system->pos.x;
  map_ypos = cur_system->pos.y;

  /* Update path if set. */
  if(map_path != NULL) {
    map_npath--;
    if(map_npath == 0) {
      /* Path is empty. */
      free(map_path);
      map_path = NULL;
    } else {
      /* Get rid of bottom of the path. */
      memcpy(&map_path[0], &map_path[1], sizeof(StarSystem*) * map_npath);
      map_path = realloc(map_path, sizeof(StarSystem*) * map_npath);

      /* Set the next jump to be the next in path. */
      for(j = 0; j < cur_system->njumps; j++) {
        if(map_path[0] == &systems_stack[cur_system->jumps[j]]) {
          planet_target = -1; /* Override planet_target. */
          hyperspace_target = j;
          break;
        }
      }
    }
  }
}

/* A* Algorithm fo shortest path finding. */

/* The node struct. */
typedef struct SysNode_ {
  struct SysNode_* next, *gnext;

  struct SysNode_* parent;
  StarSystem* sys;
  double r; /* Ranking. */
  int g;    /* Step. */
} SysNode;

static SysNode* A_gc;
/* Prototypes. */
static SysNode* A_newNode(StarSystem* sys, SysNode* parent);
static double A_h(StarSystem* n, StarSystem* g);
static double A_g(SysNode* n);
static SysNode* A_add(SysNode* first, SysNode* cur);
static SysNode* A_rm(SysNode* first, StarSystem* cur);
static SysNode* A_in(SysNode* first, StarSystem* cur);
static SysNode* A_lowest(SysNode* first);
static void A_freeList(SysNode* first);

/* Creates a new node link to star system. */
static SysNode* A_newNode(StarSystem* sys, SysNode* parent) {
  SysNode* n;

  n = malloc(sizeof(SysNode));

  n->next = NULL;
  n->parent = parent;
  n->sys = sys;
  n->r = DBL_MAX;
  n->g = 0.;

  n->gnext = A_gc;
  A_gc = n;

  return n;
}

static double A_h(StarSystem* n, StarSystem* g) {
  (void)n;
  (void)g;
  /* Euclidean distance. */
  /*return sqrt(pow2(n->pos.x - g->pos.x) + pow2(n->pos.y - g->pos.y))/100.; */
  return 0.;
}

/* Get the g from a node. */
static double A_g(SysNode* n) {
  return n->g;
}

/* Add a node to the linkes list. */
static SysNode* A_add(SysNode* first, SysNode* cur) {
  SysNode* n;

  if(first == NULL)
    return cur;

  n = first;
  while(n->next != NULL)
    n = n->next;
  n->next = cur;

  return first;
}

/* Remove a node from a linked list. */
static SysNode* A_rm(SysNode* first, StarSystem* cur) {
  SysNode* n, *p;

  if(first->sys == cur) {
    n = first->next;
    first->next = NULL;
    return n;
  }

  p = first;
  n = p->next;
  do {
    if(n->sys == cur) {
      n->next = NULL;
      p->next = n->next;
      break;
    }
  } while((n=n->next) != NULL);

  return first;
}

/* Check if node is in linked list. */
static SysNode* A_in(SysNode* first, StarSystem* cur) {
  SysNode* n;
  
  if(first == NULL)
    return NULL;

  n = first;
  do {
    if(n->sys == cur)
      return n;
  } while((n=n->next) != NULL);
  return NULL;
}

/* Return the lowest ranking node from a linked list of nodes. */
static SysNode* A_lowest(SysNode* first) {
  SysNode* lowest, *n;

  if(first == NULL)
    return NULL;

  n = first;
  lowest = n;
  do {
    if(n->r < lowest->r)
      lowest = n;
  } while((n=n->next) != NULL);

  return lowest;
}

/* Free a linked list. */
static void A_freeList(SysNode* first) {
  SysNode* p, *n;

  if(first == NULL)
    return;

  p = NULL;
  n = first;
  do {
    if(p != NULL)
      free(p);
    p = n;
  } while((n=n->gnext) != NULL);

  free(p);
}

StarSystem** map_getJumpPath(int* njumps, char* sysstart, char* sysend, int ignore_known) {
  int i, cost;

  StarSystem* sys, *ssys, *esys, **res;

  SysNode* cur, *neighbour;
  SysNode* open, *closed;
  SysNode* ocost, *ccost;

  A_gc = NULL;

  /* Initial and target systems. */
  ssys = system_get(sysstart);  /* Start. */
  esys = system_get(sysend);    /* End. */

  /* System target must be known and reachable. */
  if(!ignore_known && !sys_isKnown(esys) && !space_sysReachable(esys)) {
    /* Can't reach - Don't make path. */
    (*njumps) = 0;
    return NULL;
  }

  /* Start the linked lists. */
  open = closed = NULL;
  cur = A_newNode(ssys, NULL);
  open = A_add(open, cur); /* Initial open node is the start system. */

  while((cur = A_lowest(open))->sys != esys) {
    /* Get best from open and toss to closed. */
    open = A_rm(open, cur->sys);
    closed = A_add(closed, cur);
    cost = A_g(cur) + 1;

    for(i = 0; i < cur->sys->njumps; i++) {
      sys = &systems_stack[cur->sys->jumps[i]];

      /* Make sure it's reachable. */
      if(!ignore_known && (!sys_isKnown(sys) && !space_sysReachable(esys)))
        continue;

      neighbour = A_newNode(sys, NULL);

      ocost = A_in(open, sys);
      if((ocost != NULL) && (cost < ocost->g)) {
        open = A_rm(open, sys); /* New path is better. */
      }

      ccost = A_in(closed, sys);
      if(ccost != NULL) {
        closed = A_rm(closed, sys); /* Shouldn't happen. */
      }

      if((ocost == NULL) && (ccost == NULL)) {
        neighbour->g = cost;
        neighbour->r = A_g(neighbour) + A_h(cur->sys, sys);
        neighbour->parent = cur;
        open = A_add(open, neighbour);
      }
    }
  }
  /* Build the path backwards. */
  (*njumps) = A_g(cur);
  res = malloc(sizeof(StarSystem*) * (*njumps));
  for(i = 0; i < (*njumps); i++) {
    res[(*njumps)-i-1] = cur->sys;
    cur = cur->parent;
  }

  /* Free the linked list. */
  A_freeList(A_gc);
  return res;
}

/* Marks maps around a radius of current system as known. */
int map_map(char* targ_sys, int r) {
  int i, dep;
  StarSystem* sys, *jsys;
  SysNode* closed, *open, *cur, *neighbour;

  A_gc = NULL;
  open = closed = NULL;

  if(targ_sys == NULL) sys = cur_system;
  else sys = system_get(targ_sys);
  sys_setFlag(sys, SYSTEM_KNOWN);
  open = A_newNode(sys, NULL);
  open->r = 0;

  while((cur = A_lowest(open)) != NULL) {
    /* Mark system as known and go to next. */
    sys = cur->sys;
    dep = cur->r;
    sys_setFlag(sys, SYSTEM_KNOWN);
    open = A_rm(open, sys);
    closed = A_add(closed, cur);
    
    /* Check it's jumps. */
    for(i = 0; i < sys->njumps; i++) {
      jsys = &systems_stack[cur->sys->jumps[i]];

      /* System has already been parsed or is too deep. */
      if((A_in(closed, jsys) != NULL) || (dep+1 > r))
        continue;

      /* Create new node and such. */
      neighbour = A_newNode(jsys, NULL);
      neighbour->r = dep+1;
      open = A_add(open, neighbour);
    }
  }
  A_freeList(A_gc);
  return 0;
}

/* Check to see if radius is mapped. */
int map_isMapped(char* targ_sys, int r) {
  int i, dep, ret;
  StarSystem* sys, *jsys;
  SysNode* closed, *open, *cur, *neighbour;

  A_gc = NULL;
  open = closed = NULL;

  if(targ_sys == NULL) sys = cur_system;
  else sys = system_get(targ_sys);
  open = A_newNode(sys, NULL);
  open->r = 0;
  ret = 1;

  while((cur = A_lowest(open)) != NULL) {
    /* Mark system as known and go to next. */
    sys = cur->sys;
    dep = cur->r;
    if(!sys_isFlag(sys, SYSTEM_KNOWN)) {
      ret = 0;
      break;
    }
    open = A_rm(open, sys);
    closed = A_add(closed, cur);

    /* Check it's jumps. */
    for(i = 0; i < sys->njumps; i++) {
      jsys = &systems_stack[cur->sys->jumps[i]];

      /* System has already been parsed or is too deep. */
      if((A_in(closed, jsys) != NULL) || (dep+1 > r))
        continue;

      /* Create new node and such. */
      neighbour = A_newNode(jsys, NULL);
      neighbour->r = dep+1;
      open = A_add(open, neighbour);
    }
  }

  A_freeList(A_gc);
  return ret;
}