//  ------------------------------------------------------------------
//  The Goldware Utilities.
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License as
//  published by the Free Software Foundation; either version 2 of the
//  License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  GoldNODE - A nodelist compiler for GoldED.
//  ------------------------------------------------------------------


#include <gdbgerr.h>
#include <list>
#include <map>
#include <clocale>
#include <gdefs.h>
#include <gcrcall.h>
#include <gmemall.h>
#include <gfilutil.h>
#include <gftnall.h>
#include <gftnnlge.h>
#include <gstrall.h>
#include <gshare.h>
#include <gtimall.h>
#include <gutlmisc.h>
#include <gccfgg.h>
#include <gdirposx.h>
#include <gutlos.h>
#include <glog.h>

//#define GOLDNODE_STATS 1

#ifdef GOLDNODE_STATS
#include <math.h>
#endif


//  ------------------------------------------------------------------
//  Config versions

#if defined(__OS2__)
#define __GPID__ "GoldNode+/2"
#elif defined(__WIN32__)
#define __GPID__ "GoldNode+/W32"
#elif defined(__MSDOS__)
#define __GPID__ "GoldNode+/386"
#elif defined(__linux__)
#define __GPID__ "GoldNODE+/LNX"
#elif defined(__FreeBSD__) or defined(__OpenBSD__)
#define __GPID__ "GoldNODE+/BSD"
#else
#define __GPID__ "GoldNODE+"
#endif

#define __GVER__ "1.1.4.1"         // Visible version


//  ------------------------------------------------------------------
// 32-bit versions

const size_t maxnodes = 262000;


//  ------------------------------------------------------------------

typedef vector<Addr>::iterator addr_iter;
typedef vector<Stamp>::iterator stamp_iter;
typedef list<_GEIdx> geidxlist;

// Nodelists
vector<Stamp> nodelist; // nodelist files,stamps,update marker
vector<Addr> nodezone;  // nodelist zones
vector<Stamp> userlist; // Userlist files,stamps,update marker
vector<Addr> userzone;  // Userlist zones

// Exclude/Include nodes
vector<Addr> excludenode;
vector<Addr> includenode;

// Index files
string  addrindex;
string  nodeindex;
string  listindex;

//  ------------------------------------------------------------------

const word _POINT = 0x0001;
const word _NODE  = 0x0002;
const word _NET   = 0x0003;
const word _ZONE  = 0x0004;
const word _TEST  = 777;

static string nodepath;                // Path to the nodelist files

static time_t runtime = 0;
static int    sh_mod = SH_DENYWR;
static bool   fidouser = false;
static Path   fidouserlst;
static bool   ignoredups = false;
static size_t dups = 0;
static bool   quiet = false;

#ifdef GOLDNODE_STATS
bool make_stats = false;
Path statfilename = "goldnode.stt";

struct nl_stat {
  int nodename[100];
  int location[100];
  int sysopname[100];
};

nl_stat statistic;
#endif

//  ------------------------------------------------------------------

#define fast_printf    if(not quiet) printf
#define fast_putchar   if(not quiet) putchar

//  ------------------------------------------------------------------
//  Display a "twirly"

#define TWIRLY_FACTOR 511
#define ISTWIRLY(n) (((n)&TWIRLY_FACTOR)==0)

static void twirly() {

  static int n=0;

  n = (++n)%4;
  switch(n) {
    case 0:   fast_putchar('|');   break;
    case 1:   fast_putchar('/');   break;
    case 2:   fast_putchar('-');   break;
    case 3:   fast_putchar('\\');  break;
  }

  fast_putchar('\b');
  fflush(stdout);
}


//  ------------------------------------------------------------------

static bool match_addr_mask(Addr* mask, Addr* addr) {

  if(mask->zone == GFTN_ALL or mask->zone == addr->zone)
    if(mask->net == GFTN_ALL or mask->net == addr->net)
      if(mask->node == GFTN_ALL or mask->node == addr->node)
        if(mask->point == GFTN_ALL or mask->point == addr->point)
          return true;

  return false;
}


//  ------------------------------------------------------------------

static bool macro_addr(char* str, int ap, Addr* addr) {

  word part = 0xFFFD;

  if(*str == NUL)
    part = 0;
  else if(isdigit(*str))
    part = atow(str);
  else if(*str == '?' or *str == '*')
    part = GFTN_ALL;

  if(ap == _TEST) {
    if(part == 0xFFFD)
      return false;
    else
      return true;
  }

  switch(ap) {
    case _ZONE:
      addr->zone = part;
      break;
    case _NET:
      addr->net = part;
      break;
    case _NODE:
      addr->node = part;
      break;
    case _POINT:
      addr->point = part;
      break;
    default:
      return false;
  }
  return true;
}


//  ------------------------------------------------------------------

static char* fast_parse_addr(char* str, Addr* addr) {

  char* net;
  char* node;
  char* point;
  char* domain;
  char* space;

  space = strchr(str, ' ');
  if(space)
    *space = NUL;

  net = strchr(str, ':');
  node = strchr(str, '/');
  point = strchr(str, '.');
  domain = strchr(str, '@');
  if(domain and point)
    if((dword)point > (dword)domain)
      point = NULL;

  if(space)
    *space = ' ';

  if(net) {
    addr->zone = atow(str);
    addr->net = atow(net+1);
    if(node)
      addr->node = atow(node+1);
  }
  else {
    if(node) {
      if(*str != '/')
        addr->net = atow(str);
      addr->node = atow(node+1);
    }
    else {
      if(point != str)
        addr->node = atow(str);
    }
  }
  if(point)
    addr->point = atow(point+1);

  return domain;
}


//  ------------------------------------------------------------------

static char* parse_address(char* str, Addr* addr, Addr* mainaka) {

  char* domain = NULL;

  str = strskip_wht(str);

  if(isdigit(*str) or *str == '.' or macro_addr(str, _TEST, addr)) {

    char* net = strchr(str, ':');
    char* node = strchr(str, '/');
    char* point = strchr(str, '.');
    domain = strchr(str, '@');
    if(domain and point)
      if((dword)point > (dword)domain)
        point = NULL;

    if(net) {
      macro_addr(str, _ZONE, addr);
      macro_addr(net+1, _NET, addr);
      if(node)
        macro_addr(node+1, _NODE, addr);  // zone:net/node
      else
        addr->node = mainaka->node;                // zone:net
    }
    else {
      addr->zone = mainaka->zone;
      if(node) {
        if(*str != '/')
          macro_addr(str, _NET, addr);      // net/node
        else
          addr->net = mainaka->net;                  // /node
        macro_addr(node+1, _NODE, addr);
      }
      else {
        if(point == str)
          addr->node = mainaka->node;                // .point
        else
          macro_addr(str, _NODE, addr);     // node.point
        addr->net = mainaka->net;
      }
    }
    if(point)
      macro_addr(point+1, _POINT, addr);
  }
  if(domain == NULL)
    domain = str+strlen(str);   // point at NUL char

  return(domain);
}


//  ------------------------------------------------------------------

static char* make_addr_str(char* str, Addr* addr, char* domain) {

  char* ptr = str;
  static char buf[20];

  *ptr = NUL;

  if(addr->zone) {
    if(addr->zone == GFTN_ALL)
      ptr = stpcpy(ptr, "*");
    else {
      sprintf(buf, "%u", addr->zone);
      ptr = stpcpy(ptr, buf);
    }
    *ptr++ = ':';
  }

  if(addr->net == GFTN_ALL)
    ptr = stpcpy(ptr, "*");
  else {
    sprintf(buf, "%u", addr->net);
    ptr = stpcpy(ptr, buf);
  }
  *ptr++ = '/';

  if(addr->node == GFTN_ALL)
    ptr = stpcpy(ptr, "*");
  else {
    sprintf(buf, "%u", addr->node);
    ptr = stpcpy(ptr, buf);
  }

  if(addr->point) {
    *ptr++ = '.';
    if(addr->point == GFTN_ALL)
      ptr = stpcpy(ptr, "*");
    else {
      sprintf(buf, "%u", addr->point);
      ptr = stpcpy(ptr, buf);
    }
  }

  if(domain and *domain) {
    *ptr++ = '@';
    ptr = stpcpy(ptr, domain);
  }

  *ptr = NUL;

  return(str);
}


//  ------------------------------------------------------------------
//  Compare two nodes by name/address/file/pos

static bool cmp_nnlsts(_GEIdx A, _GEIdx B) {

  int cmp;

  if((cmp = stricmp(A.name, B.name)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.zone, B.addr.zone)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.net, B.addr.net)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.node, B.addr.node)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.point, B.addr.point)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.pos, B.pos)) != 0)
    return(cmp < 0);
  return false;
}


//  ------------------------------------------------------------------
//  Compare two nodes by address/name/file/pos

static bool cmp_anlsts(_GEIdx A, _GEIdx B) {

  int cmp;

  if((cmp = CmpV(A.addr.zone, B.addr.zone)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.net, B.addr.net)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.node, B.addr.node)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.addr.point, B.addr.point)) != 0)
    return(cmp < 0);
  if((cmp = stricmp(A.name, B.name)) != 0)
    return(cmp < 0);
  if((cmp = CmpV(A.pos, B.pos)) != 0)
    return(cmp < 0);
  return false;
}


//  ------------------------------------------------------------------

static char* CvtName(char* inp) {

  char buf[300];
  char* p;
  char* q;

  // Convert underlines to spaces

  p = inp;
  while(*p)
    if(*(p++) == '_')
      *(p-1) = ' ';

  // Strip leading spaces

  p = inp;
  while(isspace(*p))
    p++;
  q = &inp[strlen(p)-1];

  // Strip trailing spaces

  while(isspace(*q))
    *q-- = NUL;

  // Search for last space or point

  while(*q != ' ' and *q != '.' and q > p)
    q--;

  // If last char is a point, find last space instead

  if(*(q+1) == 0)
    while(*q != ' ' and q > p)
      q--;

  // Exchange last name and first name(s)

  if(p != q) {
    strcpy(stpcpy(buf, q+1), ", ");
    *(q+(*q == '.' ? 1 : 0)) = 0;
    strcat(buf, p);
    strcpy(inp, buf);
  }
  struplow(inp);
  return inp;
}


//  ------------------------------------------------------------------

#ifdef GOLDNODE_STATS
void calc_statistic(FILE* ofp, int* observation, float N) {

  int i;
  float mean = 0.0;
  float sumfrekvens = 0.0;
  float varians = 0.0;

  //              12   12345   12345   123456   123456789012
  fprintf(ofp, ".---------------------------------------------.\n");
  fprintf(ofp, "|   x |  h(x) |  f(x) | x*f(x) | (x-m)^2*f(x) |\n");
  fprintf(ofp, "|-----+-------+-------+--------+--------------|\n");

  for(i=0; i<100; i++) {
    float x = i;
    if(observation[i]) {
      float hyppighed = observation[i];
      float frekvens = hyppighed / N;
      mean += x * frekvens;
      sumfrekvens += frekvens;
    }
  }

  for(i=0; i<100; i++) {
    float x = i;
    if(observation[i]) {
      float hyppighed = observation[i];
      float frekvens = hyppighed / N;
      float vartmp =  (x-mean)*(x-mean)*frekvens;
      varians += vartmp;
      fprintf(ofp, "| %3i | %5i | %5.3f | %6.3f | %12.3f | \n", i, observation[i], frekvens, x*frekvens, vartmp);
    }
  }

  fprintf(ofp, "|-----+-------+-------+--------+--------------|\n");
  fprintf(ofp, "| sum | %5.0f | %5.3f | %5.3f | %12.3f |\n", N, sumfrekvens, mean, varians);
  fprintf(ofp, "`---------------------------------------------'\n");
  fprintf(ofp, "\n");
  fprintf(ofp, "Mean: %.1f\n", mean);
  fprintf(ofp, "Variance = %.1f\n", varians);
  fprintf(ofp, "Standard deviation = %.1f\n", sqrt(varians));
  fprintf(ofp, "\n");
}
#endif


//  ------------------------------------------------------------------
//  some useful string operations

inline void index_line(char* p, char* ptrs[5]) {

  for(int i=0; i<5; i++) {
    char* q = p;
    while(*q != ',' and *q) {
      if(*q == '_')
        *q = ' ';
      q++;
    }
    if(*q) {
      ptrs[i] = p;
      *q++ = NUL;
      p = q;
    }
  }
}


//  ------------------------------------------------------------------
//  Read the nodelists and userlists

static void read_nodelists() {

  long pos;
  FILE* lfp;
  char* ptr;
  _GEIdx nlst;
  Addr nlstz;
  char buf[512], buf2[100];
  int point;
  uint line, realfno;
  size_t no, nodes;
  const char* name;
  char* lp[5];
  geidxlist nodeidx;
  stamp_iter fno;
  addr_iter zno;

  nodes = 0;

  fast_printf("\n* Compiling nodelists:\n");

  // Delete the current indexfiles so they don't take up space
  remove(addrindex.c_str());
  remove(nodeindex.c_str());

  // Compile nodelists
  for(realfno=0, fno=nodelist.begin(), zno=nodezone.begin(); fno != nodelist.end(); fno++, zno++) {

    if(nodes < maxnodes) {

      lfp = fsopen(fno->fn, "rb", sh_mod);
      if(lfp) {

        setvbuf(lfp, NULL, _IOFBF, 32000);
        fno->ft = GetFiletime(fno->fn);

        // Initialize for each nodelist file
        no = 0;
        pos = 0;
        line = 0;
        point = YES;
        nlst.reset();
        nlstz = nlst.addr = *zno;
        name = CleanFilename(fno->fn);

        // Read all nodes
        while(fgets(buf, sizeof(buf), lfp)) {
          line++;

          // Break out if eof-marker is found
          if(*buf == '\x1A')
            break;

          // Note file position
          nlst.pos = pos;

          // Get line length and fix possible errors
          uint llen = strlen(buf);
          ptr = buf+llen-1;
          while(llen and not (*ptr == '\r' or *ptr == '\n' or *ptr == '\x1A')) {
            buf[llen] = ' ';
            fast_printf("\r* |-%-12s     Warning line %u - Invalid NUL char encountered.\n", name, line);
            llen = strlen(buf);
            ptr = buf+llen-1;
          }
          pos += llen;

          // Skip whitespace
          ptr = buf;
          while(isspace(*ptr))
            ptr++;

          if(*ptr != ';' and *ptr) {

            // First test for FD pvt extension
            if(toupper(*ptr) == 'B') {   // Boss
              nlst.addr.reset();
              parse_address(ptr+5, &nlst.addr, &nlstz);
              point = YES;
              continue;
            }

            // Test for Goldware extension
            if(isdigit(*ptr)) {
              nlst.addr.reset();
              parse_address(ptr+5, &nlst.addr, &nlstz);
              point = YES;
            }

            // Hold,32,TriCom,Hornbaek,Lars_Joergensen,45-12345678,2400,XX

            // Form the full node address
            index_line(ptr, lp);

            // NOTE: I use the fact that the third letter in lp[0] is unique
            //       for all valid attrs to speed up processing

            switch(*lp[0] ? toupper(lp[0][2]) : 0) {
              case 'N':   // zone
                nlst.addr.zone = nlst.addr.net = atow(lp[1]);
                nlst.addr.node = nlst.addr.point = 0;
                point = NO;
                break;

              case 'G':   // Region
                nlst.addr.net = atow(lp[1]);
                nlst.addr.node = nlst.addr.point = 0;
                point = NO;
                if(nlst.addr.net >= 10000)
                  continue;
                break;

              case 'S':   // Host
                {
                  nlst.addr.net = atow(lp[1]);
                  nlst.addr.node = nlst.addr.point = 0;
                  point = NO;
                  Addr a;
                  fast_parse_addr(lp[2],&a);
                  if(a.net) {                   // Is POINTS24 format ?
                    nlst.addr.net = a.net;
                    nlst.addr.node = a.node;
                    nlst.addr.point = 0;
                    point = YES;
                  }
                }
                break;

              case 'B':   // Hub
                nlst.addr.node = atow(lp[1]);
                nlst.addr.point = 0;
                point = NO;
                break;

              case 'I':   // point
                nlst.addr.point = atow(lp[1]);
                break;

              case 'T':   // Pvt
              case 'W':   // Down
              case 'L':   // Hold
              default:
                if(point)
                  nlst.addr.point = atow(lp[1]);
                else {
                  nlst.addr.node = atow(lp[1]);
                  nlst.addr.point = 0;
                }
                break;
            }

            if(ISTWIRLY(no)) {
              fast_printf("\r* \\--%-12s     Zone %-5u  Net %-5u  Nodes %6lu", name, nlst.addr.zone, nlst.addr.net, (ulong)no);
            }

            bool include = true;

            // Check address against the exclude masks
            for(addr_iter n=excludenode.begin(); n != excludenode.end(); n++) {
              if(match_addr_mask(&(*n), &nlst.addr)) {
                include = false;
                break;
              }
            }

            // Check address against the include masks
            if(not include) {
              for(addr_iter n=includenode.begin(); n != includenode.end(); n++) {
                if(match_addr_mask(&(*n), &nlst.addr)) {
                  include = true;
                  break;
                }
              }
            }

            if(include) {   // Address was okay

              // Convert name to Goldware standard
              strxcpy(nlst.name, CvtName(lp[4]), sizeof(nlst.name));

              // Prepare the rest
              nlst.pos |= ((((dword)realfno) << 24) & 0xFF000000L);

              // Append to end of list
              nodeidx.push_back(nlst);
              ++nodes;

              // Count the node
              no++;

              // Stop if limit is reached
              if(nodes >= maxnodes)
                break;
            }
          }
        }

        fast_printf("\r* %c--%-12s   Nodes read: %6lu    Total read: %6lu", fno==nodelist.end()-1?'\\':'|', name, (ulong)no, (ulong)nodes);

        if(nodes >= maxnodes) {
          fast_printf("  (Limit reached)\n");
        }
        else {
          fast_printf("         \n");
        }

        fclose(lfp);
        ++realfno;
      }
      else {
        fast_printf("Error Opening nodelist %s!\n", fno->fn);
        *(fno->fn) = NUL;
      }
    }
  }

  // Compile userlists
  if(userlist.size()) {
    fast_printf("\n* Compiling userlists:\n");
  }

  for(fno=userlist.begin(), zno=userzone.begin(); fno != userlist.end() and nodes < maxnodes; fno++, zno++) {

    no = 0;

    lfp = fsopen(fno->fn, "rt", sh_mod);
    if(lfp) {

      setvbuf(lfp, NULL, _IOFBF, 32000);

      name = CleanFilename(fno->fn);

      if(nodes < maxnodes) {

        while(fgets(buf, sizeof(buf), lfp)) {

          // Get node data
          strbtrim(buf);
          ptr = buf + strlen(buf) - 1;
          while(*ptr != ' ')
            ptr--;
          nlst.reset();
          nlst.addr = *zno;
          fast_parse_addr(ptr+1, &nlst.addr);
          *ptr = NUL;
          strbtrim(buf);

          // Convert "lastname, firstname" to "firstname lastname"
          ptr = strchr(buf, ',');
          if(ptr) {
            *ptr++ = NUL;
            strxmerge(buf2, 100, strskip_wht(ptr), " ", buf, NULL);
            ptr = buf2;
          }
          else {
            ptr = buf;
          }

          // Convert name to Goldware standard
          strxcpy(nlst.name, CvtName(ptr), sizeof(nlst.name));

          bool include = true;

          // Check address against the exclude masks
          for(addr_iter n=excludenode.begin(); n != excludenode.end(); n++) {
            if(match_addr_mask(&(*n), &nlst.addr)) {
              include = false;
              break;
            }
          }

          // Check address against the include masks
          if(not include) {
            for(addr_iter n=includenode.begin(); n != includenode.end(); n++) {
              if(match_addr_mask(&(*n), &nlst.addr)) {
                include = true;
                break;
              }
            }
          }

          if(include) {   // Address was okay

            if(ISTWIRLY(nodes))
              fast_printf("\r* \\--%s: %6lu", name, (ulong)nodes);

            // Indicate userlist
            nlst.pos = (long)0xFFFFFFFFL;

            // Append to end of list
            nodeidx.push_back(nlst);
            ++nodes;

            // Count the node
            no++;

            // Stop if limit is reached
            if(nodes >= maxnodes)
              break;
          }
        }
      }

      fast_printf("\r* %c--%-12s     Nodes read: %6lu    Total read: %6lu", fno==userlist.end()-1?'\\':'|', name, (ulong)no, (ulong)nodes);

      if(nodes >= maxnodes) {
        fast_printf("  (Limit reached)\n");
      }
      else {
        fast_printf("         \n");
      }

      fclose(lfp);
    }
    else {
      fast_printf("Error Opening Userlist %s!\n", fno->fn);
    }
  }

  #ifdef GOLDNODE_STATS
  if(make_stats) {

    fast_printf("* Writing statistics to %s\n", statfilename);

    FILE *ofp = fopen(statfilename, "wt");
    if(ofp) {

      fprintf(ofp, "Nodename size statistics:\n");
      calc_statistic(ofp, statistic.nodename, nodes);

      fprintf(ofp, "\nLocation size statistics:\n");
      calc_statistic(ofp, statistic.location, nodes);

      fprintf(ofp, "\nSysopname size statistics:\n");
      calc_statistic(ofp, statistic.sysopname, nodes);

      fclose(ofp);
    }
  }
  else {
  #endif
    // At last, sort the nodes
    FILE *fp, *fido;
    geidxlist::iterator curr, prev;
    map<long, dword> namepos;

    // Sort by name
    fast_printf("\n* Sorting by name ");
    nodeidx.sort(cmp_nnlsts);

    // Write the name-sorted .GXN
    fp = fsopen(nodeindex.c_str(), "wb", sh_mod);
    if(fp) {
      name = CleanFilename(nodeindex.c_str());
      fido = NULL;
      if(fidouser)
        fido = fsopen(fidouserlst, "wt", sh_mod);
      if(fido == NULL) {
        fast_printf("\b, writing %s ", name);
        fidouser = false;
      }
      else {
        fast_printf("\b, writing %s and %s ", name, fidouserlst);
      }

      int nn = 0;
      dword nodenum = 0;
      for(prev = nodeidx.end(), curr = nodeidx.begin(); curr != nodeidx.end(); prev = curr++) {

        if(ISTWIRLY(nn++))
          twirly();

        if(ignoredups) {
          if(prev != nodeidx.end() && match_addr_mask(&curr->addr, &prev->addr)) {
            if(strieql(curr->name, prev->name)) {
              #ifdef DEBUG
              fast_printf("* Dupe: %d:%d/%d.%d %s\n",curr->addr.zone,curr->addr.net,curr->addr.node,curr->addr.point,curr->name);
              #endif
              nodeidx.erase(curr);
              curr = prev;
              ++dups;
              continue;
            }
          }
          fwrite(&(*curr), sizeof(_GEIdx), 1, fp);
        }
        else
          fwrite(&(*curr), sizeof(_GEIdx), 1, fp);
        namepos[curr->pos] = nodenum++;
        if(fidouser) {
          char buf[256];
          fprintf(fido, "%-36.36s%24.24s\n", curr->name, make_addr_str(buf, &curr->addr, ""));
        }
      }
      if(fido)
        fclose(fido);
      fclose(fp);
    }

    // Sort by address
    fast_printf(" \n* Sorting by node ");
    nodeidx.sort(cmp_anlsts);

    // Write the address-sorted .GXA
    fp = fsopen(addrindex.c_str(), "wb", sh_mod);
    if(fp) {
      name = CleanFilename(addrindex.c_str());
      fast_printf("\b, writing %s ", name);
      int nn = 0;
      for(curr = nodeidx.begin(); curr != nodeidx.end(); curr++) {
        if(ISTWIRLY(nn++))
          twirly();
        fwrite(&namepos[curr->pos], sizeof(dword), 1, fp);
      }
      fclose(fp);
    }

    // Write the list index in .GXL
    fp = fsopen(listindex.c_str(), "wt", sh_mod);
    if(fp) {
      name = CleanFilename(listindex.c_str());
      fast_printf(" \n* Writing %s\n", name);
      for(fno=nodelist.begin(); fno != nodelist.end(); fno++) {
        if(*(fno->fn))
          fprintf(fp, "%s %lu\n", fno->fn, fno->ft);
      }
      fclose(fp);
    }

    // Note compile time
    runtime = time(NULL) - runtime;

    if(dups) {
      fast_printf("\n* Total duplicate nodes: %6lu.\n", (ulong)dups);
    }
    fast_printf("\n* Nodelist compile completed. Compile time: %lu min, %lu sec.\n", (ulong)(runtime/60), (ulong)(runtime%60));
  #ifdef GOLDNODE_STATS
  }
  #endif
}


//  ------------------------------------------------------------------

static void check_nodelists(bool force) {

  FILE *fp;
  uint n;
  int compilen, compileu;
  static Path buf, newpath;

  // Find newest nodelists
  for(n=0,compilen=0; n<nodelist.size(); n++) {

    strcpy(buf, CleanFilename(nodelist[n].fn));
    char *ext = strchr(buf, '.');
    if(ext and ((atoi(ext+1) == 999) or (ext[1] == '*'))) {
      extractdirname(newpath, nodelist[n].fn);
      int extpos = ext-buf+1;
      strcpy(ext, ".*");
      gposixdir f(newpath);
      const gdirentry *de;
      time_t listtime = 0;
      bool listdefined = false;
      while((de = f.nextentry(buf)) != NULL)
        if(atoi(de->name.c_str()+extpos)) {
          if(not listdefined or (de->stat_info.st_mtime-listtime > 0)) {
            listtime = de->stat_info.st_mtime;
            listdefined = true;
            strxmerge(nodelist[n].fn, sizeof(Path), f.fullpath(), "/", de->name.c_str(), NULL);
          }
        }
    }
  }

  // Get timestamps from .GXL file
  fp = fsopen(listindex.c_str(), "rt", sh_mod);
  if(fp) {
    while(fgets(buf, sizeof(buf), fp)) {
      char* key;
      char* val=buf;
      getkeyval(&key, &val);
      key = strxcpy(newpath, strbtrim(key), sizeof(Path));
      for(n=0; n<nodelist.size(); n++) {
        if(strieql(nodelist[n].fn, key)) {
          nodelist[n].ft = atol(val);
          break;
        }
      }
      for(n=0; n<userlist.size(); n++) {
        if(strieql(userlist[n].fn, key)) {
          userlist[n].ft = atol(val);
          break;
        }
      }
    }
    fclose(fp);
  } else
    perror("error opening .gxl file");

  // Check nodelists
  for(n=0,compilen=0; n<nodelist.size(); n++) {
    if(abs(long(GetFiletime(nodelist[n].fn) - nodelist[n].ft)) > 1) {
      nodelist[n].fc = YES;
      compilen++;
    }
  }

  if(compilen) {
    fast_printf("* %u new nodelist file%s found.\n", compilen, compilen==1?"":"s");
  }
  else if(nodelist.size()) {
    fast_printf("* The nodelist file%s up-to-date.\n", nodelist.size()==1?" is":"s are");
  }

  // Check userlists
  for(n=0,compileu=0; n<userlist.size(); n++) {
    if(abs(long(GetFiletime(userlist[n].fn) - userlist[n].ft)) > 1) {
      userlist[n].fc = YES;
      compileu++;
    }
  }

  if(compileu) {
    fast_printf("* %u new userlist file%s found.\n", compileu, compileu==1?"":"s");
  }
  else if(userlist.size()) {
    fast_printf("* The userlist file%s up-to-date.\n", userlist.size()==1?" is":"s are");
  }

  if(force or compilen or compileu)
    read_nodelists();
}


//  ------------------------------------------------------------------

static void fatal_error(const char* what) {

  fast_printf(what);
  fast_printf("\n");
  exit(5);
}


//  ------------------------------------------------------------------

static int do_if(char* val) {

  if(strieql(val, "OS/2") OR strieql(val, "OS2")) {
    #ifdef __OS2__
    return true;
    #else
    return false;
    #endif
  }
  else if(strieql(val, "NT") OR strieql(val, "W32") OR strieql(val, "WIN32")) {
    #ifdef __WIN32__
    return true;
    #else
    return false;
    #endif
  }
  else if(strieql(val, "386")) {
    #if defined(__MSDOS__)
    return true;
    #else
    return false;
    #endif
  }
  else if(strieql(val, "DOS")) {
    #ifdef __MSDOS__
    return true;
    #else
    return false;
    #endif
  }
  else if(strieql(val, "LINUX") or strieql(val, "UNIX")) {
    #ifdef __UNIX__
    return true;
    #else
    return false;
    #endif
  }
  else if(strieql(val, "INOS2")) {
    #if defined(__OS2__)
    return true;
    #elif defined(__GNUC__)
    return false;
    #else
    return _osmajor >= 10;
    #endif
  }
  else if(strieql(val, "FIREBIRD")) {
    return true;
  }
  else if(strieql(val, "YES") OR strieql(val, "TRUE") OR strieql(val, "ON"))
    return true;
  return atoi(val) != 0;
}


//  ------------------------------------------------------------------

static int parse_config(const char *__configfile, Addr& zoneaddr) {

  FILE* fp;
  char buf[512];
  char* ptr;
  char* key;
  word crc;
  char* value;
  char* value2;
  int _gotcond,line = 0;
  static int in_if = NO;
  static int in_else = NO;
  static int cond_status = YES;

  fp = fsopen(__configfile, "rt", sh_mod);
  if(fp) {
    while(fgets((ptr=buf), sizeof(buf), fp)) {
      line++;
      // Replace TABs with SPACEs
      ptr = strskip_wht(ptr);
      if(*ptr != ';' and *ptr) {
        crc=getkeyvalcrc(&key, &ptr);
        getkeyval(&value, &ptr);
        getkeyval(&value2, &ptr);

        _gotcond = YES;
        switch(crc) {
          case CRC_IF:
            if(in_if) {
              fast_printf("* %s: Misplaced IF at line %u. IF's cannot be nested.\n", __configfile, line);
            }
            in_if = YES;
            cond_status = do_if(value);
            break;
          case CRC_ELIF:
          case CRC_ELSEIF:
            if((not in_if) or in_else) {
              fast_printf("* %s: Misplaced ELIF/ELSEIF at line %u.\n", __configfile, line);
            }
            cond_status = do_if(value);
            break;
          case CRC_ELSE:
            if((not in_if) or in_else) {
              fast_printf("* %s: Misplaced ELSE at line %u.\n", __configfile, line);
            }
            in_else = YES;
            cond_status ^= YES;
            break;
          case CRC_ENDIF:
            if(not in_if) {
              fast_printf("* %s: Misplaced ENDIF at line %u.\n", __configfile, line);
            }
            in_if = in_else = NO;
            cond_status = YES;
            break;
	  default:
            _gotcond = NO;
            break;
        }

        if((not _gotcond) and cond_status) {
          switch(crc) {
            case CRC_NODEPATH:
              nodepath = value;
              break;
            case CRC_ADDRESS:
            case CRC_AKA:
              if(not zoneaddr.net) {
                parse_address(value, &zoneaddr, &zoneaddr);
                zoneaddr.point = 0;
              }
              break;
            case CRC_NODELIST:
              {
                Stamp ndl;
                Addr ndz;

                if(atoi(value2)) {
                  parse_address(value2, &ndz, &ndz);
                  if(ndz.zone == 0) {
                    ndz.zone  = ndz.node;
                    ndz.net   = 0;
                    ndz.node  = 0;
                  }
                }
                else
                  ndz = zoneaddr;
                ndz.point = 0;
                ndl.ft = (dword)-1;
                ndl.fc = NO;
                strcpy(ndl.fn, value);
                nodelist.push_back(ndl);
                nodezone.push_back(ndz);
              }
              break;
            case CRC_USERLIST:
              {
                Stamp ndl;
                Addr ndz;

                if(atoi(value2)) {
                  parse_address(value2, &ndz, &ndz);
                  if(ndz.zone == 0) {
                    ndz.zone  = ndz.node;
                    ndz.net   = 0;
                    ndz.node  = 0;
                  }
                }
                else
                  ndz = zoneaddr;
                ndz.point = 0;
                ndl.ft = (dword)-1;
                ndl.fc = NO;
                strcpy(ndl.fn, value);
                userlist.push_back(ndl);
                userzone.push_back(ndz);
              }
              break;
            case CRC_EXCLUDENODES:
              {
                Addr exn;
                parse_address(value, &exn, &zoneaddr);
                excludenode.push_back(exn);
              }
              break;
            case CRC_INCLUDENODES:
              {
                Addr inn;
                parse_address(value, &inn, &zoneaddr);
                includenode.push_back(inn);
              }
              break;
            case CRC_SHAREMODE:
              if(atoi(value))
                sh_mod = atoi(value);
              else if(striinc("NO", value))
                sh_mod = 0;
              break;
            case CRC_INCLUDE:
              if(not parse_config(value,zoneaddr))     // NOTE! This is a recursive call!
                fast_printf("* Could not read configuration file '%s' !\n",value);
              break;
	  default:
              break;
          }
        }
      }
    }

    fclose(fp);
    return(YES);
  }
  else {
    return(NO);
  }
}


//  ------------------------------------------------------------------

static bool ExistCfg(char* path, char* file) {

  bool found = fexist(AddPath(path, file));
  if(found)
    strcat(path, file);
  return found;
}


//  ------------------------------------------------------------------

static bool FindCfg(char* path) {

  bool found = false;
  
  if(!is_dir(path)) {
    if(fexist(path))
      return true;
    else
      return false;
  }
  AddBackslash(path);
  #if defined(__OS2__)
  found = ExistCfg(path, "ged2.cfg");
  #elif defined(__WIN32__)
  found = ExistCfg(path, "gedw32.cfg");
  #endif
  if(not found)
    found = ExistCfg(path, "golded.cfg");
  return found;
}


//  ------------------------------------------------------------------

static bool read_config(const char *cfg, const char *argv_0) {

  Addr zoneaddr;
  Path buf;

  bool found = (*cfg != NUL) ? true : false;
  if(not found) {
    // Look for configfilename in the environment
    const char *ptr = getenv("GOLDNODE");
    #if defined(__OS2__)
    if(not(ptr and *ptr))
      ptr = getenv("GED2");
    #elif defined(__WIN32__)
    if(not(ptr and *ptr))
      ptr = getenv("GEDW32");
    #endif
    if(not(ptr and *ptr))
      ptr = getenv("GOLDED");
    if(not(ptr and *ptr))
      ptr = getenv("GED");
    if(ptr and *ptr) {
      strxcpy(buf, ptr, sizeof(buf));
      found = FindCfg(buf);
    }

    // Get it in current directory
    if(not found) {
      getcwd(buf, sizeof(buf));
      found = FindCfg(buf);
    }

    // Get it where the the .EXE file is
    if(not found) {
      extractdirname(buf, argv_0);
      found = FindCfg(buf);

      // If we still could not find config name...
      if(not found)
        strcat(buf, "golded.cfg");
    }
  }
  else
    strxcpy(buf, cfg, sizeof(Path));

  nodelist.clear();
  nodezone.clear();
  userlist.clear();
  userzone.clear();
  if(not parse_config(buf, zoneaddr)) {
    errorlevel = 1;
    return false;
  }

  if(nodelist.empty() and userlist.empty() == 0)
    fatal_error("* Error: No NODELISTs or USERLISTs defined!");

  if(zoneaddr.net == 0)
    fatal_error("* Error: No ADDRESS or AKAs defined!");

  if(nodepath.empty())
    nodepath = getcwd(buf, sizeof(buf));

  AddBackslash(nodepath);
  MakePathname(listindex, nodepath, "goldnode.gxl");
  MakePathname(nodeindex, nodepath, "goldnode.gxn");
  MakePathname(addrindex, nodepath, "goldnode.gxa");
  size_t n;
  for(n=0; n<nodelist.size(); n++)
    MakePathname(nodelist[n].fn, nodepath.c_str(), nodelist[n].fn);
  for(n=0; n<userlist.size(); n++)
    MakePathname(userlist[n].fn, nodepath.c_str(), userlist[n].fn);

  return true;
}


//  ------------------------------------------------------------------

static void run_gn(int argc, char* argv[]) {

  int n;
  char* ptr;
  bool force=false, conditional=false;

  // Note start time
  runtime = time(NULL);

  const char *cfg = "";

  for(n=1; n<argc; n++) {
    if(strchr("-", *argv[n])) {
      ptr = argv[n]+2;
      if(*ptr == '=')
        ptr++;
      switch(toupper(argv[n][1])) {

        case 'C':
          conditional = true;
          break;

        case 'D':
          ignoredups = true;
          break;

        case 'F':
          force = true;
          break;

        case 'Q':
          quiet = true;
          break;

        #ifdef GOLDNODE_STATS
        case 'T':
          make_stats = true;
          break;
        #endif

        case 'U':
          fidouser = true;
          strcpy(fidouserlst, ptr);
          break;

      }
    }
    else
      cfg = argv[n];
  }

  fast_printf(
    __GPID__ " " __GVER__ " Nodelist Compiler.\n"
    "Copyright (C) 1990-1999 Odinn Sorensen\n"
    "Copyright (C) 1999-2000 Alexander S. Aganichev\n"
    "-------------------------------------------------------------------------------\n"
    "\n"
  );

  if(not(force or conditional)) {
    fast_printf(
      "Commandline syntax: %s [-options] [configfile]\n"
      "\n"
      "[-options] =   -C        Conditional compile.\n"
      "               -F        Forced compile.\n"
      "               -D        Remove duplicate nodes from index while compiling.\n"
      "               -Q        Quiet compile. No screen output improves speed.\n"
      "               -S<size>  Set max size of a name in the index.\n"
      "               -U<file>  Create sorted FIDOUSER.LST userlist file.\n"
      #ifdef GOLDNODE_STATS
      "               -T        Make statistics.\n"
      #endif
      ""
      "[configfile] =           The path AND filename of GOLDED.CFG\n"
      "                         configuration file to read.\n"
      "\n",
      CleanFilename(argv[0])
    );
  }
  else {

    if(force)
      fast_printf("* Forced compile.\n");

    if(read_config(cfg, argv[0])) {
      if(force or conditional)
        check_nodelists(force);
    }
    else {
      fast_printf("\nCould not find the configuration file!\n");
    }
  }
}


//  ------------------------------------------------------------------

int main(int argc, char *argv[]) {

  throw_init();

  // set locale
  setlocale(LC_CTYPE, "");
  #if defined(GUTLOS_FUNCS)
  g_init_os(0);
  #endif

  #ifdef GOLDNODE_STATS
  memset(&statistic, 0, sizeof(nl_stat));
  #endif

  run_gn(argc, argv);

  #if defined(GUTLOS_FUNCS)
  g_deinit_os();
  #endif

  THROW_CHECK();

  return errorlevel;
}


//  ------------------------------------------------------------------