/*------------------------------------------------------------------------
 *
 *  File:	o1k_ab.cc
 *  Revision:	1.5
 *  Last Mod:	17:13:21, 03/05/97
 *
 *  Description:
 *     Implementation of the O1kAddrBook object
 *     
 *----------------------------------------------------------------------*/
#pragma ident	"@(#)o1k_ab.cc	1.5\t97/03/05  "

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ndbm.h>

#include "o1k_conf.h"

#define NUM_STRINGS 5

static DBM *db = NULL;

class O1kUserNode;

/*------------------------------------------------------------------------
 *
 *  Class:	O1kUserNode
 *
 * Description:
 *     A node for a doubly-linked O1kUserList. The node contains
 *     user info as well as pointers to the next and previous
 *     O1kUserNodes. A routine greaterThan() is provided for
 *     sorting ability.
 *     
 *----------------------------------------------------------------------*/

class O1kUserNode {

public:  
  char first[O1K_AB_MAX_STRING];
  char last[O1K_AB_MAX_STRING];  
  char email[O1K_AB_MAX_STRING];  
  char phone[O1K_AB_MAX_STRING];  
  char userhost[O1K_AB_MAX_STRING];

  O1kUserNode* next;
  O1kUserNode* prev;
 
  O1kUserNode();
  ~O1kUserNode();

  void set(char* f, char* l, char* e, char* p, char* u);
  int  greaterThan(O1kUserNode* unp, int cmp_type);
};

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserNode::O1kUserNode()
 *
 *  Description:
 *     Init default O1kUserNode user info and set next/prev to NULL.
 *     
 *----------------------------------------------------------------------*/

O1kUserNode::O1kUserNode()
{
  strcpy(first,    "");
  strcpy(last,     "");
  strcpy(email,    "");
  strcpy(phone,    "");
  strcpy(userhost, "");

  next = prev = NULL;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserNode::~O1kUserNode()
 *
 *  Description:
 *     Does nothing.
 *     
 *----------------------------------------------------------------------*/

O1kUserNode::~O1kUserNode()
{
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserNode::set()
 *
 *  Description:
 *     Sets user info
 *     
 *----------------------------------------------------------------------*/

void O1kUserNode::set(char* f, char* l, char* e, char* p, char* u)
{
  strcpy(first,    f);
  strcpy(last,     l);
  strcpy(email,    e);
  strcpy(phone,    p);
  strcpy(userhost, u);  
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserNode::greaterThan()
 *
 *  Description:
 *     Compares this O1kUserNode to the parameter O1kUserNode based
 *     on user info selected by parameter cmp_type. 
 *     
 *  Parameters:
 *     O1kUserNode* node  : user to compare to
 *     int cmp_type       : type of comparison
 *
 *  Returns:
 *     1 if node is greater than parameter O1kUserNode 
 *     
 *----------------------------------------------------------------------*/

int O1kUserNode::greaterThan(O1kUserNode* node, int cmp_type)
{
  char* c1;
  char* c2;
  
  if (!node){
    fprintf(stderr,"error in O1kUserNode::greaterThan\n");
    return 0;
  }

  switch (cmp_type){
    case SORT_USER:  c1 = userhost; c2 = node->userhost; break;
    case SORT_FIRST: c1 = first;    c2 = node->first;    break;
    case SORT_LAST:  c1 = last;     c2 = node->last;     break;
    case SORT_EMAIL: c1 = email;    c2 = node->email;    break;
  }
  
  if (strcmp(c1, c2) > 0){
    return 1;
  } else {
    return 0;
  }
}

/*------------------------------------------------------------------------
 *
 *  Class:	O1kUserList
 *
 * Description:
 *     The O1kUserList is a double-link list of O1kUserNodes.
 *     One can add and remove users from this list. One can
 *     sort this list in different ways.
 *     
 *----------------------------------------------------------------------*/

class O1kUserList {

public:    
  O1kUserNode* head;
  O1kUserNode* tail;

  O1kUserList();
  ~O1kUserList();  

  void addUser(O1kUserNode* unp);
  void removeUser(O1kUserNode* unp);
  void swap(O1kUserNode* n1, O1kUserNode* n2);
  void sort(int cmp_type);
  void print();
};

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::O1kUserList()
 *
 *  Description:
 *     Init O1kUserList
 *     
 *----------------------------------------------------------------------*/

O1kUserList::O1kUserList()
{
  head = tail = NULL;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::~O1kUserList()
 *
 *  Description:
 *     Empty list by removing each user
 *     
 *----------------------------------------------------------------------*/

O1kUserList::~O1kUserList()
{
  while (head)
    removeUser(head);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::addUser()
 *
 *  Description:
 *     Add a user to the list
 *
 *  Parameters:
 *     O1kUserNode* unp  : user to add
 * 
 *----------------------------------------------------------------------*/

void O1kUserList::addUser(O1kUserNode* unp)
{
  if (head == NULL){
    /* list empty */
    head = tail = unp;
    unp->next = unp->prev = NULL;
  } else {
    /* list not empty */
    tail->next = unp;
    unp->next = NULL;
    unp->prev = tail;
    tail = unp;
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::removeUser()
 *
 *  Description:
 *     Remove a user to the list
 *
 *  Parameters:
 *     O1kUserNode* unp  : user to remove
 * 
 *----------------------------------------------------------------------*/

void O1kUserList::removeUser(O1kUserNode* unp)
{
  /* find user */
  O1kUserNode* tmp;

  for (tmp = head; tmp != unp && tmp != NULL; tmp = tmp->next);

  if (tmp == head){
    /* delete node at head */
    head = head->next;
    if (head)
      head->prev = NULL;
    if (tmp == tail)
      tail = head;
  } else if (unp) {
    /* not and head */
    if (tmp == tail){
      tail = tmp->prev;
      tail->next = NULL;
    } else {
      tmp->prev->next = tmp->next;
      tmp->next->prev = tmp->prev;      
    }
  } else {
    fprintf(stderr,"Could not find node 0x%x\n",unp);
    return;
  }

  delete unp;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::swap()
 *
 *  Description:
 *     Swap to users in the list
 *
 *  Parameters:
 *     O1kUserNode* n1  : user 1 to swap
 *     O1kUserNode* n2  : user 2 to swap
 *
 *----------------------------------------------------------------------*/

void O1kUserList::swap(O1kUserNode* n1, O1kUserNode* n2)
{
  if (!n1 || !n2){
    fprintf(stderr,"error O1kUserList::swap %x %x\n",n1,n2);
    return;
  }
  
  if (n1->prev)
    n1->prev->next = n2;
  if (n2->next)
    n2->next->prev = n1;

  n2->prev = n1->prev;
  n1->next = n2->next;  
  
  n1->prev = n2;
  n2->next = n1;

  if (head == n1)
    head = n2;

  if (tail == n2)
    tail = n1;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::sort()
 *
 *  Description:
 *     Simple sort routine to sort list
 *
 *  Parameters:
 *     int s_type   : sort based on this user info type
 *
 *----------------------------------------------------------------------*/

void O1kUserList::sort(int s_type)
{
  int count;
  int i,j;

  O1kUserNode* n1;
  O1kUserNode* n2;

  count = 0;
  for (n1 = head; n1 != NULL; count++, n1 = n1->next);

  if (count > 1){
    for (i=count; i!=1; i--){
      for (n1=n2=head, j=i; j!=1 && n2; j--){ 
        n2 = n1->next;
        if (n1->greaterThan(n2, s_type)){
          swap(n1, n2);
        } else {
          n1 = n2;
        if (n2)
          n2 = n2->next;
        }
      }
    }
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kUserList::print()
 *
 *  Description:
 *     Debug print of list
 *
 *----------------------------------------------------------------------*/

void O1kUserList::print()
{
  O1kUserNode* unp;
  int i = 0;
  for (unp = head; unp != NULL; unp = unp->next, i++){
    printf("NODE[%d] [%s] [%s] [%s] [%s] [%s]\n",i,
           unp->userhost, unp->first, unp->last,
           unp->email, unp->phone);
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::O1kAddrBook()
 *
 *  Description:
 *     Created new address book. Set default sort type to SORT_USER
 *     and set default address book location to the current dir.
 *     
 *----------------------------------------------------------------------*/

O1kAddrBook::O1kAddrBook()
{
  char* v;
  
  users = new O1kUserList();

  v = (char *) getenv("O1K_CONF_DB");
  if (v != NULL){
    strncpy(db_name, v, O1K_DB_NAME_LEN);
  } else {
    strcpy(db_name, "./.o1k_conf_db");
  }
  
  cur_sort =  SORT_USER;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::~O1kAddrBook()
 *
 *  Description:
 *     Delete address book.
 *     
 *----------------------------------------------------------------------*/

O1kAddrBook::~O1kAddrBook()
{
  if (users)
    delete users;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::sort()
 *
 *  Description:
 *     Sort the address book.
 *
 *  Parameters:
 *     SortType s_type  : sort type
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::sort(SortType s_type)
{
  cur_sort = s_type;
  users->sort(s_type);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::setuser()
 *
 *  Description:
 *     Set the user name. This in turn sets the location for
 *     the address book.
 *
 *  Parameters:
 *     char* name  : user name
 *
 *----------------------------------------------------------------------*/

void O1kAddrBook::setuser(char* name)
{
  char* v = (char *) getenv("O1K_CONF_DB");

  if (v != NULL){
    strncpy(db_name, v, O1K_DB_NAME_LEN);
  } else {
    sprintf(db_name, "/home/%s/.o1k_conf_db",name);
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::load()
 *
 *  Description:
 *     This routine loads the address book data base from the
 *     previous set file location and adds the users in the
 *     database to the user list.
 *     
 *  Notes:
 *     Currently, whenever load() is called, the current
 *     user list is deleted and a new one is created.
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::load()
{
  O1kUserNode* unp;
  datum key;
  datum value;  

  char* s;
  char* e;

  /* close address book database if still open */
  if (db){
    dbm_close(db);
    db = NULL;
  }

  /* delete and create new user list */
  if (users){
    delete users;
  }
  users = new O1kUserList();
  if (!users){
    fprintf(stderr,"O1kAddrBook::load(): could not create user list\n");
    return;
  }

  /* open address book database */
  if ((db = dbm_open(db_name, O_RDWR | O_CREAT, 0660)) == NULL){
    fprintf(stderr,"could not create/open address book database %s\n", db_name);
    return;
  }

  /* get entries from address book database and add to user list */
  for (key = dbm_firstkey(db);  key.dptr != NULL;  key = dbm_nextkey(db)){
    value = dbm_fetch(db, key);

    /* create new user */
    if ((unp = new O1kUserNode()) == NULL){
      fprintf(stderr,"could not create user\n");
      return;
    }

    /* copy info to user */
    strncpy(unp->userhost, key.dptr, key.dsize);
    unp->userhost[key.dsize] = '\0';
    
    s = value.dptr;
    
    /* first name */
    e = (char*) strchr(s, '\n');
    strncpy(unp->first, s, e-s);
    unp->first[key.dsize] = '\0';
    s = e+1;

    /* last name */
    e = (char*) strchr(s, '\n');
    strncpy(unp->last, s, e-s);
    unp->last[key.dsize] = '\0';
    s = e+1;

    /* email name */
    e = (char*) strchr(s, '\n');
    strncpy(unp->email, s, e-s);
    unp->email[key.dsize] = '\0';
    s = e+1;

    /* phone name */
    e = (char*) strchr(s, '\n');
    strncpy(unp->phone, s, e-s);
    unp->phone[key.dsize] = '\0';

    /* add user to user list */
    users->addUser(unp);
  }

  /* sort user list */
  sort(cur_sort);

  /* close address book database */
  if (db){
    dbm_close(db);
    db = NULL;
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::storeuser()
 *
 *  Description:
 *     Store user in address book database. If exist, replace with
 *     new entry.
 *     
 *  Parameters:
 *     O1kUserNode* unp : user info to store
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::storeuser(O1kUserNode* unp)
{
  datum key;           /* key is user@host */
  datum value;
  char buffer[O1K_AB_MAX_STRING*NUM_STRINGS];

  key.dptr  = unp->userhost;
  key.dsize = strlen(key.dptr);

  strcpy(buffer,unp->first);
  strcat(buffer,"\n");  
  strcat(buffer,unp->last);  
  strcat(buffer,"\n");  
  strcat(buffer,unp->email);  
  strcat(buffer,"\n");  
  strcat(buffer,unp->phone); 
  strcat(buffer,"\n");
  
  value.dptr = buffer;
  value.dsize = strlen(buffer);
  
  if (dbm_store(db, key, value, DBM_INSERT) == 1)
    dbm_store(db, key, value, DBM_REPLACE);

}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::store()
 *
 *  Description:
 *     Store user list in address book database. 
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::store()
{
  O1kUserNode* unp;
  datum key;
  datum value;  
  char buf[O1K_AB_MAX_STRING];
  int done;

  /* close address book database if still open */  
  if (db){
    dbm_close(db);
    db = NULL;
  }

  /* open address book database */
  if ((db = dbm_open(db_name, O_RDWR | O_CREAT, 0660)) == NULL){
    fprintf(stderr,"could not create/open address book database %s\n", db_name);
    return;
  }

  /* store each user */
  for (unp = users->head; unp != NULL; unp = unp->next){
    storeuser(unp);
  }

  /* remove any not in list */
  do {
    done = 1;
    for (key = dbm_firstkey(db);  key.dptr != NULL;  key = dbm_nextkey(db)){
      value = dbm_fetch(db, key);
      strncpy(buf, key.dptr, key.dsize);
      buf[key.dsize] = '\0';
      unp = getUser(SORT_USER, buf);
      if (!unp){
        dbm_delete(db, key);
        done = 0;
      }
    }
  } while (!done);

  /* close address book database */
  if (db){
    dbm_close(db);
    db = NULL;
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::add_user()
 *
 *  Description:
 *     Create a new user with the info provided and add
 *     to user list.
 *     
 *  Parameters:
 *     char* f   :  First name
 *     char* l   :  Last Name
 *     char* e   :  Email Addr
 *     char* p   :  Phone Number
 *     char* u   :  user@host name for calling 
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::add_user(char* f, char* l, char* e, char* p, char* u)
{
  O1kUserNode* unp;
  
  if ((unp = new O1kUserNode()) == NULL){
    fprintf(stderr,"could not create user\n");
    return;
  }
  unp->set(f,l,e,p,u);
  users->addUser(unp);
  sort(cur_sort);
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::delete_user()
 *
 *  Description:
 *     Delete a user from the list given info of given type.
 *     
 *  Parameters:
 *     SortType s_type   :  type of user info
 *     char* s           :  user info
 *
 *  Notes:
 *     The really only uniq address book entry is the
 *     user@host entry. This is the target for a call.
 *     One should really only delete a user given this
 *     type of info. 
 *
 *----------------------------------------------------------------------*/

void O1kAddrBook::delete_user(SortType s_type, char* s)
{
  O1kUserNode* unp;
  
  unp = getUser(s_type, s);
  if (unp){
    users->removeUser(unp);
    sort(cur_sort);
  } else {
    fprintf(stderr,"delete_user(%d) %s not found\n",s_type, s);
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::getUser()
 *
 *  Description:
 *     Get a user from the list given info of given type.
 *     
 *  Parameters:
 *     SortType s_type   :  type of user info
 *     char* s           :  user info
 *
 *  Notes:
 *     The really only uniq address book entry is the
 *     user@host entry. This is the target for a call.
 *     One should really only get a user given this
 *     type of info. 
 *
 *----------------------------------------------------------------------*/

O1kUserNode* O1kAddrBook::getUser(SortType s_type, char* s)
{
  O1kUserNode* unp = NULL;
  char* s2;
  
  for (unp = users->head; unp != NULL; unp = unp->next){
    switch (s_type){
      case SORT_USER:  s2 = unp->userhost; break;
      case SORT_FIRST: s2 = unp->first;    break;
      case SORT_LAST:  s2 = unp->last;     break;
      case SORT_EMAIL: s2 = unp->email;    break;
    }
    if (strcmp(s, s2) == 0)
      break;
  }
  return unp;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::getFirstUser()
 *
 *  Description:
 *     Return first user in list
 *
 *----------------------------------------------------------------------*/

O1kUserNode* O1kAddrBook::getFirstUser()
{
  return users->head;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::getNextUser()
 *
 *  Description:
 *     Return user in list after given user
 *
 *----------------------------------------------------------------------*/

O1kUserNode* O1kAddrBook::getNextUser(O1kUserNode* unp)
{
  return unp->next;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::getUserInfo()
 *
 *  Description:
 *     Get user info from O1kUserNode
 *     
 *  Parameters:
 *     O1kUserNode* unp : user info node
 *     char* f   :  buffer to place First name
 *     char* l   :  buffer to place Last Name
 *     char* e   :  buffer to place Email Addr
 *     char* p   :  buffer to place Phone Number
 *     char* u   :  buffer to place user@host name
 *     
 *----------------------------------------------------------------------*/

void O1kAddrBook::getUserInfo(O1kUserNode* unp, char* f, char* l, char* e, char* p, char* u)
{
  strcpy(f, unp->first);
  strcpy(l, unp->last);
  strcpy(e, unp->email);
  strcpy(p, unp->phone);
  strcpy(u, unp->userhost);  
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kAddrBook::getUserInfo()
 *
 *  Description:
 *     Get user info from specified type
 *     
 *  Parameters:
 *     SortType s_type   :  type of user info
 *     char* s           :  user info      
 *     char* f   :  buffer to place First name
 *     char* l   :  buffer to place Last Name
 *     char* e   :  buffer to place Email Addr
 *     char* p   :  buffer to place Phone Number
 *     char* u   :  buffer to place user@host name
 *
 *  Notes:
 *     The really only uniq address book entry is the
 *     user@host entry. This is the target for a call.
 *     One should really only get a user given this
 *     type of info.
 * 
 *----------------------------------------------------------------------*/

void O1kAddrBook::getUserInfo(SortType s_type, char* s,  char* f, char* l, char* e, char* p, char* u)
{
  O1kUserNode* unp = NULL;

  unp = getUser(s_type, s);
  
  if (unp){
    strcpy(f, unp->first);
    strcpy(l, unp->last);
    strcpy(e, unp->email);
    strcpy(p, unp->phone);
    strcpy(u, unp->userhost);
  } else {
    fprintf(stderr,"getUserInfo(%d) %s not found\n",s_type, s);
    strcpy(f, "");
    strcpy(l, "");
    strcpy(e, "");
    strcpy(p, "");
    strcpy(u, "");
  }
  
}

