
//  ------------------------------------------------------------------
//  GoldED+
//  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., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307 USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Nodelist lookup and browser.
//  ------------------------------------------------------------------

#include <golded.h>
#include <gftnnlfd.h>
#include <gftnnlfu.h>
#include <gftnnlge.h>
#include <gftnnlv7.h>


//  ------------------------------------------------------------------
//  Static data for the browser and associated functions

static ftn_nodelist_index_base* NLP = NULL;
static ftn_nodelist_entry       entry;


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

class NodelistBrowser
{

public:

    int   ypos;                        // Window Starting Row
    int   xpos;                        // Window Starting Column
    int   ylen;                        // Window Height
    int   xlen;                        // Window Width
    int   btype;                       // Window Border Type
    vattr battr;                       // Window Border Color
    vattr wattr;                       // Window Color
    vattr tattr;                       // Window Title Color
    vattr sattr;                       // Window Selection Bar Color
    vattr hattr;                       // Window Highlight Color
    vattr loattr;                      // Window LoAttr Color
    vattr sbattr;                      // Window Scrollbar Color
    char* title;                       // Window Title
    int   helpcat;                     // Window Help Category

    int pos;                           // Display Pos (1 to maxpos)
    int maxpos;                        // Display Pos

    int  aborted;                      // True if aborted
    int  listwrap;                     // True if wrap-around is supported

    ftn_nodelist_entry*  entries;
    std::vector<std::string> liststr;
    gwindow nodewin;
    gwindow listwin;
    char     user_maybe[45];
    char     titlet[80];
    int     user_fuzidx;
    int     newmaybe;
    int     firstkey;

    NodelistBrowser();
    ~NodelistBrowser();

    void Open();
    void Close();

    void BeforeCursor();
    void AfterCursor();

    void InitDisplay();

    void DisplayBar();
    void DisplayLine(int line=-1);
    void DisplayPage();

    void Home();
    void End();

    void Up();
    void Down();

    void PageUp();
    void PageDown();

    void Center();

    int Run();

    void BuildListString(int line);

    void ScrollUp();
    void ScrollDown();

    int DoKey(gkey& keycode);
};


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

NodelistBrowser::NodelistBrowser()
{

    // Not implemented yet
}


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

NodelistBrowser::~NodelistBrowser()
{

    throw_release(entries);
}


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

void NodelistBrowser::Open()
{

    maxpos = ylen - 4;

    vcurhide();
    nodewin.openxy(ypos, xpos, 2, xlen, 5, battr, wattr, sbattr);
    listwin.openxy(ypos+2, xpos, ylen-2, xlen, btype, battr, wattr, sbattr);

    entries = (ftn_nodelist_entry*)throw_calloc(maxpos, sizeof(ftn_nodelist_entry));
    liststr.resize(maxpos);

    InitDisplay();
}


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

void NodelistBrowser::Close()
{

    listwin.close();
    nodewin.close();
}


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

void NodelistBrowser::BeforeCursor()
{

    // Nothing to do
}


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

void NodelistBrowser::AfterCursor()
{
    CREATEBUFFER(char, buf, MAXCOL);
    CREATEBUFFER(char, line1, MAXCOL);
    CREATEBUFFER(char, line2, MAXCOL);

    ftn_nodelist_entry* entryp = entries + (pos - 1);

    gsprintf(PRINTF_DECLARE_BUFFER_AUTO(line1, MAXCOL), " %s%s%s%s%s%s ",
             entryp->name,
             (*entryp->system ? ", " : ""), entryp->system,
             (*entryp->status ? " <" : ""), entryp->status,
             (*entryp->status ? ">" : "")
            );
    gsprintf(PRINTF_DECLARE_BUFFER_AUTO(buf, MAXCOL), "%s %s", LNG->Phone, *entryp->phone ? entryp->phone : "-Unpublished-");
    strrjust(strsetsz(buf, MAXCOL-strlen(line1)-2));
    strcat(line1, buf);
    strcat(line1, " ");

    gsprintf(PRINTF_DECLARE_BUFFER_AUTO(line2, MAXCOL), " %s%s%s",
             entryp->location,
             (*entryp->location ? ", " : ""), entryp->address
            );
    gsprintf(PRINTF_DECLARE_BUFFER_AUTO(buf, MAXCOL), "%s%s%s%s",
             entryp->baud,
             (*entryp->baud ? " Bps" : ""),
             ((*entryp->baud and *entryp->flags) ? ", " : ""), entryp->flags
            );
    strrjust(strsetsz(buf, MAXCOL-strlen(line2)-2));
    strcat(line2, buf);
    strcat(line2, " ");
    nodewin.prints(0,0, wattr, line1);
    nodewin.prints(1,0, wattr, line2);

    Path nlname;
    *nlname = NUL;
    if (NLP->index_name() and NLP->nodelist_name())
        gsprintf(PRINTF_DECLARE_BUFFER(nlname), " %s / %s ", NLP->index_name(), CleanFilename(NLP->nodelist_name()));
    else if (NLP->index_name())
        gsprintf(PRINTF_DECLARE_BUFFER(nlname), " %s ", NLP->index_name());

    if (*nlname)
        listwin.title(nlname, tattr, TCENTER|TBOTTOM);
}


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

void NodelistBrowser::InitDisplay()
{
    gsprintf(PRINTF_DECLARE_BUFFER(titlet), LNG->Lookup, user_maybe);
    listwin.title(titlet, tattr, TCENTER);

    Center();
}


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

void NodelistBrowser::DisplayBar()
{

    listwin.prints(pos-1, 0, sattr, liststr[pos-1]);
}


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

void NodelistBrowser::DisplayLine(int line)
{

    if(line == -1)
        line = pos;
    listwin.prints(line-1, 0, wattr, liststr[line-1]);
}


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

void NodelistBrowser::BuildListString(int line)
{

    int x1 = (MAXCOL-80)/3;
    int x2 = x1;
    int x3 = (MAXCOL-80) - (x1+x2);

    ftn_nodelist_entry* entryp = entries + (line - 1);
    *entryp = NLP->entry();

    CREATEBUFFER(char, buf, MAXCOL);
    buf[MAXCOL - 1] = '\0';
    if(NLP->browsing_names())
        sprintf(buf, " %-*.*s %-*.*s %-*.*s ", 24+x1, 24+x1, entryp->name, 21+x2, 21+x2, entryp->address, 29+x3, 29+x3, entryp->system);
    else
        sprintf(buf, " %-*.*s %-*.*s %-*.*s ", 21+x2, 21+x2, entryp->address, 24+x1, 24+x1, entryp->name, 29+x3, 29+x3, entryp->system);
    liststr[line - 1] = buf;
}


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

void NodelistBrowser::DisplayPage()
{

    w_info(LNG->Wait);

    int line = 0;
    while(line < maxpos)
        entries[line++].addr.net = 0;

    NLP->push_state();

    // Get entries above and including current entry
    line = pos;
    do
    {
        BuildListString(line--);
    }
    while(line and NLP->previous());

    NLP->pop_state();
    NLP->push_state();

    // Get entries below current entry
    line = pos + 1;
    while((line <= maxpos) and NLP->next())
        BuildListString(line++);

    NLP->pop_state();

    w_info(NULL);

    line = 1;
    CREATEBUFFER(char, linebuf, MAXCOL);
    linebuf[MAXCOL-2] = NUL;

    // Display blank lines if necessary
    memset(linebuf, ' ', MAXCOL-2);
    while(line <= maxpos)
    {
        if(entries[line-1].addr.net)
            break;
        // Display separator line if necessary
        if(entries[line].addr.net)
            memset(linebuf, _box_table(btype, 1), MAXCOL-2);
        listwin.prints(line-1, 0, wattr|ACSET, linebuf);
        line++;
    }

    // Display entry lines
    while((line <= maxpos) and entries[line-1].addr.net)
    {
        if(line == pos)
            DisplayBar();
        else
            DisplayLine(line);
        line++;
    }

    // Display separator line if necessary
    memset(linebuf, _box_table(btype, 1), MAXCOL-2);
    if(line <= maxpos)
    {
        listwin.prints(line-1, 0, wattr|ACSET, linebuf);
        line++;
    }

    // Display blank lines if necessary
    memset(linebuf, ' ', MAXCOL-2);
    while(line <= maxpos)
    {
        listwin.prints(line-1, 0, wattr, linebuf);
        line++;
    }
}


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

void NodelistBrowser::Home()
{

    pos = 1;
    NLP->first();
    DisplayPage();
}


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

void NodelistBrowser::End()
{

    pos = maxpos;
    NLP->last();
    DisplayPage();
}


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

void NodelistBrowser::ScrollUp()
{
    memmove(entries, entries+1, (maxpos-1)*sizeof(ftn_nodelist_entry));
    liststr.erase(liststr.begin());
    liststr.resize(liststr.size() + 1);
    BuildListString(maxpos);
    listwin.scroll_up();
}


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

void NodelistBrowser::ScrollDown()
{

    memmove(entries+1, entries, (maxpos-1)*sizeof(ftn_nodelist_entry));
    liststr.insert(liststr.begin(), std::string());
    liststr.resize(liststr.size() - 1);
    BuildListString(1);
    listwin.scroll_down();
}


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

void NodelistBrowser::Up()
{

    if(NLP->previous())
    {
        if(pos > 1)
        {
            DisplayLine();
            pos--;
        }
        else
        {
            DisplayLine();
            ScrollDown();
        }
    }
    else if(listwrap)
    {
        End();
    }
}


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

void NodelistBrowser::Down()
{

    if(NLP->next())
    {
        if(pos < maxpos)
        {
            DisplayLine();
            pos++;
        }
        else
        {
            DisplayLine();
            ScrollUp();
        }
    }
    else if(listwrap)
    {
        Home();
    }
}


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

void NodelistBrowser::PageUp()
{

    if(pos > 1)
    {
        DisplayLine();
        do
        {
            if(not NLP->previous())
                break;
        }
        while(--pos > 1);
    }
    else
    {
        int count = 1;
        while(count < maxpos)
        {
            if(not NLP->previous())
                break;
            count++;
        }
        DisplayPage();
    }
}


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

void NodelistBrowser::PageDown()
{

    if(pos < maxpos)
    {
        DisplayLine();
        do
        {
            if(not NLP->next())
                break;
        }
        while(++pos < maxpos);
    }
    else
    {
        int count = 1;
        while(count < maxpos)
        {
            if(not NLP->next())
                break;
            count++;
        }
        DisplayPage();
    }
}


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

void NodelistBrowser::Center()
{

    switch(CFG->displistcursor)
    {
    case LIST_TOP:
        pos = 1;
        break;
    case LIST_NEARTOP:
        pos = (maxpos / 4) + 1;
        break;
    case LIST_MIDDLE:
        pos = (maxpos / 2) + 1;
        break;
    case LIST_NEARBOTTOM:
        pos = (3 * (maxpos / 4)) + 1;
        break;
    case LIST_BOTTOM:
        pos = maxpos;
        break;
    }
    DisplayPage();
}


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

int NodelistBrowser::Run()
{

    int keyok;
    gkey newkey;
    gkey keycode;

#ifdef GOLD_MOUSE
    gmou.HideCursor();
#endif

    Open();
    if(helpcat)
        whelppcat(helpcat);

    do
    {

        listwin.move_cursor(pos-1, 0);
        DisplayBar();
        AfterCursor();
#ifdef GOLD_MOUSE
        gmou.ShowCursor();
#endif
        keyok = YES;

        do
        {
            newkey = keycode = getxchtick();
            if(newkey == Key_Tick)
                DoKey(keycode);
        }
        while(newkey == Key_Tick);

        do
        {
            newkey = 0;
#ifdef GOLD_MOUSE
            gmou.HideCursor();
#endif
            switch(keycode)
            {
            case Key_Up:
                BeforeCursor();
                Up();
                break;
            case Key_Dwn:
                BeforeCursor();
                Down();
                break;
            case Key_PgUp:
                BeforeCursor();
                PageUp();
                break;
            case Key_PgDn:
                BeforeCursor();
                PageDown();
                break;
            case Key_Home:
                BeforeCursor();
                Home();
                break;
            case Key_End:
                BeforeCursor();
                End();
                break;
            default:
                keyok = DoKey(keycode);
                newkey = keycode;
            }
            if(newkey)
                keycode = newkey;
        }
        while(newkey);
    }
    while(keyok);

#ifdef GOLD_MOUSE
    gmou.HideCursor();
#endif

    if(helpcat)
        whelpop();
    Close();

#ifdef GOLD_MOUSE
    gmou.ShowCursor();
#endif

    return aborted;
}


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

int NodelistBrowser::DoKey(gkey& keycode)
{

    gkey key = keycode;
    keycode = 0;
    if(key < KK_Commands)
    {
        gkey tmpkey = key_tolower(key);
        gkey kk = SearchKey(tmpkey, NodeKey, NodeKeys);
        if(kk)
            key = kk;
    }

    switch(key)
    {
    case KK_NodeAskExit:
    {
        GMenuQuit MenuQuit;
        if(MenuQuit.Run())
            keycode = KK_NodeQuitNow;
    }
    break;

    case KK_NodeQuitNow:
        gkbd.quitall = YES;
        if(gkbd.kbuf == NULL)
            kbput(Key_Esc);
    // Drop through

    case KK_NodeAbort:
        newmaybe = NO;
        aborted = YES;
    // Drop Through

    case KK_NodeSelect:
        if(newmaybe)
        {
            newmaybe = NO;
            firstkey = YES;
            Addr matchaddr;
            matchaddr.set_all(0xFFFF);
            matchaddr.set(user_maybe);
            bool gotzone = false;
            bool gotnet = false;
            bool gotnode = false;
            bool gotpoint = false;
            if(matchaddr.zone != 0xFFFF)
                gotzone = true;
            else
                matchaddr.zone = 0;
            if(matchaddr.net != 0xFFFF)
                gotnet = true;
            else
                matchaddr.net = 0;
            if(matchaddr.node != 0xFFFF)
                gotnode = true;
            else
                matchaddr.node = 0;
            if(matchaddr.point != 0xFFFF)
                gotpoint = true;
            else
                matchaddr.point = 0;
            if(gotzone or gotnet or gotnode or gotpoint)
            {
                if(not gotzone)
                    matchaddr.zone = AA->Aka().addr.zone;
                if(not gotnet)
                    matchaddr.net = AA->Aka().addr.net;
                if(gotpoint and not gotnode)
                    matchaddr.node = AA->Aka().addr.node;
                if(matchaddr.point >= GFTN_FIRST)
                    matchaddr.point = 0;
                if(matchaddr.node >= GFTN_FIRST)
                    matchaddr.node = 0;
                if(matchaddr.net >= GFTN_FIRST)
                    matchaddr.net = 0;
                if(matchaddr.zone >= GFTN_FIRST)
                    matchaddr.zone = 0;
            }

            if(matchaddr.net)
                NLP->find(matchaddr);
            else
                NLP->find(user_maybe);
            InitDisplay();
        }
        else
        {
            BeforeCursor();
            return NO;
        }
        break;

    case Key_Tab:
        if(NLP->browsing_names() and NLP->can_browse_address())
        {
            strcpy(user_maybe, NLP->address());
            NLP->find(NLP->addrs());
        }
        else if(NLP->browsing_addresses() and NLP->can_browse_name())
        {
            strcpy(user_maybe, NLP->name());
            NLP->find(NLP->name());
        }
        user_fuzidx = strlen(user_maybe);
        InitDisplay();
        break;

    case KK_NodeGotoPrev:
        keycode = Key_Up;
        firstkey = YES;
        break;

    case KK_NodeGotoNext:
        keycode = Key_Dwn;
        firstkey = YES;
        break;

    case KK_NodeGotoFirst:
        keycode = Key_Home;
        firstkey = YES;
        break;

    case KK_NodeGotoLast:
        keycode = Key_End;
        firstkey = YES;
        break;

    case KK_NodeDosShell:
        DosShell();
        break;

    case Key_Tick:
        CheckTick(KK_NodeQuitNow);
        break;

    case KK_NodeUndefine:
        break;

    default:
        if(not PlayMacro(key, KT_N))
        {
            int n = key & 0xFF;
            if((user_fuzidx < 41) or (key == Key_BS))
            {
                if(firstkey)
                {
                    if((key != Key_BS) and not isspace(n))
                    {
                        *user_maybe = 0;
                        user_fuzidx = 0;
                    }
                }
                firstkey = NO;
                if((n >= ' ') or (key == Key_BS))
                {
                    newmaybe = YES;
                    if(key != Key_BS)
                        user_maybe[user_fuzidx++] = (char)n;
                    else if(user_fuzidx)
                        user_maybe[--user_fuzidx] = 0;
                    user_maybe[user_fuzidx] = 0;
                    gsprintf(PRINTF_DECLARE_BUFFER(titlet), LNG->Lookup, user_maybe);
                    listwin.title(titlet, tattr, TCENTER);
                }
            }
        }
    }

    return YES;
}


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

static int browse_nodelist(char* title, int topline)
{

    w_info(NULL);

    NodelistBrowser* browser = new NodelistBrowser;
    throw_new(browser);

    browser->btype = W_BMENU;
    browser->battr = C_MENUB;
    browser->wattr = C_MENUW;
    browser->sattr = C_MENUS;
    browser->tattr = C_MENUT;
    browser->hattr = C_MENUQ;
    browser->sbattr = C_MENUPB;
    browser->helpcat = H_NodelistBrowser;
    browser->ypos = topline;
    browser->xpos = 0;
    browser->ylen = MAXROW-browser->ypos-1;
    browser->xlen = MAXCOL;
    browser->listwrap = CFG->switches.get(displistwrap);
    browser->firstkey = YES;
    browser->newmaybe = NO;
    browser->aborted = NO;
    strxcpy(browser->user_maybe, title, sizeof(browser->user_maybe));
    browser->user_fuzidx = strlen(browser->user_maybe);

    int aborted = browser->Run();
    if(not aborted)
        entry = browser->entries[browser->pos-1];

    delete browser;

    return (not aborted);
}


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

static bool NLP_open()
{

    if(*CFG->nodepathv7)
    {
        NLP = new ftn_version7_nodelist_index;
        throw_new(NLP);
        NLP->set_path(CFG->nodepathv7);
    }
    else if(*CFG->nodepathfd)
    {
        NLP = new ftn_frontdoor_nodelist_index;
        throw_new(NLP);
        NLP->set_path(CFG->nodepathfd);
    }
    else if(*CFG->fidouserlist)
    {
        NLP = new ftn_fidouser_nodelist_index;
        throw_new(NLP);
        NLP->set_path(CFG->fidouserlist);
    }
    else
    {
        CheckNodelists();
        NLP = new ftn_golded_nodelist_index;
        throw_new(NLP);
        NLP->set_path(CFG->nodepath);
    }

    return NLP->open();
}


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

static void NLP_close()
{

    NLP->close();
    throw_delete(NLP);
}


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

static Name nlname;

const char *lookup_nodelist(ftn_addr* addr)
{

    const char *r = NULL;

    if(NLP_open())
    {

        NLP->find(*addr);
        if(NLP->found())
        {
            strcpy(nlname, NLP->entry().name);
            r = nlname;
        }
    }
    NLP_close();
    return r;
}


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

void Lookup(GMsg* msg, Addr* addr, char* name, int topline, char* status)
{

    char* ptr;
    INam buf, buf1, tmpname;
    bool found = false;
    int robotchk;
    bool dolookup = true;
    bool namelookup = true;
    Addr matchaddr;
    bool exactmatch = true;

    strtrim(name);
    if(*name == NUL)
    {
        matchaddr = AA->Aka().addr;
        namelookup = false;
        exactmatch = false;
    }
    strcpy(tmpname, name);

    if(not CFG->addressmacro.empty())
    {
        std::vector<AddrMacro>::iterator n;
        ptr = name;
        strcpy(buf, ptr);
        for(n=CFG->addressmacro.begin(); n != CFG->addressmacro.end(); n++)
        {
            if(strieql(ptr, n->macro))
            {
                strcpy(buf1, n->name);
                ptr = buf1;
                if(*buf1 == '@')
                {
                    // UUCP/INTERNET addressing
                    ptr = strchr(buf1, '/');
                    if(ptr)
                    {
                        *ptr++ = NUL;
                        strcpy(msg->iaddr, ptr);
                        strcpy(msg->idest, ptr);
                        ptr = buf1+1;
                    }
                    else
                    {
                        strcpy(msg->iaddr, buf1+1);
                        strcpy(msg->idest, buf1+1);
                        ptr = strcpy(buf1, *AA->Internetgate().name ? AA->Internetgate().name : "UUCP");
                    }
                }
                strcpy(name, ptr);
                if(n->addr.net)
                    *addr = n->addr;
                else
                    *addr = AA->Internetgate().addr;
                strtrim(name);
                if(topline >= 0)
                    return;
            }
        }
    }

    // Automatic internet gating
    if(strchr(name, '@') and AA->Internetgate().addr.valid())
    {
        strcpy(msg->idest, name);
        if(*AA->Internetgate().name)
        {
            strcpy(msg->iaddr, name);
            strcpy(msg->to, AA->Internetgate().name);
            strcpy(name, msg->to);
        }
        *addr = AA->Internetgate().addr;
        namelookup = false;
        if(topline >= 0)
            return;
    }

    // Check if name is really an address
    if(*tmpname)
    {
        matchaddr.set_all(0xFFFF);
        matchaddr.set(name);
        bool gotzone = false;
        bool gotnet = false;
        bool gotnode = false;
        bool gotpoint = false;
        if(matchaddr.zone != 65535u)
            gotzone = true;
        else
            matchaddr.zone = 0;
        if(matchaddr.net != 65535u)
            gotnet = true;
        else
            matchaddr.net = 0;
        if(matchaddr.node != 65535u)
            gotnode = true;
        else
            matchaddr.node = 0;
        if(matchaddr.point != 65535u)
            gotpoint = true;
        else
            matchaddr.point = 0;
        if(gotzone or gotnet or gotnode or gotpoint)
        {
            *addr = matchaddr;
            if(not gotzone)
                addr->zone = AA->Aka().addr.zone;
            if(not gotnet)
                addr->net = AA->Aka().addr.net;
            if(gotpoint and not gotnode)
                addr->node = AA->Aka().addr.node;
            matchaddr = *addr;
            namelookup = false;
            if(matchaddr.point >= GFTN_FIRST)
            {
                matchaddr.point = 0;
                exactmatch = false;
            }
            if(matchaddr.node >= GFTN_FIRST)
            {
                matchaddr.node = 0;
                exactmatch = false;
            }
            if(matchaddr.net >= GFTN_FIRST)
            {
                matchaddr.net = 0;
                exactmatch = false;
            }
            if(matchaddr.zone >= GFTN_FIRST)
            {
                matchaddr.zone = 0;
                exactmatch = false;
            }
        }
    }

    if(namelookup)
    {
        ptr = strrchr(name, ' ');
        if(ptr != NULL)
        {
            addr->reset();
            addr->set(ptr+1);
            if(addr->net)
            {
                // Address was given
                *ptr = NUL;
                strtrim(name);
                if(topline >= 0)
                    return;
            }
        }
    }

    robotchk = NO;
    if(striinc("Sysop", name) or strieql(AA->Whoto(), name))
        robotchk = YES;
    else
    {
        for(gstrarray::iterator n = CFG->robotname.begin(); n != CFG->robotname.end(); n++)
            if(striinc(n->c_str(), name))
            {
                robotchk = YES;
                break;
            }
    }

    if(robotchk)
    {
        addr->reset();
        if(topline >= 0)
            return;
    }

    if(topline >= 0)
    {
        dolookup = false;
        if(AA->isnet() and CFG->switches.get(lookupnet))
            dolookup = true;
        else if(AA->isecho() and CFG->switches.get(lookupecho))
            dolookup = true;
        else if(AA->islocal() and CFG->switches.get(lookuplocal))
            dolookup = true;
    }

    if(dolookup)
    {

        if(NLP_open())
        {

            if(namelookup)
                NLP->find(name);
            else
                NLP->find(matchaddr);

            found = NLP->found() && exactmatch;

            if (found && (topline != -100))
            {
                NLP->push_state();
                if(NLP->next())
                    if(NLP->found())
                        found = false;
                NLP->pop_state();
            }
            else if (!found && matchaddr.point && (topline < 0))
            {
                matchaddr.point = 0;
                NLP->find(matchaddr);
                found = NLP->found();
            }

            entry = NLP->entry();

            if (topline == -100)
            {
                if (found)
                    strcpy(name, entry.location);
                else
                    strcpy(name, "");

                NLP_close();
                return;
            }

            if(not found or (topline < 0))
            {
                strcpy(buf, information);
                update_statusline(status);
                found = make_bool(browse_nodelist(tmpname, abs(topline)));
                update_statusline(buf);
            }
        }
        else
        {
            w_info(LNG->NoNodelist);
            waitkeyt(10000);
            w_info(NULL);
        }

        NLP_close();
    }

    if(found)
    {
        strcpy(name, entry.name);
        *addr = entry.addr;
    }
    else
    {
        addr->reset();
    }

    if(topline >= 0)
    {
        if(CFG->switches.get(internetlookup))
        {
            if(*entry.system and strchr(entry.system, '@') and AA->Internetgate().addr.valid())
            {
                strcpy(msg->iaddr, entry.system);
                strcpy(msg->idest, entry.system);
                if(*AA->Internetgate().name)
                    strcpy(msg->to, AA->Internetgate().name);
                else if(strlen(entry.system) >= sizeof(Name))
                    strcpy(msg->to, *AA->Internetgate().name ? AA->Internetgate().name : "UUCP");
                else
                {
                    strcpy(msg->to, entry.system);
                    *msg->iaddr = NUL;
                    *msg->idest = NUL;
                }
                strcpy(name, msg->to);
                *addr = AA->Internetgate().addr;
            }
        }
    }
}


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

struct location_item
{
    Addr addr;
    std::string loc;

    location_item(Addr &a)
    {
        addr = a;
    }

    bool operator<(Addr &other)
    {
        return (addr.compare(other) < 0);
    }
    bool operator==(Addr &other)
    {
        return addr.equals(other);
    }
};

std::vector<location_item> g_LocationCache;

void LookupNodeClear()
{
    g_LocationCache.clear();
}

void LookupNodeLocation(GMsg* msg, std::string &location, int what)
{
    Subj statuslinebak;
    strcpy(statuslinebak, information);

    vcurhide();
    w_info(LNG->Wait);

    Addr addr;
    switch (what)
    {
    case LOOK_CITY2:
        addr = msg->dest;
        break;
    case LOOK_CITY1:
        addr = msg->orig;
        break;
    }

    if (addr.zone == 0)
        addr.zone = AA->Aka().addr.zone;

    std::vector<location_item>::iterator cacheIt = g_LocationCache.begin();
    std::vector<location_item>::iterator cacheEnd = g_LocationCache.end();

    while ((cacheIt != cacheEnd) && (*cacheIt < addr)) cacheIt++;

    if ((cacheIt != cacheEnd) && (*cacheIt == addr))
        location = cacheIt->loc;
    else
    {
        char buf[256];
        location_item item(addr);

        addr.make_string(buf);
        Lookup(msg, &addr, buf, -100, LNG->LookupInfo);

        if (addr.invalid())
            buf[0] = NUL;

        std::string city = buf;

        if (!CFG->locationalias.empty())
        {
            std::vector<std::pair<std::string, std::string> >::iterator it = CFG->locationalias.begin();
            std::vector<std::pair<std::string, std::string> >::iterator end = CFG->locationalias.end();

            std::string city_upr = city;
            strupr(city_upr);

            for (; it != end; ++it)
            {
                const std::string& src = it->first;
                const std::string& alias = it->second;
                std::string::size_type pos = city_upr.find(src);
                if (pos != std::string::npos)
                {
                    city.replace(pos, src.size(), alias);
                    city_upr = city;
                    strupr(city_upr);
                }
            }
        }

        item.loc = location = city;
        g_LocationCache.insert(cacheIt, item);
    }

    update_statusline(statuslinebak);

    w_info(NULL);
}


//  ------------------------------------------------------------------
//  Lookup and display info about the node

void LookupNode(GMsg* msg, const char* name, int what)
{

    Subj statuslinebak;
    strcpy(statuslinebak, information);

    vcurhide();
    w_info(LNG->Wait);

    Addr addr;
    char buf[256];
    char* ptr = buf;

    switch(what)
    {
    case LOOK_DEST:
        if(AA->isnet())
        {
            addr = msg->dest;
            if(addr.zone == 0)
                addr.zone = AA->Aka().addr.zone;
            addr.make_string(buf);
        }
        else
        {
            addr.reset();
            strcpy(buf, msg->to);
        }
        break;
    case LOOK_ORIG:
        addr = msg->orig;
        if(addr.zone == 0)
            addr.zone = AA->Aka().addr.zone;
        addr.make_string(buf);
        break;
    case LOOK_NAME:
        addr = msg->orig;
        strcpy(buf, strskip_wht(name));
        strtok(buf, " \t\n\r");   // Only lookup first word
        break;
    }
    Lookup(msg, &addr, ptr, -6, LNG->LookupInfo);

    update_statusline(statuslinebak);

    w_info(NULL);
}


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

void CheckNodelists()
{

    // Copy of previous timestamp
    static time32_t oldft = 0;

    // Get timestamp of the .GXL file
    Path file;
    strcpy(file, AddPath(CFG->nodepath, "goldnode.gxl"));
    time32_t ft = GetFiletime(file);

    // Check nodelists if timestamp changed
    if(ft != oldft)
    {

        // Keep copy of timestamp for later lookups
        oldft = ft;

        strcpy(NODE->addrindex, AddPath(CFG->nodepath, "goldnode.gxa"));
        strcpy(NODE->nodeindex, AddPath(CFG->nodepath, "goldnode.gxn"));

        Subj statuslinebak;
        strcpy(statuslinebak, information);

        update_statusline(LNG->CheckingNodelists);

        gfile fp(file, "rt", CFG->sharemode);
        if (fp.isopen())
        {
            // Read the list index
            char buf[256];
            NODE->nodelists = 0;
            while (fp.Fgets(buf, sizeof(buf)))
            {
                NODE->nodelist = (Stamp*)throw_realloc(NODE->nodelist, (NODE->nodelists+1)*sizeof(Stamp));
                char* key;
                char* val=buf;
                getkeyval(&key, &val);
                key = strxcpy(NODE->nodelist[NODE->nodelists].fn, strbtrim(key), sizeof(Path));
                //MakePathname(key, CFG->nodepath, key);
                NODE->nodelist[NODE->nodelists].ft = atol(val);
                NODE->nodelists++;
            }
            fp.Fclose();

            // Check the files
            if(CFG->switches.get(nodelistwarn))
            {
                for(int n=0; n<NODE->nodelists; n++)
                {
                    if(not fexist(MapPath(NODE->nodelist[n].fn)))
                    {
                        w_infof(" %s %s ", LNG->NodelistMissing, NODE->nodelist[n].fn);
                        HandleGEvent(EVTT_ATTENTION);
                        waitkeyt(10000);
                        w_info(NULL);
                    }
                    else if (GetFiletime(NODE->nodelist[n].fn) != NODE->nodelist[n].ft)
                    {
                        w_infof(" %s %s ", LNG->NodelistOutdated, NODE->nodelist[n].fn);
                        HandleGEvent(EVTT_ATTENTION);
                        waitkeyt(10000);
                        w_info(NULL);
                    }
                }
            }
        }

        update_statusline(statuslinebak);
    }

    throw_release(NODE->nodelist);
    NODE->nodelists = 0;
}

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

