
//  ------------------------------------------------------------------
//  The Goldware Utilities.
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2001 Alexander S. Aganichev
//  Copyright (C) 2005 Stas Degteff
//  ------------------------------------------------------------------
//  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.
//  ------------------------------------------------------------------

// using namespace std;

#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>
#include <iomanip>
#include <iostream>
#include <fstream>

#define GOLDNODE_STATS 1

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

#include <golded3.h>
#include <gmemdbg.h>

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

#define __GVER_NAME__ "GoldNode+"
#define __GVER_SHORTNAME__ "GN"
#define __gver_name__ __GVER_NAME__
#define __gver_shortname__ __GVER_SHORTNAME__

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

const size_t maxnodes = 262000;


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

typedef std::vector<Addr>::iterator addr_iter;
typedef std::vector<Stamp>::iterator stamp_iter;

#if (_MSC_VER == 1200)
class _geidxlist : public std::list<_GEIdx>
{
public:
    void sort()
    {
        if (2 <= size())
        {
            const size_t _MAXN = 15;
            _Myt _X(allocator), _A[_MAXN + 1];
            size_t _N = 0;

            while (!empty())
            {
                _X.splice(_X.begin(), *this, begin());
                size_t _I;

                for (_I = 0; _I < _N && !_A[_I].empty(); ++_I)
                {
                    _A[_I].merge(_X);
                    _A[_I].swap(_X);
                }

                if (_I == _MAXN) _A[_I].merge(_X);
                else
                {
                    _A[_I].swap(_X);
                    if (_I == _N) ++_N;
                }
            }

            for (int _NN = _N; _NN >= 0; _NN--)
                merge(_A[_NN]);
        }
    }
};

typedef _geidxlist geidxlist;
#else
typedef std::list<_GEIdx> geidxlist;
#endif

// Nodelists
std::vector<Stamp> nodelist; // nodelist files,stamps,update marker
std::vector<Addr> nodezone;  // nodelist zones
std::vector<Stamp> userlist; // Userlist files,stamps,update marker
std::vector<Addr> userzone;  // Userlist zones
std::vector< std::pair<std::string, std::string> > mappath;

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

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

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

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

static std::string nodepath;                // Path to the nodelist files

static time32_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

char* _MapPath(char* map, bool reverse = false);

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

#define TWIRLY_FACTOR 511
#define ISTWIRLY(n) (not quiet and (((n)&TWIRLY_FACTOR)==0))

static void twirly()
{

    static int n=0;

    n = (n + 1) % 4;
    switch(n)
    {
    case 0:
        std::cout << "|\b" << std::flush;
        break;
    case 1:
        std::cout << "/\b" << std::flush;
        break;
    case 2:
        std::cout << "-\b" << std::flush;
        break;
    case 3:
        std::cout << "\\\b" << std::flush;
        break;
    }
}


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

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(point > 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(point > 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(const _GEIdx &A, const _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(const _GEIdx &A, const _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;
}


//  ------------------------------------------------------------------
#if defined(_MSC_VER)
enum { sort_by_address=0, sort_by_name } static sort_type;
bool operator<(const _GEIdx &A, const _GEIdx &B)
{
    if (sort_type==sort_by_address)
        return cmp_anlsts(A, B);
    else
        return cmp_nnlsts(A, B);
}
#endif
//  ------------------------------------------------------------------

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(std::ofstream &ofp, int* observation, float N)
{

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

    //         12   12345   12345   123456   123456789012
    ofp << ".---------------------------------------------." << std::endl
        << "|   x |  h(x) |  f(x) | x*f(x) | (x-m)^2*f(x) |" << std::endl
        << "|-----+-------+-------+--------+--------------|" << std::endl;

    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;
            ofp << "| " << std::setw(3) << i << " | " << std::setw(5) << observation[i] << " | " << std::setprecision(3) << std::setw(5) << frekvens << " | " << std::setw(6) << x*frekvens << " | " << std::setw(12) << vartmp << " | " << std::endl;
        }
    }

    ofp << "|-----+-------+-------+--------+--------------|" << std::endl
        << "| sum | " << std::setprecision(0) << std::setw(5) << N << " | " << std::setprecision(3) << std::setw(5) << sumfrekvens << " | " << std::setw(6) << mean << " | " << std::setw(12) << varians << " |" << std::endl
        << "`---------------------------------------------'" << std::endl
        << std::endl
        << "Mean: " << std::setprecision(1) << mean << std::endl
        << "Variance = " << varians << std::endl
        << "Standard deviation = " << sqrt(varians) << std::endl
        << std::endl;
}
#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()
{
    gfile lfp;
    long pos;
    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;

    if(not quiet) std::cout << std::endl << "* Compiling nodelists:" << std::endl;

    // 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.Fopen(fno->fn, "rb", sh_mod);
            if (lfp.isopen())
            {
                lfp.SetvBuf(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 (lfp.Fgets(buf, sizeof(buf)))
                {
                    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] = ' ';
                        if(not quiet)
                        {
                            int len = 16-strlen(name);
                            std::cout << "\r* |--" << name << std::setw((len > 0) ? len : 1) << " " << "Warning line " << line << " - Invalid NUL char encountered." << std::endl;
                        }
                        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))
                        {
                            int len = 16-strlen(name);
                            std::cout << "\r* \\--" << name << std::setw((len > 0) ? len : 1) << " " << "Zone " << nlst.addr.zone << "   \tNet " << nlst.addr.net << "   \tNodes " << (uint32_t)no << "        " << std::flush;
                        }

                        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;
                        }
                    }
                }

                if(not quiet)
                {
                    int len = 16-strlen(name);
                    std::cout << "\r* " << ((fno == nodelist.end()-1) ? '\\' : '|') << "--" << name << std::setw((len > 0) ? len : 1) << " " << "Nodes read: " << (uint32_t)no << "\tTotal read: " << (uint32_t)nodes << ((nodes >= maxnodes) ? " (Limit reached)" : "                ") << std::endl;
                }

                lfp.Fclose();
                ++realfno;
            }
            else
            {
                if(not quiet) std::cout << "Error opening nodelist " << fno->fn << '!' << std::endl;
                *(fno->fn) = NUL;
            }
        }
    }

    // Compile userlists
    if(userlist.size())
    {
        if(not quiet) std::cout << std::endl << "* Compiling userlists:" << std::endl;
    }

    pos = 0;

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

        no = 0;

        lfp.Fopen(fno->fn, "rt", sh_mod);
        if (lfp.isopen())
        {
            lfp.SetvBuf(NULL, _IOFBF, 32000);

            name = CleanFilename(fno->fn);

            if (nodes < maxnodes)
            {
                while (lfp.Fgets(buf, sizeof(buf)))
                {
                    // 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))
                        {
                            int len = 16-strlen(name);
                            std::cout << "\r* \\--" << name << std::setw((len > 0) ? len : 1) << " " << "Nodes: " << (uint32_t)nodes << "        " << std::flush;
                        }

                        // Indicate userlist
                        nlst.pos = (long)0xFF000000L | (pos++);

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

                        // Count the node
                        no++;

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

            if(not quiet)
            {
                int len = 16-strlen(name);
                std::cout << "\r* " << ((fno == userlist.end()-1) ? '\\' : '|') << "--" << name << std::setw((len > 0) ? len : 1) << " " << "Nodes read: " << (uint32_t)no << "\tTotal read: " << (uint32_t)nodes << ((nodes >= maxnodes) ? " (Limit reached)" : "                ") << std::endl;
            }

            lfp.Fclose();
        }
        else
        {
            if(not quiet) std::cout << "Error opening userlist " << fno->fn << '!' << std::endl;
        }
    }

#ifdef GOLDNODE_STATS
    if(make_stats)
    {

        if(not quiet) std::cout << "* Writing statistics to " << statfilename << NL;

        std::ofstream ofp(statfilename);
        if(not ofp)
        {
            if(not quiet) std::cout << "Error opening statfile " << statfilename << '!' << NL;
        }
        else
        {
            ofp << "Nodename size statistics:" << std::endl;
            calc_statistic(ofp, statistic.nodename, nodes);

            ofp << std::endl << "Location size statistics:" << std::endl;
            calc_statistic(ofp, statistic.location, nodes);

            ofp << std::endl << "Sysopname size statistics:" << std::endl;
            calc_statistic(ofp, statistic.sysopname, nodes);
        }
    }
    else
    {
#endif
        // At last, sort the nodes
        geidxlist::iterator curr, prev;
        std::map<long, dword> namepos;

        // Sort by name
        if(not quiet) std::cout << NL << "* Sorting by name " << std::flush;
#if defined(_MSC_VER)
        sort_type = sort_by_name;
        nodeidx.sort();
#else
        nodeidx.sort(cmp_nnlsts);
#endif
        // Write the name-sorted .GXN
        gfile fp(nodeindex.c_str(), "wb", sh_mod);
        if (fp.isopen())
        {
            name = CleanFilename(nodeindex.c_str());

            gfile fido;
            if (fidouser) fido.Fopen(fidouserlst, "wt", sh_mod);

            if (!fido.isopen())
            {
                if(not quiet) std::cout << "\b, writing " << name << ' ' << std::flush;
                fidouser = false;
            }
            else
            {
                if(not quiet) std::cout << "\b, writing " << name << " and " << fidouserlst << ' ' << std::flush;
            }

            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
                            if(not quiet) std::cout << "* Dupe: " << curr->addr.zone << ':' << curr->addr.net << '/' << curr->addr.node << '.' << curr->addr.point << ' ' << curr->name << NL;
#endif
                            nodeidx.erase(curr);
                            curr = prev;
                            ++dups;
                            continue;
                        }
                    }
                    fp.Fwrite(&(*curr), sizeof(_GEIdx));
                }
                else
                    fp.Fwrite(&(*curr), sizeof(_GEIdx));

                namepos[curr->pos] = nodenum++;
                if (fidouser)
                {
                    char buf[256];
                    fido.Printf("%-36.36s%24.24s\n", curr->name, make_addr_str(buf, &curr->addr, ""));
                }
            }

            fp.Fclose();
        }

        // Sort by address
        if(not quiet) std::cout << ' ' << NL "* Sorting by node " << std::flush;
#if defined(_MSC_VER)
        sort_type = sort_by_address;
        nodeidx.sort();
#else
        nodeidx.sort(cmp_anlsts);
#endif

        // Write the address-sorted .GXA
        fp.Fopen(addrindex.c_str(), "wb", sh_mod);
        if (fp.isopen())
        {
            name = CleanFilename(addrindex.c_str());
            if(not quiet) std::cout << "\b, writing " << name << ' ' << std::flush;
            int nn = 0;
            for(curr = nodeidx.begin(); curr != nodeidx.end(); curr++)
            {
                if(ISTWIRLY(nn++))
                    twirly();
                fp.Fwrite(&namepos[curr->pos], sizeof(dword));
            }
            fp.Fclose();
        }

        // Write the list index in .GXL
        fp.Fopen(listindex.c_str(), "wt", sh_mod);
        if (fp.isopen())
        {
            name = CleanFilename(listindex.c_str());
            if (not quiet) std::cout << ' ' << NL "* Writing " << name << std::endl;
            for (fno=nodelist.begin(); fno != nodelist.end(); fno++)
            {
                if (*(fno->fn))
                    fp.Printf("%s %u\n", fno->fn, fno->ft);
            }

            fp.Fclose();
        }

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

        if(not quiet)
        {
            if(dups)
            {
                std::cout << NL "* Total duplicate nodes: " << (uint32_t)dups << '.' << NL;
            }
            std::cout << NL "* Nodelist compile completed. Compile time: " << (uint32_t)(runtime/60) << " min, " << (uint32_t)(runtime%60) << " sec." << NL;
        }
#ifdef GOLDNODE_STATS
    }
#endif
}


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

static void check_nodelists(bool force)
{
    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;
            time32_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), newpath, de->name.c_str(), NULL);
                    }
                }
            strchg(nodelist[n].fn, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
        }
    }

    // Get timestamps from .GXL file
    gfile fp(listindex.c_str(), "rt", sh_mod);
    if (fp.isopen())
    {
        while (fp.Fgets(buf, sizeof(buf)))
        {
            char* key;
            char* val=buf;
            getkeyval(&key, &val);
            key = strxcpy(newpath, strbtrim(key), sizeof(Path));
            _MapPath(key);
            strchg(key, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
            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;
                }
            }
        }
        fp.Fclose();
    }
    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(not quiet)
    {
        if(compilen)
        {
            std::cout << "* " << compilen << " new nodelist file" << ((compilen == 1) ? "" : "s") << " found." NL;
        }
        else if(nodelist.size())
        {
            std::cout << "* The nodelist file" << ((nodelist.size() == 1) ? " is" : "s are") << " up-to-date." NL;
        }
    }

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

    if(not quiet)
    {
        if(compileu)
        {
            std::cout << "* " << compileu << " new userlist file" << ((compileu == 1) ? "" : "s") << " found." NL;
        }
        else if(userlist.size())
        {
            std::cout << "* The userlist file" << ((userlist.size() == 1) ? " is" : "s are") << " up-to-date." NL;
        }
    }

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


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

static void fatal_error(const char* what)
{

    if(not quiet) std::cout << what << NL;
    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") or strieql(val, "DOS") or strieql(val, "DPMI32"))
    {
#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, "SPELL"))
    {
#ifdef GCFG_SPELL_INCLUDED
        return true;
#else
        return false;
#endif
    }
    else if(strieql(val, "FIREBIRD"))
        return true;
    else if(strieql(val, "ASA") or strieql(val, "PLUS"))
        return true;
    else if(strieql(val, "YES") or strieql(val, "TRUE") or strieql(val, "ON"))
        return true;
    return !!atoi(val);
}


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

char* _MapPath(char* fmap, bool reverse)
{

    Path buf, cmap;

    strxcpy(cmap, fmap, sizeof(Path));
    if(reverse)
        strchg(cmap, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);

    std::vector< std::pair<std::string, std::string> >::iterator i;
    for(i = mappath.begin(); i != mappath.end(); i++)
    {
        const char* p = reverse ? i->second.c_str() : i->first.c_str();
        const char* q = reverse ? i->first.c_str() : i->second.c_str();
        if(strnieql(cmap, p, strlen(p)))
        {
            strxcpy(buf, fmap, sizeof(Path));
            strxmerge(fmap, sizeof(Path), q, buf+strlen(p), NULL);
            char sl1, sl2;
            const char* ptr;

            ptr = strpbrk(p, "/\\");
            sl1 = ptr ? *ptr : NUL;
            ptr = strpbrk(q, "/\\");
            sl2 = ptr ? *ptr : NUL;

            if(sl1 and sl2 and (sl1 != sl2))
                strchg(fmap, sl1, sl2);

            break;
        }
    }
    return fmap;
}


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

static int parse_config(const char *__configfile, Addr& zoneaddr)
{
    char buf[512];
    const char * const top_buf = buf+sizeof(buf);
    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;

    gfile fp(__configfile, "rt", sh_mod);
    if (fp.isopen())
    {
        while (fp.Fgets((ptr=buf), sizeof(buf)))
        {
            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)
                    {
                        if(not quiet) std::cout << "* " << __configfile << ": Misplaced IF at line " << line << ". IF's cannot be nested." << NL;
                    }
                    in_if = YES;
                    cond_status = do_if(value);
                    break;
                case CRC_ELIF:
                case CRC_ELSEIF:
                    if((not in_if) or in_else)
                    {
                        if(not quiet) std::cout << "* " << __configfile << ": Misplaced ELIF/ELSEIF at line " << line <<  '.' << NL;
                    }
                    cond_status = do_if(value);
                    break;
                case CRC_ELSE:
                    if((not in_if) or in_else)
                    {
                        if(not quiet) std::cout << "* " << __configfile << "Misplaced ELSE at line " << line <<  '.' << NL;
                    }
                    in_else = YES;
                    cond_status ^= YES;
                    break;
                case CRC_ENDIF:
                    if(not in_if)
                    {
                        if(not quiet) std::cout << "* " << __configfile << ": Misplaced ENDIF at line " << line << '.' << NL;
                    }
                    in_if = in_else = NO;
                    cond_status = YES;
                    break;
                default:
                    _gotcond = NO;
                    break;
                }

                if((not _gotcond) and cond_status)
                {
                    switch(crc)
                    {
                    case CRC_NODEPATH:
                        _MapPath(value);
                        PathCopy(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;
                        strschg_environ(value, top_buf-value);
                        _MapPath(value);
                        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;
                        strschg_environ(value, top_buf-value);
                        _MapPath(value);
                        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
                            sh_mod = GetYesno(value) ? SH_DENYNO : SH_COMPAT;
                        break;
                    case CRC_INCLUDE:
                        strschg_environ(value, top_buf-value);
                        _MapPath(value);
                        if(not parse_config(value,zoneaddr))     // NOTE! This is a recursive call!
                            if(not quiet) std::cout << "* Could not read configuration file " << value << '!' << NL;
                        break;
                    case CRC_MAPPATH:
                    {
                        std::pair<std::string, std::string> mapentry;

                        mapentry.first = value;
                        mapentry.second = value2;
                        mappath.push_back(mapentry);
                    }
                    break;
                    default:
                        break;
                    }
                }
            }
        }

        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);
    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();
    mappath.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];
    }

    if(not quiet)
    {
        std::cout << __gver_name__ << " - Nodelist Compiler for Golded+ v." << __gver_shortver__ << __gver_platform__ << __gver_postversion__ << "." NL
                  << "Copyright (C) 1990-1999 Odinn Sorensen" NL
                  << "Copyright (C) 1999-2001 Alexander S. Aganichev" NL
                  << "Copyright (C) 2005 Stas Degteff & Golded+ team" NL
                  << "-------------------------------------------------------------------------------\n" NL;
    }

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

        if(force)
            if(not quiet) std::cout << "* Forced compile." NL;

        if(read_config(cfg, argv[0]))
        {
            if(force or conditional)
                check_nodelists(force);
        }
        else
        {
            if(not quiet) std::cout << NL "Could not find the configuration file!" NL;
        }
    }
}


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

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;
}


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