//  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$
//  ------------------------------------------------------------------
//  FrontDoor Nodelist Indexing.
//  Based on code kindly provided by Scott Dudley.
//  ------------------------------------------------------------------

#include <gfilutil.h>
#include <gstrall.h>
#include <gftnnlfd.h>


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

inline const _FDUdx& ftn_frontdoor_nodelist_index::namerec() const {

  return block.node.name[node-1];
}


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

inline const _FDFdx& ftn_frontdoor_nodelist_index::addrrec() const {

  return block.node.addr[node-1];
}


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

inline const _FDGdx& ftn_frontdoor_nodelist_index::noderec() const {

  return namebrowse ? *((_FDGdx*)&block.node.name[node-1]) : *((_FDGdx*)&block.node.addr[node-1]);
}


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

int ftn_frontdoor_nodelist_index::namecmp() const {

  #ifdef DEBUG
  printf("[%s] [%-15.15s] ", searchname, namerec().name);
  #endif

  const char* a = searchname;
  const char* b = namerec().name;
  int n = 1;
  while(n <= 15) {
    int d = (int)((byte)*a) - (int)((byte)*b);
    if(d != 0)
      return d > 0 ? n : -n;
    a++;
    b++;
    n++;
  }
  return 0;
}


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

void ftn_frontdoor_nodelist_index::getaddr(ftn_addr& addr) const {

  if(namebrowse) {
    addr.zone  = (word)((namerec().zone_hi  << 8) | namerec().zone_lo);
    addr.net   = (word)((namerec().net_hi   << 8) | namerec().net_lo);
    addr.node  = (word)((namerec().node_hi  << 8) | namerec().node_lo);
    addr.point = (word)((namerec().point_hi << 8) | namerec().point_lo);
  }
  else {
    addr.zone  = (word)((addrrec().zone_hi  << 8) | addrrec().zone_lo);
    addr.net   = (word)((addrrec().net_hi   << 8) | addrrec().net_lo);
    addr.node  = (word)((addrrec().node_hi  << 8) | addrrec().node_lo);
    addr.point = (word)((addrrec().point_hi << 8) | addrrec().point_lo);
  }
}


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

int ftn_frontdoor_nodelist_index::addrcmp() const {

  ftn_addr currentaddr;
  getaddr(currentaddr);

  #ifdef DEBUG
  printf("[%d:%d/%d.%d] [%d:%d/%d.%d] ",
    searchaddr.zone,  searchaddr.net,  searchaddr.node,  searchaddr.point,
    currentaddr.zone, currentaddr.net, currentaddr.node, currentaddr.point
  );
  #endif

  return searchaddr.compare(currentaddr);
}


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

void ftn_frontdoor_nodelist_index::getstatus(char* _status, int type) const {

  switch(type) {
    case ISZC:    strcpy(_status, "Zone");   break;
    case ISRC:    strcpy(_status, "Region"); break;
    case ISNC:    strcpy(_status, "Host");   break;
    case ISHUB:   strcpy(_status, "Hub");    break;
    case ISPVT:   strcpy(_status, "Pvt");    break;
    case ISHOLD:  strcpy(_status, "Hold");   break;
    case ISDOWN:  strcpy(_status, "Down");   break;
    case ISPOINT: strcpy(_status, "Point");  break;
    default:      *_status = NUL;
  }
}


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

void ftn_frontdoor_nodelist_index::fetchdata() {

  bool infdpvt   = !!(noderec().nlofs & IN_FDNET);
  bool infdpoint = !!(noderec().nlofs & IN_FDPOINT);
  bool infdnode  = !!(noderec().nlofs & IN_FDNODE);

  if(infdnode) {

    int fd = ::sopen(AddPath(nlpath, "FDNODE.FDA"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
    if(fd != -1) {
      _FDFdn fda;
      while(read(fd, &fda, sizeof(_FDFdn)) == sizeof(_FDFdn)) {
        if(not fda.erased) {
          if((fda.zone == data.addr.zone) and (fda.net == data.addr.net) and (fda.node == data.addr.node) and (fda.point == data.addr.point)) {
            strnp2cc(data.name, fda.user, 36);
            strnp2cc(data.system, fda.name, 30);
            strnp2cc(data.phone, fda.telephone, 40);
            strnp2cc(data.location, fda.location, 40);
            switch(fda.maxbaud) {
              case ISBAUD300   : strcpy(data.baud, "300");    break;
              case ISBAUD1200  : strcpy(data.baud, "1200");   break;
              case ISBAUD2400  : strcpy(data.baud, "2400");   break;
              case ISBAUD4800  : strcpy(data.baud, "4800");   break;
              case ISBAUD7200  : strcpy(data.baud, "7200");   break;
              case ISBAUD9600  : strcpy(data.baud, "9600");   break;
              case ISBAUD12000 : strcpy(data.baud, "12000");  break;
              case ISBAUD14400 : strcpy(data.baud, "14400");  break;
              case ISBAUD16800 : strcpy(data.baud, "16800");  break;
              case ISBAUD19200 : strcpy(data.baud, "19200");  break;
              case ISBAUD38400 : strcpy(data.baud, "38400");  break;
              case ISBAUD57600 : strcpy(data.baud, "57600");  break;
              case ISBAUD64000 : strcpy(data.baud, "64000");  break;
              case ISBAUD76800 : strcpy(data.baud, "76800");  break;
              case ISBAUD115200: strcpy(data.baud, "115200"); break;
              default:           *data.baud = NUL;
            }
            getstatus(data.status, fda.status);
            *data.flags = NUL;
            long c = fda.capability;
            char* f = data.flags;
            if(c & CMflag)      strcat(f, "CM,");
            if(c & MOflag)      strcat(f, "MO,");
            if(c & LOflag)      strcat(f, "LO,");
            if(c & V32flag)     strcat(f, "V32,");
            if(c & V32bflag)    strcat(f, "V32B,");
            if(c & V33flag)     strcat(f, "V33,");
            if(c & V34flag)     strcat(f, "V34,");
            if(c & V42flag)     strcat(f, "V42,");
            if(c & V42bflag)    strcat(f, "V42B,");
            if(c & MNPflag)     strcat(f, "MNP,");
            if(c & H96flag)     strcat(f, "H96,");
            if(c & HSTflag)     strcat(f, "HST,");
            if(c & HST14flag)   strcat(f, "H14,");
            if(c & HST16flag)   strcat(f, "H16,");
            if(c & MAXflag)     strcat(f, "MAX,");
            if(c & PEPflag)     strcat(f, "PEP,");
            if(c & ZYXflag)     strcat(f, "ZYX,");
            if(c & XAflag)      strcat(f, "XA,");
            if(c & XBflag)      strcat(f, "XB,");
            if(c & XCflag)      strcat(f, "XC,");
            if(c & XPflag)      strcat(f, "XP,");
            if(c & XRflag)      strcat(f, "XR,");
            if(c & XWflag)      strcat(f, "XW,");
            if(c & XXflag)      strcat(f, "XX,");
            if(c & FAXflag)     strcat(f, "FAX,");
            if(c & UISDNAflag)  strcat(f, "V110L,");
            if(c & UISDNBflag)  strcat(f, "V110H,");
            if(c & UISDNCflag)  strcat(f, "X75,");
            if(*f)              f[strlen(f)-1] = NUL;
            break;
          }
        }
      }
      ::close(fd);
    }
  }
  else {

    int fd = infdpvt ? pfd : (infdpoint ? ppfd : nfd);
    long offset = noderec().nlofs & ~(IN_FDNET|IN_FDPOINT);

    char buf[256];
    buf[255] = NUL;

    lseek(fd, offset, SEEK_SET);
    read(fd, buf, 255);
    if(*buf != ';') {

      char* end = strchr(buf, '\r');
      if(end)
        *end = NUL;

      strchg(strtrim(buf), '_', ' ');

      *data.name = NUL;
      *data.system = NUL;
      *data.location = NUL;
      *data.phone = NUL;
      *data.flags = NUL;
      *data.baud = NUL;

      char* q = buf;
      char* p = strchr(buf, ',');
      if(p) {
        *p++ = NUL;
        strxcpy(data.system, q, sizeof(data.system));
        p = strchr((q=p), ',');
        if(p) {
          *p++ = NUL;
          strxcpy(data.location, q, sizeof(data.location));
          p = strchr((q=p), ',');
          if(p) {
            *p++ = NUL;
            strxcpy(data.name, q, sizeof(data.name));
            p = strchr((q=p), ',');
            if(p) {
              *p++ = NUL;
              strxcpy(data.phone, q, sizeof(data.phone));
              p = strchr((q=p), ',');
              sprintf(data.baud, "%lu", atol(q));
              if(p)
                strxcpy(data.flags, p+1, sizeof(data.flags));
            }
          }
        }
      }
    }

    getstatus(data.status, namebrowse ? namerec().type : addrrec().type);
  }

  data.addr.make_string(data.address);
}


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

#ifdef DEBUG
void ftn_frontdoor_nodelist_index::printnode() const {

  printf("<%05d.%05d.%05d.%d> <%02d:%02d> ",
    blockno,
    block.info.index,
    noderec().block_num,
    depth,
    block.info.nodes,
    node
  );
}
#endif


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

void ftn_frontdoor_nodelist_index::getnodedata() {

  #ifdef DEBUG
  printnode();
  #endif
  getaddr(data.addr);
  fetchdata();
}


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

void ftn_frontdoor_nodelist_index::getblock() {

  lseek(xfd, (long)blockno*(long)blocksize, SEEK_SET);
  read(xfd, &block, blocksize);
}


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

void ftn_frontdoor_nodelist_index::push() {

  stack[depth].blockno = blockno;
  stack[depth].block_num = noderec().block_num;
  stack[depth].maxnodes = block.info.nodes;
  stack[depth].node = node;
  depth++;
}


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

void ftn_frontdoor_nodelist_index::pop() {

  depth--;
  node = stack[depth].node;
  blockno = stack[depth].blockno;
}


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

bool ftn_frontdoor_nodelist_index::search() {

  xfd = namebrowse ? ufd : fdfd;
  blocksize = namebrowse ? sizeof(_FDUdb) : sizeof(_FDFdb);

  // Read beginning control block
  lseek(xfd, 0L, SEEK_SET);
  read(xfd, &ctl, sizeof(_FDCtl));

  // Now trace down the tree, starting at the master index
  exactmatch = false;
  int diff = 0;
  int prevdiff = 0;
  blockno = ctl.hdr.master_idx;
  maxblockno = (uint)(lseek(xfd, 0L, SEEK_END)/blocksize);
  depth = 0;
  node = 1;
  push();
  
  // Do one block at a time...
  while((not exactmatch) and (blockno < maxblockno)) {

    // Get one block
    node = 0;
    getblock();

    // Scan all nodes in this block to find a match
    lastblockno = block.info.index;
    do {
      node++;
      if(namebrowse) {
        #ifdef DEBUG
        printnode();
        #endif
        prevdiff = diff;
        diff = namecmp();
        #ifdef DEBUG
        printf("{%d}\n", diff);
        #endif
        if(diff <= 0) {
          if(diff == 0) {
            exactmatch = true;
          }
          break;
        }
        lastblockno = namerec().block_num;
      }
      else {
        #ifdef DEBUG
        printnode();
        #endif
        prevdiff = diff;
        diff = addrcmp();
        #ifdef DEBUG
        printf("{%d}\n", diff);
        #endif
        if(diff <= 0) {
          if(diff == 0) {
            exactmatch = true;
          }
          break;
        }
        lastblockno = addrrec().block_num;
      }
    } while(node < block.info.nodes);

    if(not exactmatch) {
      if(lastblockno) {
        push();
        blockno = lastblockno;
      }
      else {
        break;
      }
    }
  }

  if(diff == 0) {
    #ifdef DEBUG
    printf("Gotcha!\n");
    #endif
    while(previous()) {
      if(not exactmatch) {
        next();
        break;
      }
    }
    return true;
  }
  else 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
  }

  getnodedata();

  return exactmatch;
}


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

bool ftn_frontdoor_nodelist_index::prevnode() {

  if(node == 1) {
    if(blockno == 1)
      return false;
    if(depth > 0) {
      pop();
      getblock();
      node--;
      if(node <= 0) {
        blockno = block.info.index;
        getblock();
        node = block.info.nodes;
      }
    }
    else {
      return false;
    }
  }
  else {
    node--;
    if(noderec().block_num) {
      node++;
      push();
      node--;
      blockno = noderec().block_num;
      getblock();
      node = block.info.nodes;
    }
  }

  return true;
}


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

bool ftn_frontdoor_nodelist_index::nextnode() {

  if(noderec().block_num) {
    // Current node has a block. Go to it.
    node++;
    push();
    node--;
    blockno = noderec().block_num;
    node = 1;
    getblock();
  }
  else if(node >= block.info.nodes) {
    // Reached end of nodes in current block.
    if(depth > 0) {
      if((stack[depth-1].node == stack[depth-1].maxnodes) and (stack[depth-1].block_num == blockno))
        return false;
      // Go back to previous block
      pop();
      getblock();
    }
    else {
      // Reached final leaf
      return false;
    }
  }
  else {
    node++;
  }

  return true;
}


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

ftn_frontdoor_nodelist_index::ftn_frontdoor_nodelist_index() {

  fdfd = nfd  = pfd  = ppfd = ufd  = -1;
  is_intermail = false;
  isopen = false;
  blocksize = 0;
  depth = 0;
}


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

ftn_frontdoor_nodelist_index::~ftn_frontdoor_nodelist_index() {

  if(isopen)
    close();
}


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

bool ftn_frontdoor_nodelist_index::open() {

  if(isopen)
    close();

  if(fexist(AddPath(nlpath, "IMNODE.CTL")))
    is_intermail = true;

  // Open the nodelist binary file first
  fdfd = ::sopen(AddPath(nlpath, "NODELIST.FDX"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
  if(fdfd == -1) {
    close();
    return false;
  }

  // Read the control block up front
  read(fdfd, &ctl, sizeof(_FDCtl));

  // Now use that info to open the primary nodelist
  Path primary;
  sprintf(nodelist, "NODELIST.%3.3s", ctl.nl_ext);
  sprintf(primary, "%s%s", nlpath, nodelist);
  nfd = ::sopen(primary, O_RDONLY | O_BINARY, SH_DENYNO, S_STDRD);
  // Don't check for failure, since there may not be a primary nodelist!
  
  // Open the userlist file
  ufd = ::sopen(AddPath(nlpath, "USERLIST.FDX"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);
  if(ufd == -1) {
    close();
    return false;
  }
  
  // Failing to open the private nodelist is not an error, since there may not be one!
  pfd = ::sopen(AddPath(nlpath, is_intermail ? "IMNET.PVT" : "FDNET.PVT"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);

  // Failing to open the point nodelist is not an error, since there may not be one!
  ppfd = ::sopen(AddPath(nlpath, is_intermail ? "IMPOINT.PVT" : "FDPOINT.PVT"), O_RDONLY|O_BINARY, SH_DENYNO, S_STDRD);

  isopen = true;

  return true;
}


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

void ftn_frontdoor_nodelist_index::close() {

  if(fdfd != -1)  ::close(fdfd);  fdfd = -1;
  if(nfd != -1)   ::close(nfd);   nfd  = -1;
  if(pfd != -1)   ::close(pfd);   pfd  = -1;
  if(ppfd != -1)  ::close(ppfd);  ppfd = -1;
  if(ufd != -1)   ::close(ufd);   ufd  = -1;

  isopen = false;
}


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

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

  char buf[80], tmp[80];

  strcpy(tmp, lookup_name);
  namebrowse = true;
  char* ptr = strrchr(tmp, ' ');
  if(ptr) {
    if(ptr == (tmp+strlen(tmp)-1)) {
      while((*ptr == ' ') and (ptr > tmp))
        ptr--;
      ptr++;
    }
    *ptr = NUL;
    sprintf(buf, "%s %s", ptr+1, tmp);
    *ptr = ' ';
  }
  else {
    strcpy(buf, tmp);
  }
  strupr(strtrim(buf));
  sprintf(searchname, "%-15.15s", buf);

  return search();
}


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

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

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


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

bool ftn_frontdoor_nodelist_index::previous() {

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


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

bool ftn_frontdoor_nodelist_index::next() {

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


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

void ftn_frontdoor_nodelist_index::first() {

  if(namebrowse) {
    memset(searchname, ' ', sizeof(searchname)-1);
    searchname[sizeof(searchname)-1] = NUL;
  }
  else {
    searchaddr.reset();
  }
  search();
}


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

void ftn_frontdoor_nodelist_index::last() {

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


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

void ftn_frontdoor_nodelist_index::push_state() {

  state.node = node;
  state.depth = depth;
  state.blockno = blockno;
  memcpy(state.stack, stack, sizeof(stack));
}


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

void ftn_frontdoor_nodelist_index::pop_state() {

  memcpy(stack, state.stack, sizeof(stack));
  blockno = state.blockno;
  depth = state.depth;
  node = state.node;
  getblock();
  getnodedata();
  compare();
}


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

const char* ftn_frontdoor_nodelist_index::index_name() const {

  return namebrowse ? "USERLIST.FDX" : "NODELIST.FDX";
}


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

const char* ftn_frontdoor_nodelist_index::nodelist_name() const {

  if(noderec().nlofs & IN_FDNODE)
    return "FDNODE.FDA";
  else if(noderec().nlofs & IN_FDNET)
    return is_intermail ? "IMNET.PVT" : "FDNET.PVT";
  else if(noderec().nlofs & IN_FDPOINT)
    return is_intermail ? "IMPOINT.PVT" : "FDPOINT.PVT";
  return nodelist;
}


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