//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Version 7 nodelist processing module.
//  Based on source code from Binkley 2.50.
//  V7+ support based on draft #2 by Thomas Waldmann.
//  ------------------------------------------------------------------

#include <gfilutil.h>
#include <gstrall.h>
#include <gutlmisc.h>
#include <gftnnlv7.h>


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

char v7nodeflags[16][9] =
{
    { "Hub" },
    { "Host" },
    { "Region" },
    { "Zone" },
    { "CM," },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" }
};


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

char v7modemtype[8][9] =
{
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" },
    { "" }
};


//  ------------------------------------------------------------------
//  Unpack a dense version of a symbol (base 40 polynomial)

static void v7unpack(char* src, char* dest, uint count)
{

    // This table has been modified to minimize searches
    //                     1234567890123456789012345678901234567890
    static char unwrk[] = " EANROSTILCHBDMUGPKYWFVJXZQ-\'0123456789";

    union
    {
        word  w;
        byte  c[2];
    } u;

    int i, j;
    char obuf[4];

    *dest = NUL;

    while(count)
    {

        u.c[0] = *src++;
        u.c[1] = *src++;

        count -= 2;

        for(j=2; j>=0; j--)
        {

            i = u.w % 40;
            u.w /= (word)40;
            obuf[j] = unwrk[i];
        }

        obuf[3] = NUL;

        strcat(dest, obuf);
    }
#ifdef DEBUG
    printf("{%s}", dest);
#endif
}


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

const char* ftn_version7_nodelist_index::namekey() const
{

    return key;
}


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

const ftn_addr& ftn_version7_nodelist_index::addrkey() const
{

    return *((ftn_addr*)key);
}


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

int ftn_version7_nodelist_index::namecmp() const
{

#ifdef DEBUG
    printf("[%s] [%s] ", searchname, namekey());
#endif

    const char* a = searchname;
    const char* b = namekey();
    int n = 1;
    int d;
    while(1)
    {
        d = g_tolower((uint8_t)*a) - g_tolower((uint8_t)*b);
        if((d != 0) or (*a == NUL) or (*b == NUL))
            break;
        a++;
        b++;
        n++;
    }
    return d != 0 ? (d > 0 ? n : -n) : 0;
}


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

int ftn_version7_nodelist_index::addrcmp() const
{

#ifdef DEBUG
    printf("[%d:%d/%d.%d] [%d:%d/%d.%d] ",
           searchaddr.zone, searchaddr.net, searchaddr.node, searchaddr.point,
           addrkey()->zone, addrkey()->net, addrkey()->node, keylength == 6 ? 0 : addrkey()->point
          );
#endif

    ftn_addr currentaddr;
    currentaddr = addrkey();
    if(keylength == 6)
        currentaddr.point = 0;

    return searchaddr.compare(currentaddr);
}


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

void ftn_version7_nodelist_index::fetchdata()
{

    if(node)
    {

        // Get data package
        _V7Data v7data;
        lseek(dfh, block.ndx.lnodeblk.leafref[node-1].keyval, SEEK_SET);
        read(dfh, &v7data, sizeof(_V7Data));

        // Reset data buffers
        char buf[160], buf2[1024];
        memset(buf, 0, sizeof(buf));
        memset(buf2, 0, sizeof(buf2));

        // Get phone
        read(dfh, data.phone, v7data.phone_len);
        data.phone[v7data.phone_len] = NUL;

        // Skip password
        lseek(dfh, v7data.password_len, SEEK_CUR);

        // Get packed data and unpack it
        read(dfh, buf2, v7data.pack_len);
        v7unpack(buf2, buf, v7data.pack_len);

        // Get system name
        struplow(strxcpy(data.system, buf, v7data.bname_len+1));

        // Get name
        if(namebrowse)
            strunrevname(data.name, namekey());
        else
        {
            char* namep = buf + v7data.bname_len;
            strxcpy(data.name, namep, v7data.sname_len+1);
        }
        struplow(data.name);

        // Get location
        char* locationp = buf + v7data.bname_len + v7data.sname_len;
        struplow(strxcpy(data.location, locationp, v7data.cname_len+1));

        // Check if V7+ data is available
        char* v7plus = locationp + v7data.cname_len;
        dword v7p = 0;
        int i, j;

        for(i=0; (i<8) and (*v7plus); v7plus++, i++)
        {
            if(not isxdigit(*v7plus))
                break;
            else
                v7p = (v7p <<4) | (xtoi(*v7plus));
        }

        use_v7plus = (tfh > 0) and (i == 8);

        if(use_v7plus)
        {

            lseek(tfh, v7p, SEEK_SET);

            if(v7data.nodeflags & V7_B_Point)  // node is a point
                lseek(tfh, (long)dtpctl.AllFixSize, SEEK_CUR);
            else
                lseek(tfh, (long)dtpctl.AllFixSize + (long)dtpctl.AddFixSize, SEEK_CUR);

            word raw_length;

            read(tfh, &raw_length, sizeof(raw_length));

            if(raw_length < sizeof(buf2))
            {
                read(tfh, buf2, raw_length);
                data.unpack(buf2);
            }
        }
        else
        {

            // Get status
            for(j=1, i=0; i<4; j+=j, i++)
            {
                if(v7data.nodeflags & j)
                    strcpy(data.status, v7nodeflags[i]);
            }
            *data.status = NUL;

            // Get baud
            sprintf(data.baud, "%lu", 300L * (long)v7data.baudrate);

            // Get flags
            *data.flags = NUL;
            char* ptr = data.flags;

            for(j=(1<<4),i=4; i<16; j+=j,i++)
                if(v7data.nodeflags & j)
                    ptr = stpcpy(ptr, v7nodeflags[i]);

            // Get modem types
            for(j=1,i=0; i<8; j+=j,i++)
                if(v7data.modemtype & j)
                    ptr = stpcpy(ptr, v7modemtype[i]);

            // Erase the trailing comma
            if(ptr != data.flags)
                *(--ptr) = NUL;
        }

        // Get address
        data.addr.zone  = v7data.zone;
        data.addr.net   = v7data.net;
        data.addr.node  = v7data.node;
        data.addr.point = (word)((v7data.nodeflags & V7_B_Point) ? v7data.hubnode : 0);
        data.addr.make_string(data.address);
    }
}


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

void ftn_version7_nodelist_index::getindexkey()
{

    _V7IndxRef* ip = &(block.ndx.inodeblk.indxref[inode-1]);
    keylength = ip->indxlen;
    memcpy(key, (char*)&block+ip->indxofs, keylength);
    key[keylength] = NUL;

#ifdef DEBUG
    printf("`---%02d:%02d <%04ld> ",
           inode,
           block.ndx.inodeblk.indxcnt,
           block.ndx.inodeblk.indxref[inode-1].indxptr
          );
#endif
}


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

void ftn_version7_nodelist_index::getleafkey()
{

    _V7LeafRef* lp = &(block.ndx.lnodeblk.leafref[node-1]);
    keylength = lp->keylen;
    memcpy(key, (char*)&block+lp->keyofs, keylength);
    key[keylength] = NUL;

#ifdef DEBUG
    printf("`---%02d:%02d ", node, block.ndx.lnodeblk.indxcnt);
#endif
}


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

void ftn_version7_nodelist_index::getblock()
{

    lseek(xfh, blockno*(long)ctl.ndx.ctlblk.ctlblksize, SEEK_SET);
    read(xfh, &block, sizeof(_V7Ndx));

#ifdef DEBUG
    if(block.ndx.inodeblk.indxfirst != -1)
    {
        printf("INDEXINFO: Branch:%ld, Below:%ld, Left:%ld, Right:%ld.\n",
               blockno,
               block.ndx.inodeblk.indxfirst,
               block.ndx.inodeblk.indxblink,
               block.ndx.inodeblk.indxflink
              );
    }
#endif
}


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

void ftn_version7_nodelist_index::getleaf()
{

#ifdef DEBUG
    printf("LEAF_INFO: Branch:%ld, Left:%ld, Right:%ld.\n",
           blockno,
           block.ndx.lnodeblk.indxblink,
           block.ndx.lnodeblk.indxflink
          );
#endif
}


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

bool ftn_version7_nodelist_index::search()
{

    int diff = 0;
    int prevdiff = 0;
    int previndexdiff = 0;
    exactmatch = false;

    xfh = namebrowse ? sfh : nfh;
    node = 0;

    // Get CtlRec
    lseek(xfh, 0, SEEK_SET);
    read(xfh, &ctl, sizeof(_V7Ndx));

    // The guts of the matter -- walk from CtlRec to Leaf
    blockno = ctl.ndx.ctlblk.ctlroot;
    memset(&block, 0, sizeof(_V7Ndx));
    inode = 1;

    // Read the first Index node.
    getblock();

    // Follow the node tree until we either match a key right in the
    // index node, or locate the leaf node which must contain the data.
    while(block.ndx.inodeblk.indxfirst != -1)
    {
        if(block.ndx.inodeblk.indxcnt == 0)
        {
            exactmatch = false;
            return false;
        }

        for(inode=1; inode<=block.ndx.inodeblk.indxcnt; inode++)
        {

            getindexkey();

            diff = namebrowse ? namecmp() : addrcmp();
            previndexdiff = diff;

#ifdef DEBUG
            printf("(%d)\n", diff);
#endif

            if(diff <= 0)
                break;
        }

        if(inode == 1)
            blockno = block.ndx.inodeblk.indxfirst;
        else if(diff == 0)
            blockno = block.ndx.inodeblk.indxref[inode-1].indxptr;
        else
            blockno = block.ndx.inodeblk.indxref[(--inode)-1].indxptr;

        getblock();
    }

    // We can only get here if we've found the leafnode which must
    // contain our data. Find our guy here or die trying.

    if(block.ndx.lnodeblk.indxcnt != 0)
    {

        // Search for a higher key

        getleaf();

        for(node=1; node<=block.ndx.lnodeblk.indxcnt; node++)
        {

            getleafkey();

            prevdiff = diff;
            diff = namebrowse ? namecmp() : addrcmp();

#ifdef DEBUG
            printf("(%d)\n", diff);
#endif

            if(diff < 0)
                break;
            if(diff == 0)
            {
                while(previous())
                {
                    if(not exactmatch)
                    {
                        next();
                        break;
                    }
                }
                return true;
            }
        }

        if((prevdiff > 0) and (diff <0))
        {
            if(absolute(prevdiff) > absolute(diff))
            {
#ifdef DEBUG
                printf("Begin lookup at previous blockno.\n");
#endif
                prevnode();
            }
            else
            {
#ifdef DEBUG
                printf("Begin lookup at current blockno.\n");
#endif
            }
        }
        else
        {
            if(absolute(previndexdiff) > absolute(diff))
            {
#ifdef DEBUG
                printf("Begin lookup at next blockno.\n");
#endif
                nextnode();
            }
            else
            {
#ifdef DEBUG
                printf("Begin lookup at this blockno.\n");
#endif
                if(node > 1)
                    node--;
            }
        }

        fetchdata();
    }

    return exactmatch;
}


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

bool ftn_version7_nodelist_index::prevnode()
{

    if(node == 1)
    {
        // Reached first node in current leaf
        if(block.ndx.lnodeblk.indxblink == 0)
            return false;
        blockno = block.ndx.inodeblk.indxblink;
        getblock();
        getleaf();
        node = block.ndx.lnodeblk.indxcnt;
    }
    else
    {
        node--;
    }
    getleafkey();

    return true;
}


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

bool ftn_version7_nodelist_index::nextnode()
{

    if(node >= block.ndx.lnodeblk.indxcnt)
    {
        // Reached end of nodes in current leaf
        if(block.ndx.lnodeblk.indxflink == 0)
            return false;
        blockno = block.ndx.inodeblk.indxflink;
        getblock();
        getleaf();
        node = 1;
    }
    else
    {
        node++;
    }
    getleafkey();

    return true;
}


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

ftn_version7_nodelist_index::ftn_version7_nodelist_index()
{

    nfh = sfh = dfh = tfh = -1;
    use_v7plus = false;
    isopen = false;
}


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

ftn_version7_nodelist_index::~ftn_version7_nodelist_index()
{

    if(isopen)
        close();
}


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

bool ftn_version7_nodelist_index::open()
{

    if(isopen)
        close();

    nfh = ::sopen(AddPath(nlpath, "NODEX.NDX"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
    sfh = ::sopen(AddPath(nlpath, "SYSOP.NDX"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
    if(sfh == -1)
        sfh = ::sopen(AddPath(nlpath, "NODEX.SDX"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
    dfh = ::sopen(AddPath(nlpath, "NODEX.DAT"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
    tfh = ::sopen(AddPath(nlpath, "NODEX.DTP"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);

    if((nfh == -1) or (sfh == -1) or (dfh == -1))
    {
        // Unable to open an index file
        close();
        return false;
    }

    if(tfh != -1)
        read(tfh, &dtpctl, sizeof(_V7DTPCtl));

    isopen = true;

    return true;
}


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

void ftn_version7_nodelist_index::close()
{

    if(dfh != -1)  ::close(dfh);
    dfh = -1;
    if(sfh != -1)  ::close(sfh);
    sfh = -1;
    if(nfh != -1)  ::close(nfh);
    nfh = -1;
    if(tfh != -1)  ::close(tfh);
    tfh = -1;

    isopen = false;
}


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

bool ftn_version7_nodelist_index::find(const char* lookup_name)
{

    namebrowse = true;

    char tmpname[80];
    strcpy(tmpname, lookup_name);
    strchg(tmpname, '.', ' ');
    struplow(strrevname(searchname, tmpname));

    return search();
}


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

bool ftn_version7_nodelist_index::find(const ftn_addr& addr)
{

    namebrowse = false;
    searchaddr = addr;
    return search();
}


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

bool ftn_version7_nodelist_index::previous()
{

    bool moved = prevnode();
    if(moved)
    {
        fetchdata();
        compare();
    }
    return moved;
}


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

bool ftn_version7_nodelist_index::next()
{

    bool moved = nextnode();
    if(moved)
    {
        fetchdata();
        compare();
    }
    return moved;
}


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

void ftn_version7_nodelist_index::first()
{

    if(namebrowse)
    {
        *searchname = NUL;
    }
    else
    {
        searchaddr.reset();
    }
    search();
}


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

void ftn_version7_nodelist_index::last()
{

    if(namebrowse)
    {
        memset(searchname, 0xFF, sizeof(searchname));
        searchname[sizeof(searchname)-1] = NUL;
    }
    else
    {
        searchaddr.set_all(0xFFFF);
    }
    search();
}


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

void ftn_version7_nodelist_index::push_state()
{

    state.blockno = blockno;
    state.node = node;
}


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

void ftn_version7_nodelist_index::pop_state()
{

    blockno = state.blockno;
    node = state.node;
    getblock();
    getleaf();
    getleafkey();
    fetchdata();
    compare();
}


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

const char* ftn_version7_nodelist_index::index_name() const
{

    return namebrowse ? "SYSOP.NDX" : "NODEX.NDX";
}


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

const char* ftn_version7_nodelist_index::nodelist_name() const
{

    return use_v7plus ? "NODEX.DTP" : (const char*)NULL;
}


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