//  ------------------------------------------------------------------
//  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$
//  ------------------------------------------------------------------
//  Userbase (Addressbook) functions.
//  ------------------------------------------------------------------

#include <cerrno>
#include <fcntl.h>
#include <golded.h>
#include <geusrbse.h>
#include <gftnnl.h>


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

guserbase::guserbase() {

  long tries = 0;

  strcpy(fname, AddPath(CFG->goldpath, CFG->golduser));

  do {
    usrbase.open(fname, O_RDWR|O_CREAT|O_BINARY, SH_DENYNO, S_STDRW);
    if(not usrbase) {
      if((errno != EACCES) or (not PopupLocked(++tries, false, fname))) {
        WideLog->ErrOpen();
        WideLog->printf("! Addressbook cannot be opened.");
        WideLog->printf(": %s", fname);
        WideLog->ErrOSInfo();
        OpenErrorExit();
      }
    }
  } while(not usrbase);

  if(tries)
    PopupLocked(0, 0, NULL);

  if((uint) usrbase.filelength() < sizeof(gusrbaseheader) + sizeof(gusrbaseentry)) {

    header.version = 0;

    strcpy(entry.macro, "_asa_");
    strcpy(entry.name, "Alexander S. Aganichev");
    entry.fidoaddr.reset();
    entry.fidoaddr.zone = 2;
    entry.fidoaddr.net  = 5020;
    entry.fidoaddr.node = 201;
    entry.fidoaddr.point = 58;
    strcpy(entry.iaddr, "aaganichev@yandex.ru");
    entry.prefer_internet = YES;
    entry.is_deleted = NO;
    strcpy(entry.pseudo, "");
    strcpy(entry.organisation, "");
    strcpy(entry.snail1, "Zelenograd");
    strcpy(entry.snail2, "Moscow");
    strcpy(entry.snail3, "Russia");
    entry.dataphone[0] = NUL;
    strcpy(entry.voicephone, "+7-(095)-535-10-42");
    entry.faxphone[0] = NUL;
    entry.firstdate = entry.lastdate = entry.times = 0;
    strcpy(entry.homepage, "http://aaganichev.narod.ru");
    entry.group = 0;
    strcpy(entry.comment1, "GoldED+ Maintainer");
    entry.comment2[0] = NUL;
    entry.comment3[0] = NUL;

    usrbase.lseek(0, SEEK_SET);
    usrbase.write(&header.version, sizeof(header.version));
    write_entry(0);
  }

  index = 0;
  read_time = 0;  // Indicate that we're should reread timestamp
  refresh_maximum_index();
  need_update = false;
}


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

guserbase::~guserbase() {

  usrbase.close();
}


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

void guserbase::refresh_maximum_index() {

  // Are we doing it for the first time?
  if(not read_time) {
    usrbase.getftime(&read_time);
    need_update = true;
  }
  else {
    dword tmp;
    usrbase.getftime(&tmp);
    if(read_time != tmp) {
      read_time = tmp;
      need_update = true;
    }
  }
  if(need_update)
    maximum_index = (usrbase.filelength()-sizeof(gusrbaseheader)) / sizeof(gusrbaseentry) - 1;
  if(index > maximum_index)
    index = maximum_index;
}


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

void guserbase::lock() {

  if(WideCanLock) {

    long tries = 0;

    do {
      usrbase.lock(0, 1);
      if(not usrbase.okay()) {
        if(not PopupLocked(++tries, false, fname)) {
          WideLog->ErrLock();
          WideLog->printf("! GoldED's Addressbook could not be locked.");
          WideLog->printf(": %s", fname);
          WideLog->ErrOSInfo();
          LockErrorExit();
        }
      }
    } while(not usrbase.okay());
    if(tries)
      PopupLocked(0, 0, NULL);
  }
}


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

void guserbase::unlock() {

  if(WideCanLock)
    usrbase.unlock(0, 1);
}


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

void guserbase::open() {

  window.openxy(ypos, xpos, ylen+2, xlen+2,  btype, battr, LGREY_|_BLACK);
  cwidth = (xlen-28) / 2;

  window.message(LNG->UserHeaderName, TP_BORD, 3, tattr);
  window.message(LNG->UserHeaderOrg,  TP_BORD, 4+cwidth, tattr);
  window.message(LNG->UserHeaderAka,  TP_BORD, 5+(cwidth*2)/3 + cwidth, tattr);

  center(CFG->displistcursor);
}


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

void guserbase::close() {

  window.close();
}


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

void guserbase::do_delayed() {

  wscrollbar(W_VERT, maximum_index+1, maximum_index, index);
  update_statuslinef(LNG->UserStatusline, "ST_USERSTATUSLINE", index+1, maximum_index+1, maximum_index-index);
}


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

void guserbase::print_line(uint idx, uint pos, bool isbar) {

  char buf[200];
  char buf2[100];

  read_entry(idx);

  *buf2 = NUL;

  if(AA->isinternet() or not entry.fidoaddr.valid()) {
    if(*entry.iaddr) {
      strcat(buf2, "<");
      strcat(buf2, entry.iaddr);
      strcat(buf2, ">");
    }
  }
  else {
    if(entry.fidoaddr.valid()) {
      *buf2 = '(';
      entry.fidoaddr.make_string(buf2+1);
      strcat(buf2, ")");
    }
  }

  sprintf(buf, "%c %-*.*s %-*.*s %s ",
    entry.is_deleted ? 'D' : ' ',
    cwidth, (int)cwidth, entry.name,
    (cwidth*2)/3, (int)(cwidth*2)/3, entry.organisation,
    buf2);

  strsetsz(buf, xlen);
  window.prints(pos, 0, isbar ? sattr : wattr, buf);
}


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

addressbook_form::~addressbook_form() { }
addressbook_form::addressbook_form(gwindow& w) : gwinput2(w) { }
void addressbook_form::after() { gwinput2::after(); }
void addressbook_form::before() { gwinput2::before(); }


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

bool addressbook_form::validate() {

  return true;
}


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

void addressbook_form::LoadForm() {

  gusrbaseentry& entry = g->entry;

  entry.fidoaddr.make_string(fidoaddr);
  name      = entry.name;
  macro     = entry.macro;
  pseudo    = entry.pseudo;
  iaddr     = entry.iaddr;
  organisation = entry.organisation;
  voicephone= entry.voicephone;
  faxphone  = entry.faxphone;
  dataphone = entry.dataphone;
  group     = entry.group;
  snail1    = entry.snail1;
  snail2    = entry.snail2;
  snail3    = entry.snail3;
  comment1  = entry.comment1;
  comment2  = entry.comment2;
  comment3  = entry.comment3;
  homepage  = entry.homepage;
}


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

void addressbook_form::SaveForm() {

  gusrbaseentry& entry = g->entry;
  entry.fidoaddr.reset();
  entry.fidoaddr.set(fidoaddr);
  strcpy(entry.name, name.c_str());
  strcpy(entry.macro, macro.c_str());
  strcpy(entry.pseudo, pseudo.c_str());
  strcpy(entry.iaddr, iaddr.c_str());
  strcpy(entry.organisation, organisation.c_str());
  strcpy(entry.voicephone, voicephone.c_str());
  strcpy(entry.faxphone, faxphone.c_str());
  strcpy(entry.dataphone, dataphone.c_str());
  entry.group = group.c_str()[0];
  strcpy(entry.snail1, snail1.c_str());
  strcpy(entry.snail2, snail2.c_str());
  strcpy(entry.snail3, snail3.c_str());
  strcpy(entry.comment1, comment1.c_str());
  strcpy(entry.comment2, comment2.c_str());
  strcpy(entry.comment3, comment3.c_str());
  strcpy(entry.homepage, homepage.c_str());
}


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

bool guserbase::edit_entry(uint idx) {

  gwindow window;
  char tbuf[50];

  const int width = 75;
  const int height = 16;

  window.openxy((MAXROW-height)/2, (MAXCOL-width)/2, height, width, btype, battr, wattr);
  window.shadow(C_SHADOW);

  sprintf(tbuf, " Record <%d> ", idx+1);
  window.title(tbuf, tattr);

  window.prints( 0, 1, wattr, "Full Name :");
  window.prints( 1, 1, wattr, "Macro Name:");
  window.prints( 1,34, wattr, "Nick Name :");
  window.prints( 2, 1, wattr, "Fidonet   :");
  window.prints( 2,34, wattr, "Internet  :");
  window.prints( 3, 1, wattr, "Organisat.:");
  window.horizontal_line(4, 0, width-2, btype, battr);

  window.prints( 5, 1, wattr, "Voice Num.:");
  window.prints( 6, 1, wattr, "Fax Number:");
  window.prints( 7, 1, wattr, "Data Num. :");
  window.prints( 5,34, wattr, "Address(1):");
  window.prints( 6,34, wattr, "Address(2):");
  window.prints( 7,34, wattr, "Address(3):");
  window.prints( 8, 1, wattr, "Group     :");
  window.prints( 8,34, wattr, "Homepage  :");
  window.prints( 9, 1, wattr, "Comment(1):");
  window.prints(10, 1, wattr, "Comment(2):");
  window.prints(11, 1, wattr, "Comment(3):");
  window.horizontal_line(12, 0, width-2, btype, battr);

  window.prints(13, 1, wattr, "First Used:");
  window.prints(13, 27, wattr, "Last Used:");
  window.prints(13, 53, wattr, "Times Used:");

  char dbuf[16];
  time32_t dt = entry.firstdate;
  if(dt)
    window.prints(13, 13, wattr, strftimei(dbuf, 16, "%d %b %y", ggmtime(&dt)));
  dt = entry.lastdate;
  if(dt)
    window.prints(13, 38, wattr, strftimei(dbuf, 16, "%d %b %y", ggmtime(&dt)));

  sprintf(dbuf, "%8u", entry.times);
  window.prints(13, width-11, wattr, dbuf);

  addressbook_form form(window);
  form.g = this;
  form.setup(C_HEADW, C_HEADW, C_HEADE, _box_table(W_BHEAD, 13), true);

  read_entry(idx);
  form.LoadForm();

  form.add_field(addressbook_form::id_name,         0, 13, 59, form.name, sizeof(entry.name));
  form.add_field(addressbook_form::id_macro,        1, 13, 20, form.macro, sizeof(entry.macro));
  form.add_field(addressbook_form::id_pseudo,       1, 46, 26, form.pseudo, sizeof(entry.pseudo));
  form.add_field(addressbook_form::id_fidoaddr,     2, 13, 20, form.fidoaddr, 24);
  form.add_field(addressbook_form::id_iaddr,        2, 46, 26, form.iaddr, sizeof(entry.iaddr));
  form.add_field(addressbook_form::id_organisation, 3, 13, 59, form.organisation, sizeof(entry.organisation));
  form.add_field(addressbook_form::id_voicephone,   5, 13, 20, form.voicephone, sizeof(entry.voicephone));
  form.add_field(addressbook_form::id_faxphone,     6, 13, 20, form.faxphone, sizeof(entry.faxphone));
  form.add_field(addressbook_form::id_dataphone,    7, 13, 20, form.dataphone, sizeof(entry.dataphone));
  form.add_field(addressbook_form::id_group,        8, 13, 20, form.group, sizeof(entry.group)+1);
  form.add_field(addressbook_form::id_snail1,       5, 46, 26, form.snail1, sizeof(entry.snail1));
  form.add_field(addressbook_form::id_snail2,       6, 46, 26, form.snail2, sizeof(entry.snail2));
  form.add_field(addressbook_form::id_snail3,       7, 46, 26, form.snail3, sizeof(entry.snail3));
  form.add_field(addressbook_form::id_homepage,     8, 46, 26, form.homepage, sizeof(entry.homepage));
  form.add_field(addressbook_form::id_comment1,     9, 13, 59, form.comment1, sizeof(entry.comment1));
  form.add_field(addressbook_form::id_comment2,    10, 13, 59, form.comment2, sizeof(entry.comment2));
  form.add_field(addressbook_form::id_comment3,    11, 13, 59, form.comment3, sizeof(entry.comment3));

  form.run(H_EditAdrEntry);
  window.close();

  if(not form.dropped)
    form.SaveForm();

  return not form.dropped;
}


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

bool guserbase::find_entry(char* name, bool lookup) {

  if(not strblank(name)) {
    std::string tmpaddr;
    gusrbaseentry old_entry = entry;
    uint old_index = index;

    refresh_maximum_index();
    usrbase.lseek(sizeof(gusrbaseheader), SEEK_SET);
    for(index=0; index<=maximum_index; index++) {
      read_entry(index, &entry);

      if(strieql(name, entry.name))
        return true;
      else if(lookup) {
        if(strieql(name, entry.macro) or streql(name, entry.iaddr))
          return true;
        else {
          entry.fidoaddr.make_string(tmpaddr);
          if (streql(name, tmpaddr.c_str()))
            return true;
        }
      }
    }

    index = old_index;
    entry = old_entry;
  }

  return false;
}


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

void guserbase::write_entry(uint idx, bool updateit) {

  if(updateit and not entry.is_deleted) {
    time32_t a    = gtime(NULL);
    struct tm *tp = ggmtime(&a);
    tp->tm_isdst  = -1;
    time32_t b    = gmktime(tp);
    entry.lastdate = a + a - b;
    if(not entry.firstdate)
      entry.firstdate = entry.lastdate;

    entry.times++;
  }

  usrbase.lseek(sizeof(gusrbaseheader) + sizeof(gusrbaseentry)*(idx+1)-1, SEEK_SET);
  char z = 0;
  usrbase.write(&z, 1); // adjust entry size first...
  usrbase.lseek(sizeof(gusrbaseheader) + sizeof(gusrbaseentry)*idx, SEEK_SET);
  usrbase.write(entry.macro, sizeof(entry.macro));
  usrbase.write(entry.name, sizeof(entry.name));
  usrbase.write(&entry.fidoaddr.zone, sizeof(entry.fidoaddr.zone));
  usrbase.write(&entry.fidoaddr.net, sizeof(entry.fidoaddr.net));
  usrbase.write(&entry.fidoaddr.node, sizeof(entry.fidoaddr.node));
  usrbase.write(&entry.fidoaddr.point, sizeof(entry.fidoaddr.point));
  usrbase.write(entry.iaddr, sizeof(entry.iaddr));
  usrbase.write(&entry.prefer_internet, sizeof(entry.prefer_internet));
  usrbase.write(&entry.is_deleted, sizeof(entry.is_deleted));
  usrbase.write(entry.pseudo, sizeof(entry.pseudo));
  usrbase.write(entry.organisation, sizeof(entry.organisation));
  usrbase.write(entry.snail1, sizeof(entry.snail1));
  usrbase.write(entry.snail2, sizeof(entry.snail2));
  usrbase.write(entry.snail3, sizeof(entry.snail3));
  usrbase.write(entry.dataphone, sizeof(entry.dataphone));
  usrbase.write(entry.voicephone, sizeof(entry.voicephone));
  usrbase.write(entry.faxphone, sizeof(entry.faxphone));
  usrbase.write(&entry.firstdate, sizeof(entry.firstdate));
  usrbase.write(&entry.lastdate, sizeof(entry.lastdate));
  usrbase.write(&entry.times, sizeof(entry.times));
  usrbase.write(entry.homepage, sizeof(entry.homepage));
  usrbase.write(&entry.group, sizeof(entry.group));
  usrbase.write(entry.comment1, sizeof(entry.comment1));
  usrbase.write(entry.comment2, sizeof(entry.comment1));
  usrbase.write(entry.comment3, sizeof(entry.comment1));
}

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

void guserbase::clear_entry(gusrbaseentry *ent) {

  ent->macro[0] = NUL;
  ent->name[0] = NUL;
  ent->fidoaddr.reset();
  ent->iaddr[0] = NUL;
  ent->prefer_internet = NO;
  ent->is_deleted = NO;
  ent->pseudo[0] = NUL;
  ent->organisation[0] = NUL;
  ent->snail1[0] = NUL;
  ent->snail2[0] = NUL;
  ent->snail3[0] = NUL;
  ent->dataphone[0] = NUL;
  ent->voicephone[0] = NUL;
  ent->faxphone[0] = NUL;
  ent->firstdate = ent->lastdate = ent->times = 0;
  ent->homepage[0] = NUL;
  ent->group = 0;
  ent->comment1[0] = NUL;
  ent->comment2[0] = NUL;
  ent->comment3[0] = NUL;
}

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

bool guserbase::read_entry(uint idx, gusrbaseentry *ent) {

  if(ent == NULL)
    ent = &entry;
  refresh_maximum_index();
  if(idx > maximum_index) {
    clear_entry(ent);
    return false;
  }
  else {
    usrbase.lseek(idx*sizeof(gusrbaseentry)+sizeof(gusrbaseheader), SEEK_SET);
    usrbase.read(ent->macro, sizeof(ent->macro));
    usrbase.read(ent->name, sizeof(ent->name));
    usrbase.read(&ent->fidoaddr.zone, sizeof(ent->fidoaddr.zone));
    usrbase.read(&ent->fidoaddr.net, sizeof(ent->fidoaddr.net));
    usrbase.read(&ent->fidoaddr.node, sizeof(ent->fidoaddr.node));
    usrbase.read(&ent->fidoaddr.point, sizeof(ent->fidoaddr.point));
    usrbase.read(ent->iaddr, sizeof(ent->iaddr));
    usrbase.read(&ent->prefer_internet, sizeof(ent->prefer_internet));
    usrbase.read(&ent->is_deleted, sizeof(ent->is_deleted));
    usrbase.read(ent->pseudo, sizeof(ent->pseudo));
    usrbase.read(ent->organisation, sizeof(ent->organisation));
    usrbase.read(ent->snail1, sizeof(ent->snail1));
    usrbase.read(ent->snail2, sizeof(ent->snail2));
    usrbase.read(ent->snail3, sizeof(ent->snail3));
    usrbase.read(ent->dataphone, sizeof(ent->dataphone));
    usrbase.read(ent->voicephone, sizeof(ent->voicephone));
    usrbase.read(ent->faxphone, sizeof(ent->faxphone));
    usrbase.read(&ent->firstdate, sizeof(ent->firstdate));
    usrbase.read(&ent->lastdate, sizeof(ent->lastdate));
    usrbase.read(&ent->times, sizeof(ent->times));
    usrbase.read(ent->homepage, sizeof(ent->homepage));
    usrbase.read(&ent->group, sizeof(ent->group));
    usrbase.read(ent->comment1, sizeof(ent->comment1));
    usrbase.read(ent->comment2, sizeof(ent->comment1));
    usrbase.read(ent->comment3, sizeof(ent->comment1));
    return true;
  }
}


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

void guserbase::pack_addressbook() {

  long tries = 0;

  lock();
  refresh_maximum_index();
  uint nidx = 0;
  uint nindex = index;
  for(uint idx = 0; idx <= maximum_index; idx++) {
    read_entry(idx);
    if(not entry.is_deleted) {
      if(nidx != idx)
        write_entry(nidx);
      ++nidx;
    }
    else if(idx < index)
      --nindex;
  }
  index = nindex;
  // zap
  maximum_index = nidx;
  // At least one record should present
  if(maximum_index)
    --maximum_index;
  usrbase.chsize((maximum_index + 1) * sizeof(gusrbaseentry) + sizeof(gusrbaseheader));
  usrbase.close();

  do {
    usrbase.open(fname, O_RDWR|O_CREAT|O_BINARY, SH_DENYNO, S_STDRW);
    if(not usrbase) {
      if((errno != EACCES) or (not PopupLocked(++tries, false, fname))) {
        WideLog->ErrOpen();
        WideLog->printf("! GoldED's Addressbook cannot be opened.");
        WideLog->printf(": %s", fname);
        WideLog->ErrOSInfo();
        OpenErrorExit();
      }
    }
  } while(not usrbase);

  if(tries)
    PopupLocked(0, 0, NULL);

  unlock();
}


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

void guserbase::update_screen(bool force) {

  refresh_maximum_index();
  if(force or need_update) {
    if(position > index)
      center(CFG->displistcursor);
    update();
    do_delayed();
    need_update = false;
  }
}


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

bool guserbase::handle_key() {

  gkey kk;

  if(key < KK_Commands) {
    key = key_tolower(key);
    kk = SearchKey(key, AddressbookKey, AddressbookKeys);
    if(kk)
      key = kk;
  }

  switch(key) {
    case KK_AddressbookQuit:
      aborted = true;
      return false;
    case KK_AddressbookAdd:
      {
        clear_entry(&entry);
        lock();
        refresh_maximum_index();
        uint nidx = maximum_index + 1;
        if(edit_entry(nidx)) {
          if(nidx == maximum_index + 1) {
            maximum_index++;
            if(maximum_position < ylen-1)
              maximum_position = maximum_index;
          }
          write_entry(nidx);
          index = nidx;
        }
        unlock();
        center(CFG->displistcursor);
      }
      break;
    case KK_AddressbookSelect:
      if(not select_list) {
        if(not entry.is_deleted) {
          lock();
          if(edit_entry(index)) {
            write_entry(index);
            display_bar();
          }
          unlock();
        }
        break;
      }
      else {
        aborted = false;
        return false;
      }
    case KK_AddressbookDelete:
      lock();
      refresh_maximum_index();
      read_entry(index);
      entry.is_deleted = not entry.is_deleted;
      write_entry(index);
      unlock();
      update_screen();
      break;
    case KK_AddressbookPack:
      pack_addressbook();
      index = position = 0;
      update_screen();
      break;

    case Key_Tick:
      CheckTick(0);    // KK_UserQuitNow ???
      break;
    default:
      SayBibi();
  }
  return true;
}


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

bool guserbase::run(GMsg* msg, bool selectonly) {

  select_list = selectonly;

  ypos    = selectonly ? 6 : 1;
  xpos    = 0;
  ylen    = MAXROW-3-ypos;
  xlen    = MAXCOL-2;
  btype   = W_BMENU;
  battr   = C_MENUB;
  wattr   = C_MENUW;
  tattr   = C_MENUT;
  sattr   = C_MENUS;
  hattr   = C_MENUQ;
  sbattr  = C_MENUPB;
  helpcat = H_Userbase;
  listwrap  = CFG->switches.get(displistwrap);
  maximum_position = MinV(maximum_index, ylen - 1);

  if(not select_list)
    find_entry(msg->By(), false);

  run_picker();

  if(not aborted) {
    read_entry(index);

    // other stuff not yet implemented
  }

  return not aborted;
}


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

void guserbase::update_addressbook(GMsg* msg, bool reverse, bool force) {

  Addr fidoaddr;
  IAdr iaddr;
  INam name;

  strcpy(name, (reverse ? msg->By() : msg->To()));
  strcpy(iaddr, (reverse ? (*msg->iorig ? msg->iorig : msg->iaddr) : msg->idest));
  fidoaddr = (reverse ? msg->orig : msg->dest);

  if(not strblank(name)) {
    if(find_entry(name)) {
      if(not force)
        if((CFG->addressbookadd == NO) or ((CFG->addressbookadd == YES) and not (AA->isnet() or AA->isemail())))
          return;
    }
    else {

      if(not force) {

        // Don't automatically add the user if...
        // 1. It's not allowed
        if((CFG->addressbookadd == NO) or ((CFG->addressbookadd == YES) and not (AA->isnet() or AA->isemail())))
          return;

        // 2. It's a robotname
        for(gstrarray::iterator n = CFG->robotname.begin(); n != CFG->robotname.end(); n++)
          if(striinc(n->c_str(), name))
            return;

        // 3. It's a username
        if(is_user(name))
          return;

        // 4. It's a WHOTO name
        if(strieql(AA->Whoto(), name))
          return;

        // 5. It's the mailinglist's sender address
        if(AA->isemail()) {
          std::vector<MailList>::iterator z;
          for(z = CFG->mailinglist.begin(); z != CFG->mailinglist.end(); z++)
            if(strieql(z->sender, name) or strieql(z->sender, iaddr))
              return;
        }

        // 6. It's a UUCP name
        if(isuucp(name))
          return;

        // 7. If it is already an email address
        if(AA->isemail() and strchr(name, '@'))
          return;


        // 8. User listed in nodelist
        const char *nlname = lookup_nodelist(&fidoaddr);
        if(nlname and strieql(nlname, name))
          return;

        // 9. It is an addressmacro
        if(not CFG->addressmacro.empty()) {
          std::vector<AddrMacro>::iterator n;
          for(n=CFG->addressmacro.begin(); n != CFG->addressmacro.end(); n++) {
            if(strieql(name, n->name) or strieql(n->name, iaddr))
              return;
            if(iaddr[0] != NUL) {
              INam buf, buf1, buf2;
              char *ptr;

              if(n->name[0] == '@') {
                strcpy(buf, n->name+1);
                // UUCP/INTERNET addressing
                ptr = strchr(buf, '/');
                if(ptr) {
                  *ptr++ = NUL;
                  if(strieql(name, buf))
                    return;
                }
                else
                  ptr = buf;
              }
              else {
                ptr = n->name;
              }
              ParseInternetAddr(ptr, buf1, buf2);
              if(strieql(iaddr, buf2) or strieql(name, buf1))
                return;
            }
          }
        }
      }

      // Ok, let's add it...
      clear_entry(&entry);

      strxcpy(entry.name, name, sizeof(entry.name));

      refresh_maximum_index();
      index = ++maximum_index;
    }

    if((AA->isinternet() and strblank(iaddr)) or (not AA->isinternet() and not fidoaddr.valid()))
      return;

    // Update address
    if(AA->isinternet() or (not *entry.iaddr and *iaddr))
      strxcpy(entry.iaddr, iaddr, sizeof(entry.iaddr));
    // If iaddr is not empty then we adding entry from gated message
    if(not AA->isinternet() and not *iaddr and (not AA->Internetgate().addr.valid() or (AA->Internetgate().addr != fidoaddr)))
      entry.fidoaddr = fidoaddr;

    lock();
    write_entry(index, not force);
    unlock();
  }
}


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

bool guserbase::lookup_addressbook(GMsg* msg, char* name, char* aka, bool browser) {

  bool found = false;

  if(not strblank(name) or browser) {
    w_info(LNG->UserWait);

    if((find_entry(name, true) and not entry.is_deleted) or browser) {

      if(browser) {
        if(not run(msg, true)) {
          w_info(NULL);
          return false;
        }
      }

      found = true;

      if(not entry.fidoaddr.valid() and not strblank(entry.iaddr) and not AA->isinternet()) {
        // do UUCP addressing
        strcpy(msg->realto, entry.name);
        if(*AA->Internetgate().name)
          strcpy(name, AA->Internetgate().name);
        else
          strcpy(name, entry.name);
      }
      else {
        strcpy(name, entry.name);
        if(AA->isinternet())
          strcpy(msg->realto, entry.name);
        else
          *msg->realto = NUL;
      }

      if(not strblank(entry.pseudo))
        strcpy(msg->pseudoto, entry.pseudo);

      if(aka) {
        *aka = NUL;
        if(AA->isinternet()) {
          strcpy(aka, entry.iaddr);
          strcpy(msg->idest, entry.iaddr);
          strcpy(msg->iaddr, entry.iaddr);
        }
        else {
          entry.fidoaddr.make_string(aka);
          if(strblank(aka) and not strblank(entry.iaddr)) {
            strcpy(msg->idest, entry.iaddr);
            strcpy(msg->iaddr, entry.iaddr);
            if(AA->Internetgate().addr.valid())
              AA->Internetgate().addr.make_string(aka);
          }
          else {
            *msg->iaddr = NUL;
            *msg->idest = NUL;
          }
        }
      }
    }
    w_info(NULL);
    return found ? (aka ? not strblank(aka) : true) : false;
  }
  return false;
}


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

void guserbase::build_pseudo(GMsg* msg, char* name, char* aka, bool direction) {

  if(*msg->iaddr and isuucp(name)) {
    if(direction)
      strcpy(msg->pseudoto, strlword(msg->iaddr, " @."));
    else
      strcpy(msg->pseudofrom, strlword(msg->iorig, " @."));
  }
  else
    strcpy(direction ? msg->pseudoto : msg->pseudofrom, strlword(name, " @."));

  if(find_entry(name, true) and not entry.is_deleted) {

    if(not strblank(entry.pseudo)) {

      if(AA->isinternet()) {
        if(strcmp(aka, entry.iaddr))
          return;
      }
      else {
        if(entry.fidoaddr.valid()) {
          Addr AKA = aka;
          if(entry.fidoaddr != AKA)
            return;
        }
      }

      strcpy(direction ? msg->pseudoto : msg->pseudofrom, entry.pseudo);
    }
  }
}


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

void update_addressbook(GMsg* msg, bool reverse, bool force) {

  guserbase p;

  p.update_addressbook(msg, reverse, force);
}


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

bool lookup_addressbook(GMsg* msg, char* name, char* aka, bool browser) {

  guserbase p;

  bool result = p.lookup_addressbook(msg, name, aka, browser);

  return result;
}


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

void build_pseudo(GMsg* msg, bool direction) {

  guserbase p;
  char buf[128];

  if(direction)
    p.build_pseudo(msg, strbtrim(msg->To()), AA->isinternet() ? msg->iaddr : msg->dest.make_string(buf));
  else
    p.build_pseudo(msg, strbtrim(msg->By()), AA->isinternet() ? msg->iorig : msg->orig.make_string(buf), false);
}


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

void edit_addressbook(GMsg* msg) {

  guserbase p;

  p.run(msg, false);

}


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