/***************************************************************************
 *                                                                         *
 *   TLCAPI.CPP                                                            *
 *                                                                         *
 *   Copyright (c) 1997  Galacticomm, Inc.    All Rights Reserved          *
 *                                                                         *
 *   Teleconference API routines                                           *
 *                                                                         *
 *                                           - Phil Henning 5/14/97        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "tlcapi.hpp"

#define FILREV "$Revision: 78 $"

extern "C" {

VOID (* tlcenter)(const CHAR*);
VOID (* tlcexit)(const CHAR*);

static VOID
dftenter(                          // default enter vector
const CHAR*);

static VOID
dftexit(                           // default exit vector
const CHAR*);

}; // extern "C"

static INT
smbCompareUserChan(
const VOID* target,
const VOID* test);

static INT
smbCompareUserNum(
const VOID* target,
const VOID* test);

SMBKEYTABLE smbUserKey[]={
     {offsetof(CTlcUser,m_pszName),smbCompareString},
     {0,smbCompareUserNum},
     {0,NULL}
};

SMBKEYTABLE smbChanKey[]={
     {offsetof(CTlcChannel,m_strName),smbCompareString},
     {0,NULL}
};

SMBKEYTABLE smbUserXrfKey[]={
     {offsetof(usrlist,userid),smbCompareString},
     {offsetof(usrlist,channel),smbCompareString},
     {0,smbCompareUserChan},
     {0,NULL}
};

SMBKEYTABLE smbActionKey[]={
     {offsetof(CTlcAList,m_name),smbCompareString},
     {0,NULL}
};

/* function definitions start here */

MARKSOURCE(tlcapi)

VOID
CTlcAPI::DeclareObjSize(           // state object storage requirements
size_t st_user,                    //   user object
size_t st_chan)                    //   channel object
{
     m_UserSize = max(st_user,m_UserSize);
     m_ChanSize = max(st_chan,m_ChanSize);
}

size_t
CTlcAPI::GetUserSize()             // get size of largest user object
{
     return(m_UserSize);
}

size_t
CTlcAPI::GetChanSize()             // get size of largest channel object
{
     return(m_ChanSize);
}

CTlcAPI::CTlcAPI()                 // default constructor
     : m_smbChans(NULL)
      ,m_smbUsers(NULL)
      ,m_smbXrf(NULL)
      ,m_smbAct(NULL)
      ,m_ChanSize(0)
      ,m_UserSize(0)
      ,iUserSmbSize(0)
      ,iChanSmbSize(0)
      ,_isinit(false)
      ,_shutdwn(false)
      ,m_iTlcSur(0)
      ,bufAddChan(NULL)
      ,bufAddUser(NULL)
      ,m_dfaTlcXrf(NULL)
      ,m_bSwitFor(FALSE)
      ,m_bForSameChg(FALSE)
      ,m_iSpeakThres(0)
      ,m_iSpeedDrop(0)
      ,m_iSwitchThres(0)


{
     m_pfxTrans     = new CObjMap;
     m_pfxCmds      = new CTlcCmdList;
     m_smbAct       = new CSmb(sizeof(CTlcAList),smbActionKey,7);

     m_keySys[0]=m_keyUnl[0]=m_keySeeUL[0]='\0';
     /* other cnf yes/no options   */
     m_bWhs2Pag     = FALSE;
     m_dfaTlcXrf    = dfaOpen("GALTELX.DAT",sizeof(TLCNUMXRF),NULL);
     tlcenter=dftenter;
     tlcexit=dftexit;
}

VOID
CTlcAPI::Init()                    // final initialiation
{
     _isinit=true;

     iUserSmbSize   = m_UserSize+((n2fgt+n2ign)*sizeof(FLIST));
     iChanSmbSize   = m_ChanSize+((n2inv)*sizeof(FLIST));

     bufAddChan     = new CHAR[iChanSmbSize];
     bufAddUser     = new CHAR[iUserSmbSize];

     m_smbChans     = new CSmb(iChanSmbSize,smbChanKey,7);
     m_smbUsers     = new CSmb(iUserSmbSize,smbUserKey,7);
     m_smbXrf       = new CSmb(sizeof(USRLIST),smbUserXrfKey,7);

     cmdJoin        = new CcmdJoin("JOIN","/J");
     cmdList        = new CcmdList("LIST","/L");
     cmdScan        = new CcmdScan("SCAN","/S");
     cmdDirect      = new CcmdDirect(intdirchr,intdirchr);
     cmdHelp        = new CcmdHelp("HELP","?");
     cmdUnlist      = new CcmdUnlist("UNLIST","/UL");
     cmdWhisper     = new CcmdWhisper("WHISPER TO","/");
     cmdEdit        = new CcmdEdit("EDIT","/E");
     cmdTopic       = new CcmdTopic("TOPIC","/T");
     cmdInvite      = new CcmdInvite("INVITE","/I");
     cmdUnInvite    = new CcmdUnInvite("UNINVITE","/U");
     cmdForget      = new CcmdForget("FORGET","/F");
     cmdRemember    = new CcmdRemember("REMEMBER","/REM");
     cmdSquelch     = new CcmdSquelch("SQUELCH","/S");
     cmdUnSquelch   = new CcmdUnSquelch("UNSQUELCH","/US");
     cmdChat        = new CcmdChat("CHAT","/C");
     cmdChannel     = new CcmdChannel("CHANNEL","/CL");
     cmdAction      = new CcmdAction("ACTION","/A");
     cmdMenu        = new CcmdMenu("MENU","/ME");
     cmdPost        = new CcmdPost("POST","/PT");
     cmdClose       = new CcmdClose("CLOSE","");
     cmdOpen        = new CcmdOpen("OPEN","");
     cmdEcho        = new CcmdEcho("ECHO","/H");
     cmdZappo       = new CcmdZappo("ZAPPO","/Z");
     cmdIgnore      = new CcmdIgnore("IGNORE","/G");
     cmdNotice      = new CcmdNotice("NOTICE","/N");
     cmdPage        = new CcmdPage("PAGE","/P");
     cmdUsers       = new CcmdUsers("USERS","#");
     cmdMain        = new CcmdMain("MAIN","/M");

     cmdRegister(cmdJoin);
     cmdRegister(cmdList);
     cmdRegister(cmdScan);
     cmdRegister(cmdDirect);
     cmdRegister(cmdHelp);
     cmdRegister(cmdUnlist);
     cmdRegister(cmdWhisper);
     cmdRegister(cmdEdit);
     cmdRegister(cmdTopic);
     cmdRegister(cmdInvite);
     cmdRegister(cmdUnInvite);
     cmdRegister(cmdForget);
     cmdRegister(cmdRemember);
     cmdRegister(cmdSquelch);
     cmdRegister(cmdUnSquelch);
     cmdRegister(cmdChat);
     cmdRegister(cmdChannel);
     cmdRegister(cmdAction);
     cmdRegister(cmdMenu);
     cmdRegister(cmdPost);
     cmdRegister(cmdClose);
     cmdRegister(cmdOpen);
     cmdRegister(cmdEcho);
     cmdRegister(cmdZappo);
     cmdRegister(cmdIgnore);
     cmdRegister(cmdNotice);
     cmdRegister(cmdPage);
     cmdRegister(cmdUsers);
     cmdRegister(cmdMain);

#ifdef DEBUG
     cmdDiag        = new CcmdDiag("DIAG","/D");
     cmdRegister(cmdDiag);
#endif // DEBUG
}

CTlcAPI::~CTlcAPI()                // default destructor
{
     CloseDfa();
     Shutdown();
     if (m_smbChans != NULL) {
          delete m_smbChans;
          m_smbChans=NULL;
     }
     if (m_smbUsers != NULL) {
          delete m_smbUsers;
          m_smbUsers=NULL;
     }
     if (m_smbXrf != NULL) {
          delete m_smbXrf;
          m_smbXrf=NULL;
     }
     if (m_smbAct != NULL) {
          delete m_smbAct;
          m_smbAct=NULL;
     }
     if (bufAddChan != NULL) {
          delete [] bufAddChan;
          bufAddChan=NULL;
     }
     if (bufAddUser != NULL) {
          delete [] bufAddUser;
          bufAddUser=NULL;
     }
     if (m_pfxCmds != NULL) {
          delete m_pfxCmds;
          m_pfxCmds=NULL;
     }
     if (m_pfxTrans != NULL) {
          delete m_pfxTrans;
          m_pfxTrans=NULL;
     }
     // Transport objects should not have
     // to be deleted manually
}

VOID
CTlcAPI::CloseDfa()
{
     if (m_dfaTlcXrf != NULL) {
          dfaClose(m_dfaTlcXrf);
          m_dfaTlcXrf=NULL;
     }
}


VOID
CTlcAPI::Shutdown(VOID)            // API Shutdown routine
{
     ASSERT(m_pfxCmds != NULL);
     _shutdwn=true;
     if (!_isinit) {
          return;
     }

     // delete the channel objects and remove them from the list
     // of channels
     if (m_smbChans != NULL) {
          VOID* ptr;
          while ((ptr=m_smbChans->GetLow(0)) != NULL) {
               chanDelete(static_cast<CTlcChannel *>(ptr));
          }
     }

     // delete commands
     for (CTlcCommand* pCmd=cmdGetFirst() ; pCmd != NULL ; pCmd=cmdGetNext()) {
          delete pCmd;
     }
}

ULONG
CTlcAPI::FindIDByUser(
const CHAR* userid)
{
     TLCNUMXRF sptr;
     ULONG number=0;

     dfaSetBlk(m_dfaTlcXrf);
     if (dfaAcqEQ(&sptr,userid,1)) {
          number=sptr.number;
     }
     dfaRstBlk();
     return(number);
}

ULONG
CTlcAPI::CreateUniqueID(
const CHAR* userid)
{
     TLCNUMXRF sptr;
     ULONG number=1;

     dfaSetBlk(m_dfaTlcXrf);
     if (dfaAcqHI(&sptr,0)) {
          number=sptr.number+1;
     }
     stzcpy(sptr.userid,userid,UIDSIZ);
     sptr.number=number;
     dfaInsert(&sptr);
     dfaRstBlk();
     return(number);
}

CHAR*
CTlcAPI::FindUserByID(
ULONG num,
CHAR** buf)
{
     static TLCNUMXRF sptr;

     dfaSetBlk(m_dfaTlcXrf);
     if (dfaAcqEQ(&sptr,&num,0)) {
          if (buf != NULL && *buf != NULL) {
               stzcpy(*buf,sptr.userid,UIDSIZ);
          }
          return(sptr.userid);
     }
     return(NULL);
}

CTlcChannel*
CTlcAPI::chanRegister(             // Register a new tele channel
CTlcChannel* pChannel)             // ptr to channel
{

     SHORT cnt;
     CTlcChannel* newptr;
     LPFLIST fptr;
     CHAR channame[CHAN_MAX_SIZE];

     ASSERT(pChannel != NULL);
     ASSERT(m_smbChans != NULL);

     memset(bufAddChan,0,iChanSmbSize);
     memmove(bufAddChan,pChannel,pChannel->m_iSize);

     if (chanGetByName(pChannel->GetName()) != NULL) {
          return(NULL);
     }
     stlcpy(channame,pChannel->GetName(),CHAN_MAX_SIZE);
     stpans(unpad(channame));
     if (strlen(channame) == 0) {
          shocst("TELECONFERENCE ERROR","Can't Create aero-length channel name");
          ASSERTM(FALSE,"Tried to create zero length channel name");
          return(NULL);
     }
     newptr=static_cast<CTlcChannel*>(m_smbChans->Insert(bufAddChan));
     fptr=reinterpret_cast<LPFLIST>((reinterpret_cast<CHAR*>(newptr))+m_ChanSize);
     memset(fptr,0,(n2inv)*(sizeof(FLIST)));
     cnt=0;
     while (cnt < n2inv) {
          fptr->number=cnt++;
          fptr++;
     }
     csnotaddchan(pChannel);
     return(newptr);
}

GBOOL
CTlcAPI::chanDelete(
CTlcChannel* pChan,
GBOOL bDisk)
{
     return(chanRemoveByName(pChan->GetName(),bDisk));
}

CTlcUser*
CTlcAPI::chanUserLow(
const CHAR* pszChan)
{
     LPUSRLIST pUl;
     USRLIST Ul;

     memset(&Ul,0,sizeof(USRLIST));
     stlcpy(Ul.channel,pszChan,CHAN_MAX_SIZE);
     if ((pUl=static_cast<LPUSRLIST>(m_smbXrf->GetGreater(&Ul,2))) != NULL) {
          return(chanFindUser(pszChan,pUl->userid));
     }
     return(NULL);
}

CTlcUser*
CTlcAPI::chanUserNext(
const CHAR* pszChan)
{
     LPUSRLIST pUl;

     if ((pUl=static_cast<LPUSRLIST>(m_smbXrf->GetNext())) != NULL) {
          if (sameas(pUl->channel,pszChan)) {
               return(chanFindUser(pszChan,pUl->userid));
          }
     }
     return(NULL);
}

CTlcUser*
CTlcAPI::chanUserGT(
const CHAR* pszChan,
const CHAR* pszUserid)
{
     CTlcUser* pUser;

     pUser=chanUserLow(pszChan);
     while (pUser != NULL) {
          if (stricmp(pUser->GetName(),pszUserid) > 0) {
               return(pUser);
          }
          pUser=chanUserNext(pszChan);
     }
     return(NULL);
}

VOID*
CTlcAPI::chanUserGetPos()          // get pos of users-in-channel list
{
     return(static_cast<VOID*>(m_smbXrf->CurrentNumber()));
}

VOID
CTlcAPI::chanUserSetPos(           // set pos of users-in-channel list
VOID *pos)                         //   position to set to
{
     m_smbXrf->GetBynum(static_cast<SMBPTR>(pos),2);
}

CTlcUser*
CTlcAPI::chanFindUser(
const CHAR* pszChan,
const CHAR* pszUserid)
{
     USRLIST ul;
     CTlcUser* pUser;

     ASSERT(pszChan != NULL);
     ASSERT(pszUserid != NULL);
     if (pszChan == NULL || pszUserid == NULL) {
          return(NULL);
     }
     stzcpy(ul.channel,pszChan,CHAN_MAX_SIZE);
     stzcpy(ul.userid,pszUserid,TLCUIDSIZ);
     ASSERT(m_smbUsers != NULL);
     pUser=static_cast<CTlcUser*>(m_smbUsers->GetEqual(pszUserid,0));
     if (pUser == NULL) {
          return (NULL);
     }
     while (!sameas(pUser->GetChannelName(),pszChan)) {
         pUser=static_cast<CTlcUser*>(m_smbUsers->GetNext());
         if (pUser == NULL || !sameas(pUser->GetName(),pszUserid)) {
              return(NULL);
         }
     }
     return(pUser);
}

CTlcUser*
CTlcAPI::chanAddUser(
const CHAR* pszChan,
const CHAR* pszUserid)
{
     USRLIST ul;
     CTlcUser* pUser,*pNewUser;
     VOID* startnew,*startold;

     pUser=static_cast<CTlcUser*>(m_smbUsers->GetEqual(pszUserid,0));
     pNewUser=static_cast<CTlcUser*>(m_smbUsers->Insert(pUser));
     pNewUser->SetChannel(pszChan);
     stzcpy(ul.channel,pszChan,CHAN_MAX_SIZE);
     stzcpy(ul.userid,pszUserid,TLCUIDSIZ);
     startnew=(reinterpret_cast<CHAR*>(pNewUser))+m_UserSize;
     startold=(reinterpret_cast<CHAR*>(pUser))+m_UserSize;
     memmove(startnew,startold,(n2fgt+n2ign)*sizeof(FLIST));
     m_smbXrf->Insert(&ul);
     pNewUser->Reinit();
     pUser->Cleanup();
     return(pNewUser);
}

VOID
CTlcAPI::chanRemoveUser(
const CHAR* pszChan,
const CHAR* pszUserid)
{
     CTlcUser* pUser;
     USRLIST ul;

     ASSERT(m_smbXrf != NULL);
     ASSERT(pszChan != NULL);
     ASSERT(pszUserid != NULL);

     if ((pUser=chanFindUser(pszChan,pszUserid)) != NULL) {
          stzcpy(ul.channel,pszChan,CHAN_MAX_SIZE);
          stzcpy(ul.userid,pszUserid,TLCUIDSIZ);
          if (m_smbXrf->GetEqual(&ul,2)) {
               m_smbXrf->Delete();
          }
          usrDelete(pUser);
     }
}

CTlcChannel*                       // ptr to channel
CTlcAPI::chanGetByName(            // get tele channel info
const CHAR* pszName)               // channel name
{
     ASSERT(m_smbChans != NULL);
     ASSERT(pszName != NULL);

     VOID* ptr=m_smbChans->GetEqual(pszName,0);

     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     return(NULL);
}

CTlcChannel*
CTlcAPI::chanGetGTE(
const CHAR* pszName)
{
     ASSERT(m_smbChans != NULL);
     ASSERT(pszName != NULL);

     VOID* ptr=m_smbChans->GetEqual(pszName,0);
     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     ptr=m_smbChans->GetGreater(pszName,0);
     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     return(NULL);
}

GBOOL                              //   returns true if able to remove
CTlcAPI::chanRemoveByName(         // Unregister a channel with the API
const CHAR* pszName,               //   Channel name to remove
GBOOL bDisk)                       //   from disk too?
{
     CTlcChannel* pChannel;
     CTlcUser* pUser;

     ASSERT(m_smbChans != NULL);
     ASSERT(pszName != NULL);

     if (sameas(pszName,mainchan) && !_shutdwn) {
          return(FALSE);
     }
     if ((pChannel=static_cast<CTlcChannel*>(m_smbChans->GetEqual(pszName,0))) != NULL) {
          pUser=chanUserLow(pszName);
          while (pUser != NULL) {
               chanRemoveUser(pszName,pUser->GetName());
               pUser=chanUserLow(pszName);
          }
          csnotdelchan(pChannel);
          pChannel->~CTlcChannel();
          m_smbChans->Delete();
     }
     if (bDisk) {
          dfaSetBlk(dfaChan);
          if (dfaAcqEQ(NULL,pszName,0)) {
               dfaDelete();
          }
          dfaRstBlk();
     }
     return(TRUE);
}

CTlcChannel*
CTlcAPI::chanCheckPub(
const CHAR* strName,
GBOOL bExact)
{
     CTlcChannel* pChannel;

     if (strName == NULL) {
          return(NULL);
     }
     pChannel=chanGetGTE(strName);
     if (pChannel != NULL && sameto(strName,pChannel->GetName())) {
          if (!bExact) {
               return(pChannel);
          }
          else if (sameas(strName,pChannel->GetName())) {
               return(pChannel);
          }
     }
     return(NULL);
}

SHORT
CTlcAPI::chanCheckPrivate(
const CHAR* strName,
CTlcChannel** pSaved)
{
     CTlcChannel* pChannel;
     INT ct;

     pChannel=chanGetFirst();
     ct=0;
     while (pChannel != NULL) {
          ASSERT(pChannel != NULL);
          if (pChannel->GetType()&CHAN_TYPE_PRIVATE) {
               if (sameto(strName,pChannel->GetName())) {
                    *pSaved=pChannel;
                    ct++;
                    break;
               }
          }
          pChannel=chanGetNext();
     }
     if (ct == 1) {
          return(CHAN_PRIVATE_FOUND);
     }
     else if (ct == 0) {
          return(CHAN_PRIVATE_NOTFND);
     }
     return(CHAN_PRIVATE_AMBIG);
}

CTlcChannel*
CTlcAPI::chanGetFirst(VOID)
{
     ASSERT(m_smbChans != NULL);

     VOID* ptr=m_smbChans->GetLow(0);
     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     return(NULL);
}

CTlcChannel*
CTlcAPI::chanGetNext(VOID)
{
     ASSERT(m_smbChans != NULL);

     VOID* ptr=m_smbChans->GetNext();
     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     return(NULL);
}

CTlcChannel*
CTlcAPI::chanGetGT(
const CHAR* name)
{
     ASSERT(m_smbChans != NULL);

     VOID* ptr=m_smbChans->GetGreater(name,0);
     if (ptr != NULL) {
          return(static_cast<CTlcChannel*>(ptr));
     }
     return(NULL);
}

VOID*
CTlcAPI::chanGetPos()              // get pos of channel list
{
     return(static_cast<VOID*>(m_smbChans->CurrentNumber()));
}

VOID
CTlcAPI::chanSetPos(               // set pos of channel list
VOID *pos)                         //   position to set to
{
     m_smbChans->GetBynum(static_cast<SMBPTR>(pos),0);
}

VOID
CTlcAPI::chanMoveAllUsers(
const CHAR* pszChanFrom,
const CHAR* pszChanTo,
SHORT reason,
SHORT iMsgNum)
{
     CTlcChannel* pChannelFrom;
     CTlcChannel* pChannelTo;
     CTlcUser* pUser;
     const CHAR* ptr1;

     pChannelFrom=chanGetByName(pszChanFrom);
     pChannelTo=chanGetByName(pszChanTo);
     if (pChannelTo == NULL || sameas(pszChanFrom,pszChanTo)) {
          pChannelTo=chanGetByName(mainchan);
     }
     pUser=chanUserLow(pszChanFrom);
     if (reason == CHAN_SWITCH_USER_LOGOFF) {
          ptr1=(dynamic_cast<CTlcPrivateChannel *>(pChannelFrom))->GetOwner();
     }
     else {
          ptr1=pszChanFrom;
     }
     while (pUser != NULL) {
          CTlcUser* pNewUser;
          pUser->OnRecvText(SYSUID,NULL,NULL,RECV_TYPE_SWITCHCHAN,iMsgNum,ptr1);
          pChannelTo->AddUser(pUser);
          pNewUser=chanFindUser(pChannelTo->GetName(),pUser->GetName());
          pNewUser->SetChannel(pChannelTo->GetName());
          pChannelFrom->RemoveUser(pUser);
          pNewUser->CmdResult(CMD_JOIN,TRUE,0);
          pUser=chanUserLow(pszChanFrom);
     }
}


GBOOL
CTlcAPI::cmdRegister(
CTlcCommand* pCmd)
{
     ASSERT(m_pfxCmds != NULL);
     if (m_pfxCmds->Register(pCmd) >= MAP_OK) {
          return(TRUE);
     }
     return(FALSE);
}

CTlcCommand*
CTlcAPI::cmdGetFirst(VOID)         // get first command in list.
{
     if (m_pfxCmds == NULL) {
          return(NULL);
     }
     return(static_cast<CTlcCommand*>(m_pfxCmds->GetFirst()));
}

CTlcCommand*
CTlcAPI::cmdGetNext(VOID)        // get next command in list
{
     if (m_pfxCmds == NULL) {
          return(NULL);
     }
     return(static_cast<CTlcCommand*>(m_pfxCmds->GetNext()));
}

CTlcUser*
CTlcAPI::usrGetFirst(VOID)         // get first user in list.
{
     ASSERT(m_smbUsers != NULL);
     return(static_cast<CTlcUser*>(m_smbUsers->GetLow(0)));
}

CTlcUser*
CTlcAPI::usrGetNext(VOID)       // get next user in list
{
     ASSERT(m_smbUsers != NULL);
     return(static_cast<CTlcUser*>(m_smbUsers->GetNext()));
}

CTlcUser*
CTlcAPI::usrGetGT(
const CHAR* pszUser)
{
     ASSERT(m_smbUsers != NULL);
     return(static_cast<CTlcUser*>(m_smbUsers->GetGreater(pszUser,0)));
}

CTlcUser*
CTlcAPI::usrGetByTlcID(
USHORT board,
ULONG unum)
{
     CTlcUser User;

     ASSERT(m_smbUsers != NULL);
     User.SetTlcBoardNum(board);
     User.SetTlcUnum(unum);
     return(static_cast<CTlcUser*>(m_smbUsers->GetEqual(&User,1)));
}


CTlcUser*
CTlcAPI::usrAdd(CTlcUser* pUser)
{
     VOID* newptr;
     LPFLIST fptr;

     ASSERT(m_smbUsers != NULL);
     ASSERT(pUser != NULL);

     memset(bufAddUser,0,iUserSmbSize);
     memmove(bufAddUser,pUser,pUser->m_iSize);
     newptr=m_smbUsers->Insert(bufAddUser);
     ASSERT(newptr != NULL);
     if (*(pUser->GetChannelName()) != '\0') {
          USRLIST ul;
          stzcpy(ul.channel,pUser->GetChannelName(),CHAN_MAX_SIZE);
          stzcpy(ul.userid,pUser->GetName(),TLCUIDSIZ);
          m_smbXrf->Insert(&ul);
     }
     else {
          SHORT cnt;
          fptr=reinterpret_cast<LPFLIST>((reinterpret_cast<CHAR*>(newptr))+m_UserSize);
          memset(fptr,0,(n2fgt+n2ign)*(sizeof(FLIST)));
          cnt=0;
          while (cnt < n2fgt) {
               fptr->number=cnt++;
               fptr++;
          }
          cnt=0;
          while (cnt < n2ign) {
               fptr->number=cnt++;
               fptr++;
          }
     }
     return(static_cast<CTlcUser*>(newptr));
}

VOID
CTlcAPI::usrDelete(CTlcUser* pUser)
{
     CTlcUser* pUsr;
     VOID* ptr;
     CHAR name[TLCUIDSIZ];
     CHAR channel[CHAN_MAX_SIZE];

     ASSERT(m_smbUsers != NULL);
     ASSERT(pUser != NULL);

     stzcpy(name,pUser->GetName(),TLCUIDSIZ);
     stzcpy(channel,pUser->GetChannelName(),TLCUIDSIZ);

     ptr=m_smbUsers->GetEqual(name,0);
     while (ptr != NULL) {
          pUsr=static_cast<CTlcUser*>(ptr);
          if (!sameas(pUsr->GetName(),name)) {
               break;
          }
          if (sameas(pUsr->GetChannelName(),channel)) {
               pUsr->~CTlcUser();
               m_smbUsers->Delete();
               ptr=m_smbUsers->GetEqual(name,0);
          }
          else {
               ptr=m_smbUsers->GetNext();
          }
     }
}

CTlcUser*
CTlcAPI::usrGetByName(const CHAR* pszUser)
{
     CTlcUser* pUser;
     VOID* ptr;
     ASSERT(m_smbUsers != NULL);
     ASSERT(pszUser != NULL);
     if (pszUser == NULL) {
          return(NULL);
     }
     ptr=m_smbUsers->GetEqual(pszUser,0);
     if (ptr == NULL) {
          return(NULL);
     }
     pUser=static_cast<CTlcUser*>(ptr);
     return(pUser);
}

VOID*
CTlcAPI::usrGetPos()               // get pos of users list
{
     return(static_cast<VOID*>(m_smbUsers->CurrentNumber()));
}

VOID
CTlcAPI::usrSetPos(                // set pos of users list
VOID *pos)                         //   position to set to
{
     m_smbUsers->GetBynum(static_cast<SMBPTR>(pos),0);
}

SHORT
CTlcAPI::usrFind(
const CHAR* pszUser,
CTlcUser** ppUser)
{
     SHORT cnt=0;
     VOID* ptr;
     SMBPTR savedptr;

     ASSERT(m_smbUsers != NULL);
     if (pszUser == NULL || *pszUser == '\0') {
          return(0);
     }
     if ((ptr=m_smbUsers->GetEqual(pszUser,0)) != NULL) {
          *ppUser=static_cast<CTlcUser*>(ptr);
          return(1);
     }
     if ((ptr=m_smbUsers->GetGreater(pszUser,0)) != NULL) {
          do {
               *ppUser=static_cast<CTlcUser*>(ptr);
               if (!sameto(pszUser,(*ppUser)->GetName())) {
                    break;
               }
               cnt++;
               if (sameas(pszUser,(*ppUser)->GetName())) {
                    return(cnt);
               }
               savedptr=m_smbUsers->CurrentNumber();
          } while ((ptr=m_smbUsers->GetNext()) != NULL);
     }
     if (cnt != 0) {
          ptr=m_smbUsers->GetBynum(savedptr,0);
          *ppUser=static_cast<CTlcUser*>(ptr);
     }
     return(cnt);
}

const CHAR*
CTlcAPI::keyGetSys(VOID)
{
     return(m_keySys);
}

VOID
CTlcAPI::keySetSys(
CHAR* key)
{
     stzcpy(m_keySys,key,KEYSIZ);
     free(key);
}

const CHAR*
CTlcAPI::keyGetUnl(VOID)
{
     return(m_keyUnl);
}

VOID
CTlcAPI::keySetUnl(
CHAR* key)
{
     stzcpy(m_keyUnl,key,KEYSIZ);
     free(key);
}


const CHAR*
CTlcAPI::keyGetSeeUL(VOID)
{
     return(m_keySeeUL);
}

VOID
CTlcAPI::keySetSeeUL(
CHAR* key)
{
     stzcpy(m_keySeeUL,key,KEYSIZ);
     free(key);
}

INT
CTlcAPI::transRegister(            // register a transport method
const CHAR* pszName,               // "name" of this interface
CTlcTransport* pTransport)         // ptr to the Transport class
{
     ASSERT(pTransport != NULL);
     ASSERT(m_pfxTrans != NULL);
     ASSERT(pszName != NULL);

     pTransport->SetName(pszName);
     return(m_pfxTrans->AddTail(pszName,pTransport));
}

CTlcTransport*
CTlcAPI::transGetFirst(VOID)       // get first transport method in list.
{
     ASSERT(m_pfxTrans != NULL);
     return(static_cast<CTlcTransport*>(m_pfxTrans->GetHead()));
}

CTlcTransport*
CTlcAPI::transGetNext(VOID)        // get next transport in list
{
     ASSERT(m_pfxTrans != NULL);
     return(static_cast<CTlcTransport*>(m_pfxTrans->GetNext()));
}

CTlcTransport*
CTlcAPI::transGetAt(
INT idx)
{
     ASSERT(m_pfxTrans != NULL);
     return(static_cast<CTlcTransport*>(m_pfxTrans->Find(idx)));
}

CTlcTransport*
CTlcAPI::transGetNamed(
const CHAR* name)
{
     ASSERT(m_pfxTrans != NULL);
     return(static_cast<CTlcTransport*>(m_pfxTrans->Find(name)));
}

INT
CTlcAPI::transGetNumber()
{
     ASSERT(m_pfxTrans != NULL);
     return(m_pfxTrans->GetCount());
}

VOID
CTlcAPI::OutputFinished()
{
     CTlcTransport* pTrans=transGetFirst();

     while (pTrans != NULL) {
          pTrans->OutputFinished();
          pTrans=transGetNext();
     }
}

GBOOL
CTlcAPI::Parse(
CHAR* pInput,
const CHAR* pszUseridFrom,
CTlcChannel* pChannel)
{
     static INT type=SPACE_DELIM;

     CTlcCommand* pCmd;
     CCommandParam pParam;
     CHAR* pszCmdParam=NULL;
     GBOOL isCommand=FALSE;
     GBOOL isAbbr;

     ASSERT(pChannel != NULL);
     stzcpy(input,pInput,tinpsz);
     parsin();
     pCmd=cmdGetFirst();
     while (pCmd != NULL) {
          ASSERT(pCmd != NULL);
          if (type == SPACE_DELIM && pCmd->m_bNeedSpace) {
               if (sameas(pCmd->m_pszCmd,margv[0])) {
                    if ((pszCmdParam=strchr(pInput,' ')) != 0) {
                         pszCmdParam++;
                    }
                    else {
                         pszCmdParam=pInput+strlen(pCmd->m_pszCmd);
                    }
                    isAbbr=FALSE;
                    isCommand=TRUE;
               }
               else if (pCmd->m_pszAbbr[0] != '\0'
                && sameas(pCmd->m_pszAbbr,margv[0])) {
                    if ((pszCmdParam=strchr(pInput, ' ')) != 0) {
                         pszCmdParam++;
                    }
                    else {
                         pszCmdParam=pInput+strlen(pCmd->m_pszAbbr);
                    }
                    isAbbr=TRUE;
                    isCommand=TRUE;
               }
          }
          if (type == NO_DELIM && !pCmd->m_bNeedSpace) {
               if (sameas(pCmd->m_pszCmd,margv[0])
                || sameto(pCmd->m_pszCmd,pInput))       {
                    pszCmdParam=pInput+(strlen(pCmd->m_pszCmd));
                    isAbbr=FALSE;
                    isCommand=TRUE;
               }
               else if (pCmd->m_pszAbbr[0] != '\0'
                && sameto(pCmd->m_pszAbbr,pInput)) {
                    pszCmdParam=pInput+(strlen(pCmd->m_pszAbbr));
                    isAbbr=TRUE;
                    isCommand=TRUE;
               }
          }
          if (isCommand) {
               CTlcUser* pUser;
               pUser=pChannel->GetUserByName(pszUseridFrom);
               pParam.m_pUser=pUser;
               pParam.m_pszCmdParam=pszCmdParam;
               type=SPACE_DELIM;
               SHORT res=pCmd->DoCmd(&pParam);
               if (res == 1) {
                    return(TRUE);
               }
               else if (res == 2 && isAbbr) {
                    stzcpy(input,pInput,tinpsz);
                    parsin();
                    isCommand=FALSE;
               }
               else {
                    return(FALSE);
               }
          }
          pCmd=cmdGetNext();
     }
     if (type == SPACE_DELIM) {
          type=NO_DELIM;
          return(Parse(pInput,pszUseridFrom,pChannel));
     }
     else {
          type=SPACE_DELIM;
          return(FALSE);      // no command found.
     }
}

VOID
CTlcAPI::actCreate(
LPACTLST liststr)
{
     CTlcAList List;
     ASSERT(m_smbAct != NULL);

     ASSERT(liststr != NULL);
     List.SetName(liststr->name);
     List.SetConfig(liststr);
     m_smbAct->Insert(&List);
}

CTlcAList*
CTlcAPI::actGetByName(
const CHAR* pszList)
{
     ASSERT(m_smbAct != NULL);
     return(static_cast<CTlcAList*>(m_smbAct->GetEqual(pszList,0)));
}

CTlcAList*
CTlcAPI::actGetFirst(
GBOOL getdel)
{
     CTlcAList* pList;

     ASSERT(m_smbAct != NULL);
     pList=static_cast<CTlcAList*>(m_smbAct->GetLow(0));
     if (pList != NULL) {
          while (pList->IsDeleted() && !getdel) {
               pList=static_cast<CTlcAList*>(m_smbAct->GetNext());
               if (pList == NULL) {
                    break;
               }
          }
     }
     return(pList);
}

CTlcAList*
CTlcAPI::actGetNext(
GBOOL getdel)
{
     CTlcAList* pList;

     ASSERT(m_smbAct != NULL);
     pList=static_cast<CTlcAList*>(m_smbAct->GetNext());
     if (pList != NULL) {
          while (pList->IsDeleted() && !getdel) {
               pList=static_cast<CTlcAList*>(m_smbAct->GetNext());
               if (pList == NULL) {
                    break;
               }
          }
     }
     return(pList);
}

CTlcAList*
CTlcAPI::actGetGT(
const CHAR* pszList)
{
     CTlcAList* pList;

     ASSERT(m_smbAct != NULL);
     pList=static_cast<CTlcAList*>(m_smbAct->GetGreater(pszList,0));
     if (pList != NULL) {
          while (pList->IsDeleted()) {
               pList=static_cast<CTlcAList*>(m_smbAct->GetNext());
               if (pList == NULL) {
                    break;
               }
          }
     }
     return(pList);
}

VOID*
CTlcAPI::actGetPos()               // get pos of actions list
{
     return(static_cast<VOID*>(m_smbAct->CurrentNumber()));
}

VOID
CTlcAPI::actSetPos(                // set pos of actions list
VOID *pos)                         //   position to set to
{
     m_smbAct->GetBynum(static_cast<SMBPTR>(pos),0);
}

VOID
CTlcAPI::actRemove(
const CHAR* pszList)
{
     CTlcAList* pList;

     ASSERT(m_smbAct != NULL);
     pList=static_cast<CTlcAList*>(m_smbAct->GetEqual(pszList,0));
     if (pList != NULL) {
          pList->~CTlcAList();
          m_smbAct->Delete();
     }
}

INT
CTlcAPI::HowManyMatch(
const CHAR* stg,
CTlcUser** ppMatchUser,
GBOOL samas)
{
     CTlcUser* pUser;
     INT cnt;

     pUser=usrGetFirst();
     cnt=0;
     while (pUser != NULL) {
          if (!(pUser->m_fFlags&USR_INVISB)
            && sameto(stg,pUser->GetName())
            && pUser->m_fFlags&USR_INCHAN) {
               cnt++;
               *ppMatchUser=pUser;
               if (samas && sameas(stg,pUser->GetName())) {
                    return(1);
               }
           }
           pUser=usrGetNext();
     }
     return(cnt);
}

CHAR*                         // ptr to msg
CTlcAPI::FindUserMsg(
const CHAR* pszStart,         // start of UID to check
INT* count,                   // ptr to an INT to hold number of matches
CTlcUser** pUser)             // ptr to ptr to hold user info
{
     CHAR* argend;
     GBOOL done=FALSE;
     INT lastcnt=0;
     INT mnum=0;
     CTlcUser* ptmpUser;

     for (int loop=0 ; loop < margc ; loop++) {
          margn[loop][0]='\0';
     }
     while (!done && (mnum < margc)) {
          argend=margn[mnum];
          if (*(argend-1) == ':') {
               *(argend-1)='\0';
               *count=HowManyMatch(pszStart,pUser,TRUE);
               *(argend-1)=':';
               mnum++;
               done=TRUE;
          }
          else {
               *count=HowManyMatch(pszStart,pUser,FALSE);
               margn[mnum][0]=' ';
          }
          if (*count == 0) {
               done=TRUE;
          }
          else {
               if (*count == 1) {
                    ptmpUser=*pUser;
               }
               lastcnt=*count;
          }
          if (!done) {
               mnum++;
          }
     }
     for (int loop=0 ; loop < margc ; loop++) {
          margn[loop][0]='\0';
     }
     rstrin();
     if ((*count != 1) && (lastcnt > 0)) {
          *count=lastcnt;
     }
     if (*count == 1) {
          *pUser=ptmpUser;
     }
     if (mnum == margc) {
          return("");
     }
     else {
          return(margv[mnum]);
     }
}

/* miscellaneous utility functions */

extern "C" {

static VOID
dftenter(
const CHAR* )
{
}

static VOID
dftexit(
const CHAR* )
{
}

}; // extern "C"

static INT
smbCompareUserChan(
const VOID* target,
const VOID* test)
{
     LPUSRLIST stored=static_cast<LPUSRLIST>(const_cast<VOID*>(test));
     LPUSRLIST lookup=static_cast<LPUSRLIST>(const_cast<VOID*>(target));

     INT ans1=stricmp(stored->channel,lookup->channel);
     INT ans2=stricmp(stored->userid,lookup->userid);

     if (ans1 == 0) {
          if (ans2 == 0) {
               return(EQUAL);
          }
          else if (ans2 < 0) {
               return(LEFTHIGH);
          }
          else {
               return(RIGHTHIGH);
          }
     }
     else if (ans1 < 0) {
          return(LEFTHIGH);
     }
     else {
          return(RIGHTHIGH);
     }
}

static INT
smbCompareUserNum(
const VOID* target,
const VOID* test)
{
     CTlcUser* stored=static_cast<CTlcUser*>(const_cast<VOID*>(test));
     CTlcUser* lookup=static_cast<CTlcUser*>(const_cast<VOID*>(target));

     ULONG sUnum=stored->GetTlcUnum();
     ULONG lUnum=lookup->GetTlcUnum();

     USHORT sBnum=stored->GetTlcBoardNum();
     USHORT lBnum=lookup->GetTlcBoardNum();

     if (sBnum == lBnum) {
          if (sUnum == lUnum) {
               return(EQUAL);
          }
          else if (sUnum > lUnum) {
               return(RIGHTHIGH);
          }
          else {
               return(LEFTHIGH);
          }
     }
     else if (sBnum > lBnum) {
          return(RIGHTHIGH);
     }
     else {
          return(LEFTHIGH);
     }
}

