/*

  This module implements a subset of eggdrop's tcl functionality.

  Commands implemented:

  bind (pub, pubm, dcc, msg, nick), putserv, putlog, killutimer, utimer, rand,
  chanlist (without flag support, always lists everyone), onchan, matchattr,
  getchanhost, maskhost, adduser (with password "_dark", level 0 and mask
  handle!hostmask_user@hostmask_host), deluser

  As you know, mbot uses levels instead of flags. As such, I had to do some
  conversions... They are:

	- -> 0		h -> -1		c -> 0		v -> 2
	m -> 9		o -> 5		p -> 1		q -> -1
	n -> 10		d -> -1		b -> 10		u -> -1
	t -> 10		k -> -1		j -> 9		a -> 5

  The flag notation "flag&otherflags" is not supported.

  Don't just blindly load eggdrop scripts and hope they'll work flawlessly.
  There are differences in the way mbot and eggdrop works, especially when
  handling users and their flags. And don't forget to see the bot's log,
  missing tcl commands are put in it.

*/

#include "mbot.h"
#include <tcl.h>

#define LEVEL_TCL 10

#define SOURCE s->script.source
#define DEST s->script.dest
#define BUF s->script.buf
#define SEND_TEXT s->script.send_text

#define TIMERID_SIZE 50
#define TCLARG (ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])

#define BIND_MSG 0
#define BIND_DCC 1
#define BIND_PUB 2
#define BIND_MSGM 3
#define BIND_PUBM 4
#define BIND_NOTC 5
#define BIND_JOIN 6
#define BIND_PART 7
#define BIND_SIGN 8
#define BIND_TOPC 9
#define BIND_KICK 10
#define BIND_NICK 11
#define BIND_MODE 12
#define BIND_CTCP 13
#define BIND_CTCR 14
#define BIND_RAW 15
#define BIND_CHON 16
#define BIND_CHOF 17
#define BIND_SENT 18
#define BIND_CHAT 19
#define BIND_SPLT 20
#define BIND_REJN 21
#define BIND_WALL 22
#define BIND_AWAY 23
#define BIND_LOAD 24
#define BIND_UNLD 25
#define BIND_NULL -1

/////////////////////
// class definition
/////////////////////

class CTcl {
public:
  CTcl (CNetServer *);
  ~CTcl (void);

  bool init (void);
  bool loadscript (char *);
  int attr2level (const char *);

  struct bind_type {
    int type;
    int level;
    char *command;
    char *proc;
    bind_type *next;
  };
  bind_type *binds;

  void bind_add (int, const char *, const char *, const char *);
  bind_type *bind_get (bind_type *, int);
  void bind_del (int, char *);

  struct timer_type {
    time_t time;
    char *command;
    char timerid[TIMERID_SIZE+1];
    timer_type *next;
  };
  timer_type *timers;

  char *timer_add (time_t, const char *);
  bool timer_del (const char *);

  Tcl_Interp *interp;

  CNetServer *s;

private:

  u_int timerseq;

  void bind_delall (void);
  void timer_delall (void);

  char buf[10][MSG_SIZE+1];

};

struct tcl_type {
  CNetServer *server;
  CTcl *tcl;
  tcl_type *next;
};
struct tcl_type *tcl_list;

///////////////
// prototypes
///////////////

CTcl *interp2tcl (Tcl_Interp *);
bool check_args (CTcl *, int, int);
static void tcl_bind_error (CNetServer *);
static int tcl_bind TCLARG;
static int tcl_putserv TCLARG;
static int tcl_putlog TCLARG;
static int tcl_killutimer TCLARG;
static int tcl_utimer TCLARG;
static int tcl_rand TCLARG;
static int tcl_chanlist TCLARG;
static int tcl_onchan TCLARG;
static int tcl_matchattr TCLARG;
static int tcl_getchanhost TCLARG;
static int tcl_maskhost TCLARG;
static int tcl_adduser TCLARG;
static int tcl_deluser TCLARG;

static bool tcl_add (CNetServer *);
static CTcl *server2tcl (CNetServer *);
static void tcl_event (CNetServer *);
static void tcl_reply (CNetServer *);
static void tcl_conf (CNetServer *, const char *);
static void tcl_stop (CModule *);
static void tcl_start (CModule *);

////////////////////
// class functions
////////////////////

CTcl::CTcl (CNetServer *server)
{
  s = server;
  interp = NULL;
  binds = NULL;
  timers = NULL;
  timerseq = 0;
}

CTcl::~CTcl (void)
{
  if (interp != NULL)
    Tcl_DeleteInterp (interp);
  bind_delall ();
  timer_delall ();
} 

bool
CTcl::init (void)
{
  interp = Tcl_CreateInterp ();
  Tcl_Init (interp);
  Tcl_CreateCommand (interp, "bind", tcl_bind, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "putserv", tcl_putserv, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "puthelp", tcl_putserv, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "putlog", tcl_putlog, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "utimer", tcl_utimer, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "killutimer", tcl_killutimer, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "rand", tcl_rand, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "chanlist", tcl_chanlist, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "onchan", tcl_onchan, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "matchattr", tcl_matchattr, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "getchanhost", tcl_getchanhost, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "maskhost", tcl_maskhost, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "adduser", tcl_adduser, (ClientData)0, NULL);
  Tcl_CreateCommand (interp, "deluser", tcl_deluser, (ClientData)0, NULL);
  Tcl_SetVar (interp, "botnick", s->nick, 0);
  return 1;
}

bool
CTcl::loadscript (char *name)
{
  int i = Tcl_EvalFile (interp, name);
  if (i != 0)
    s->write_botlog ("%s", Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY));
  return (i == 0);
}

// convert eggdrop flags to mbot levels
// must be improved to support multiple flags at the same time
int
CTcl::attr2level (const char *attr)
{
  if (attr == NULL)
    return 10;
  while (*attr != 0)
    switch (*attr++)
      {
        case '-': return 0;	// everyone
        // standard flags
        case 'm': return 9;	// almost every feature
        case 'n': return 10;	// absolute control
        case 't': return 10;	// all features dealing with the botnet
        case 'x': return 3;	// can _send_ and receive files
        case 'j': return 9;	// like a "master" of the file area
        case 'c': return 0;	// didn't understand this one...
        case 'p': return 1;	// access to the party line
        case 'b': return 10;	// marks a user that is really a bot
        case 'u': return -1;	// user record is not sent to other bots
        case 'h': return -1;	// use nice bolds & inverse in the help files?
        // global or channel-specific flags
        case 'o': return 5;	// ask for channel op status on the channel
        case 'd': return -1;	// is not permitted to ever gain channel op
        case 'k': return -1;	// kicked if they ever attempt to join
        case 'f': return 3;	// the bot won't take revenge
        case 'a': return 5;	// autoop
        case 'v': return 2;	// autovoice
        case 'q': return -1;	// do not allow this person to get a voice
      }
  return 10;
}

void
CTcl::bind_add (int type, const char *attr, const char *command,
                const char *proc)
{
  bind_type *buf = binds;
  binds = new bind_type;
  binds->type = type;
  binds->level = attr2level (attr);
  binds->command = NULL;
  strset (&binds->command, command, MSG_SIZE);
  binds->proc = NULL;
  strset (&binds->proc, proc, MSG_SIZE);
  binds->next = buf;
}

// delete all binds with <type> and <command>
void
CTcl::bind_del (int type, char *command)
{
  if (binds == NULL)
    return;
  bind_type *buf, *buf2;
  if (binds->type == type && strcasecmp (binds->command, command) == 0)
    {
      buf = binds->next;
      free (binds->command);
      free (binds->proc);
      delete (binds);
      binds = buf;
    }
  buf = binds;
  if (buf == NULL)
    return;
  while (buf->next != NULL)
    {
      if (buf->next->type == type && strcasecmp (buf->next->command, command) == 0)
        {
          buf2 = buf->next->next;
          free (buf->next->command);
          free (buf->next->proc);
          delete (buf->next);
          buf->next = buf2;
          if (buf->next == NULL)
            return;
        }
      buf = buf->next;
    }
}

CTcl::bind_type *
CTcl::bind_get (bind_type *bind, int type)
{
  while (bind != NULL)
    {
      if (bind->type == type)
        return bind;
      bind = bind->next;
    }
  return NULL;
}

void
CTcl::bind_delall (void)
{
  bind_type *buf = binds, *buf2;
  while (buf != NULL)
    {
      buf2 = buf->next;
      free (buf->command);
      free (buf->proc);
      free (buf);
      buf = buf2;
    }
}

char *
CTcl::timer_add (time_t time, const char *command)
{
  timer_type *buf = timers;
  timers = new timer_type;
  timers->time = time;
  timers->command = NULL;
  strset (&timers->command, command, MSG_SIZE);
  snprintf (timers->timerid, TIMERID_SIZE, "timer%u", timerseq++);
  timers->next = buf;
  return timers->timerid;
}

bool
CTcl::timer_del (const char *timerid)
{
  if (timers == NULL)
    return 0;
  timer_type *buf, *buf2;
  if (strcmp (timerid, timers->timerid) == 0)
    {
      buf = timers->next;
      free (timers->command);
      delete (timers);
      timers = buf;
      return 1;
    }
  else
    {
      buf = timers;
      while (buf->next != NULL)
        {
          if (strcmp (timerid, buf->next->timerid) == 0)
            {
              buf2 = buf->next->next;
              free (buf->next->command);
              delete (buf->next);
              buf->next = buf2;
              return 1;
            }
          buf = buf->next;
        }
    }
  return 0;
}

void
CTcl::timer_delall (void)
{
  timer_type *buf = timers, *buf2;
  while (buf != NULL)
    {
      buf2 = buf->next;
      free (buf->command);
      delete (buf);
      buf = buf2;
    }
}

/////////////////
// tcl commands
/////////////////

// get the tcl object using the given interpreter
CTcl *
interp2tcl (Tcl_Interp *interp)
{
  tcl_type *buf = tcl_list;
  while (buf != NULL)
    {
      if (buf->tcl->interp != NULL && interp != NULL)
        if (memcmp (buf->tcl->interp, interp, sizeof (Tcl_Interp)) == 0)
          return buf->tcl;
      buf = buf->next;
    }
  return NULL;
}

bool
check_args (CTcl *tcl, int argc_used, int argc_required)
{
  if (tcl == NULL)
    return 0;
  if (argc_used < argc_required)
    {
      Tcl_AppendResult (tcl->interp, "not enough arguments", NULL);
      return 0;
    }
  return 1;
}

static void
tcl_bind_error (CNetServer *s)
{
  s->write_botlog ("WARNING: that tcl bind type is not implemented!");
}

static int
tcl_bind TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 5))
    return TCL_ERROR;
  if (strcasecmp (argv[1], "msg") == 0)
    {
      tcl->bind_del (BIND_MSG, argv[3]);
      tcl->bind_add (BIND_MSG, argv[2], argv[3], argv[4]);
    }
  else if (strcasecmp (argv[1], "dcc") == 0)
    {
      tcl->bind_del (BIND_DCC, argv[3]);
      tcl->bind_add (BIND_DCC, argv[2], argv[3], argv[4]);
    }
  else if (strcasecmp (argv[1], "fil") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "pub") == 0)
    {
      tcl->bind_del (BIND_PUB, argv[3]);
      tcl->bind_add (BIND_PUB, argv[2], argv[3], argv[4]);
    }
  else if (strcasecmp (argv[1], "msgm") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "pubm") == 0)
    tcl->bind_add (BIND_PUBM, argv[2], argv[3], argv[4]);
  else if (strcasecmp (argv[1], "notc") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "join") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "part") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "sign") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "topc") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "kick") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "nick") == 0)
    tcl->bind_add (BIND_NICK, argv[2], argv[3], argv[4]);
  else if (strcasecmp (argv[1], "mode") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "ctcp") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "ctcr") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "raw") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "bot") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "chon") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "chof") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "sent") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "rcvd") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "chat") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "link") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "disc") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "splt") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "rejn") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "filt") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "flud") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "note") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "act") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "wall") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "bcst") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "chjn") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "chpt") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "time") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "away") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "load") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "unld") == 0)
    tcl_bind_error (tcl->s);
  else if (strcasecmp (argv[1], "nkch") == 0)
    tcl_bind_error (tcl->s);
  else
    tcl->s->write_botlog ("WARNING: unknown tcl bind.");
  return TCL_OK;
}

static int
tcl_putserv TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  tcl->s->writeln ("%s", argv[1]);
  return TCL_OK;
}

static int
tcl_putlog TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  tcl->s->write_botlog ("%s", argv[1]);
  return TCL_OK;
}

static int
tcl_utimer TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 3))
    return TCL_ERROR;
  char *timerid = tcl->timer_add (tcl->s->time_now + atoi (argv[1]), argv[2]);
  Tcl_AppendResult (interp, timerid, NULL);
  return TCL_OK;
}

static int
tcl_killutimer TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  if (!tcl->timer_del (argv[1]))
    {
      Tcl_AppendResult (interp, "invalid timer", NULL);
      return TCL_ERROR;
    }
  return TCL_OK;
}

static int
tcl_rand TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  char randstring[51];
  snprintf (randstring, 50, "%lu", (u_long) random_num (atoi (argv[1])));
  Tcl_AppendResult (interp, randstring, NULL);
  return TCL_OK;
}

static int
tcl_chanlist TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  for (int i = 0; i < tcl->s->channel_num; i++)
    if (tcl->CHANNELS[i]->name |= argv[1])
      {
        for (int i2 = 0; i2 < tcl->CHANNELS[i]->user_num; i2++)
          Tcl_AppendElement (interp, tcl->CHANNELS[i]->users[i2]->nick);
        return TCL_OK;
      }
  Tcl_AppendResult (interp, "nonexistant channel", NULL);
  return TCL_ERROR;
}

static int
tcl_onchan TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 3))
    return TCL_ERROR;
  for (int i = 0; i < tcl->s->channel_num; i++)
    if (tcl->CHANNELS[i]->name |= argv[2])
      for (int i2 = 0; i2 < tcl->CHANNELS[i]->user_num; i2++)
        if (tcl->CHANNELS[i]->users[i2]->nick |= argv[1])
          {
            Tcl_AppendResult (interp, "1", NULL);
            return TCL_OK;
          }
  Tcl_AppendResult (interp, "0", NULL);
  return TCL_OK;
}

static int
tcl_matchattr TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 3))
    return TCL_ERROR;
  int userlevel = (tcl->USERS == NULL ? 0 : tcl->USERS->match_mask_level (argv[1]));
  int flagslevel = tcl->attr2level (argv[2]);
  if ((userlevel >= flagslevel && flagslevel != -1)
      || (userlevel == -1 && flagslevel == -1))
    Tcl_AppendResult (interp, "1", NULL);
  else
    Tcl_AppendResult (interp, "0", NULL);
  return TCL_OK;
}

static int
tcl_getchanhost TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 3))
    return TCL_ERROR;
  for (int i = 0; i < tcl->s->channel_num; i++)
    if (tcl->CHANNELS[i]->name |= argv[2])
      for (int i2 = 0; i2 < tcl->CHANNELS[i]->user_num; i2++)
        if (tcl->CHANNELS[i]->users[i2]->nick |= argv[1])
          {
            char user[USER_SIZE+1];
            char host[HOST_SIZE+1];
            mask2user (tcl->CHANNELS[i]->users[i2]->mask, user);
            mask2host (tcl->CHANNELS[i]->users[i2]->mask, host);
            char userhost[USER_SIZE+HOST_SIZE+2];
            snprintf (userhost, USER_SIZE+HOST_SIZE+1, "%s@%s", user, host);
            Tcl_AppendResult (interp, userhost, NULL);
            return TCL_OK;
          }
  return TCL_OK;
}

static int
tcl_maskhost TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  char nick[NICK_SIZE+1];
  char user[USER_SIZE+1];
  char host[HOST_SIZE+1];
  char mask[MASK_SIZE+1], gen_mask[MASK_SIZE+1];
  mask2nick (argv[1], nick, NICK_SIZE);
  mask2user (argv[1], user);
  mask2host (argv[1], host);
  // make sure we always have a correct mask
  snprintf (mask, MASK_SIZE, "%s!%s@%s", nick, user, host);
  make_generic_mask (mask, gen_mask);
  Tcl_AppendResult (interp, gen_mask, NULL);
  return TCL_OK;
}

static int
tcl_adduser TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 3))
    return TCL_ERROR;
  if (tcl->USERS == NULL)
    {
      Tcl_AppendResult (interp, "0", NULL);
      return TCL_OK;
    }
  char user[USER_SIZE+1];
  char host[HOST_SIZE+1];
  char mask[MASK_SIZE+1];
  mask2user (argv[2], user);
  mask2host (argv[2], host);
  snprintf (mask, MASK_SIZE, "%s!%s@%s", argv[1], user, host);
  if (tcl->USERS->abs_mask2user (mask) == NULL)
    {
      Tcl_AppendResult (interp, "0", NULL);
      return TCL_OK;
    }
  Tcl_AppendResult (interp, "1", NULL);
  return TCL_OK;
}

static int
tcl_deluser TCLARG
{
  CTcl *tcl = interp2tcl (interp);
  if (!check_args (tcl, argc, 2))
    return TCL_ERROR;
  return TCL_OK;
}

////////////////////
// module managing
////////////////////

// add a tcl to the list
static bool
tcl_add (CNetServer *s)
{
  tcl_type *tcl_buf = tcl_list;
  tcl_list = new tcl_type;
  if (tcl_list == NULL)
    {
      tcl_list = tcl_buf;
      return 0;
    }
  tcl_list->server = s;
  tcl_list->tcl = new CTcl (s);
  tcl_list->next = tcl_buf;
  return tcl_list->tcl->init ();
}

// return the tcl for a given server, NULL if nonexistant
static
CTcl *server2tcl (CNetServer *s)
{
  tcl_type *buf = tcl_list;
  while (buf != NULL)
    {
      if (buf->server == s)
        return buf->tcl;
      buf = buf->next;
    }
  return NULL;
}

// events handler
static void
tcl_event (CNetServer *s)
{
  CTcl *tcl = server2tcl (s);
  if (tcl == NULL)
    return;

  if (strcmp (CMD[1], "JOIN") == 0)
    {
    }
  else if (strcmp (CMD[1], "QUIT") == 0)
    {
    }
  else if (strcmp (CMD[1], "KICK") == 0)
    {
    }
  else if (strcmp (CMD[1], "PART") == 0)
    {
    }
  else if (strcmp (CMD[1], "NICK") == 0)
    {
      // bind nick
      int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
      CTcl::bind_type *bind_buf = tcl->binds;
      while (bind_buf != NULL)
        {
          if ((bind_buf = tcl->bind_get (bind_buf, BIND_NICK)) == NULL)
            return;
          if (level >= bind_buf->level
//           && strcasecmp (BUF[1], bind_buf->command) == 0)
   ) // isto eh suposto ser match_mask ("#canal novonick", bind_buf->command)
   // falta tb meter um canal correcto em vez de "#" ali em baixo
            {
              mask2nick (CMD[0], BUF[3], NICK_SIZE);
              mask2user (CMD[0], BUF[4]);
              mask2host (CMD[0], BUF[5]);
              struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
              // procname <nick> <user@host> <handle> <channel> <newnick>
              snprintf (BUF[6], MSG_SIZE, "%s %s %s@%s %s %s %s",
                        bind_buf->proc, BUF[3], BUF[4],
                        BUF[5], u ? (char *)u->mask : "*",
                        "#", CMD[2]);
              if (Tcl_Eval (tcl->interp, BUF[6]) != 0)
                tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
            }
          bind_buf = bind_buf->next;
        }
    }
  else if (strcmp (CMD[1], "MODE") == 0)
    {
    }
  else if (strcmp (CMD[1], "PRIVMSG") == 0)
    {
      int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
      if (CMD[2][0] >= '0' && CMD[2][0] <= '9')
        // bind dcc
        {
          CTcl::bind_type *bind_buf = tcl->binds;
          while ((bind_buf = tcl->bind_get (bind_buf, BIND_DCC)) != NULL)
            {
              strsplit (CMD[3], BUF, 1);
              if (level >= bind_buf->level && strcasecmp (BUF[0], bind_buf->command) == 0)
                {
                  struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
                  // procname <handle> <idx> <args>
                  snprintf (BUF[6], MSG_SIZE, "%s %s %s \"%s\"",
                            bind_buf->proc,
                            u ? (char *)u->mask : "*",
                            CMD[2], BUF[2]);
                  if (Tcl_Eval (tcl->interp, BUF[6]) != 0)
                    tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
                }
              bind_buf = bind_buf->next;
            }
        }
      else if (CMD[2][0] == '#')
        {
          CTcl::bind_type *bind_buf = tcl->binds;
          while (bind_buf != NULL)
            {
              if ((bind_buf = tcl->bind_get (bind_buf, BIND_PUB)) != NULL)
                // bind pub
                {
                  strsplit (CMD[3], BUF, 1);
                  if (level >= bind_buf->level && strcasecmp (BUF[0], bind_buf->command) == 0)
                    {
                      mask2nick (CMD[0], BUF[3], NICK_SIZE);
                      mask2user (CMD[0], BUF[4]);
                      mask2host (CMD[0], BUF[5]);
                      struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
                      // procname <nick> <user@host> <handle> <channel> <args>
                      snprintf (BUF[6], MSG_SIZE, "%s %s %s@%s %s %s \"%s\"",
                                bind_buf->proc, BUF[3], BUF[4], BUF[5],
                                (u ? (char *)u->mask : "*"),
                                CMD[2], BUF[2]);
                      if (Tcl_Eval (tcl->interp, BUF[6]) != 0)
                        tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
                    }
                  bind_buf = bind_buf->next;
                }
            }
          bind_buf = tcl->binds;
          while (bind_buf != NULL)
            {
              if ((bind_buf = tcl->bind_get (bind_buf, BIND_PUBM)) != NULL)
                // bind pubm
                {
                  if (level >= bind_buf->level && match_mask (CMD[3], bind_buf->command))
                    {
                      mask2nick (CMD[0], BUF[3], NICK_SIZE);
                      mask2user (CMD[0], BUF[4]);
                      mask2host (CMD[0], BUF[5]);
                      struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
                      // procname <nick> <user@host> <handle> <channel> <text>
                      snprintf (BUF[6], MSG_SIZE, "%s %s %s@%s %s %s \"%s\"",
                                bind_buf->proc, BUF[3], BUF[4], BUF[5],
                                (u ? (char *)u->mask : "*"),
                                CMD[2], CMD[3]);
                      if (Tcl_Eval (tcl->interp, BUF[6]) != 0)
                        tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
                    }
                  bind_buf = bind_buf->next;
                }
            }
        }
      else
        // bind msg
        {
          CTcl::bind_type *bind_buf = tcl->binds;
          while (bind_buf != NULL)
            {
              if ((bind_buf = tcl->bind_get (bind_buf, BIND_MSG)) == NULL)
                return;
              strsplit (CMD[3], BUF, 1);
              if (level >= bind_buf->level && match_mask (BUF[0], bind_buf->command))
                {
                  mask2nick (CMD[0], BUF[3], NICK_SIZE);
                  mask2user (CMD[0], BUF[4]);
                  mask2host (CMD[0], BUF[5]);
                  struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
                  // procname <nick> <user@host> <handle> <args>
                  snprintf (BUF[6], MSG_SIZE, "%s %s %s@%s %s \"%s\"",
                            bind_buf->proc, BUF[3], BUF[4], BUF[5],
                            (u ? (char *)u->mask : "*"), BUF[2]);
                  if (Tcl_Eval (tcl->interp, BUF[6]) != 0)
                    tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
                }
              bind_buf = bind_buf->next;
            }
        }
    }
}

// replies handler
static void
tcl_reply (CNetServer *s)
{
  CTcl *tcl = server2tcl (s);
  if (tcl == NULL)
    return;
}

// timer handler
static void
tcl_timer (CNetServer *s)
{
  CTcl *tcl = server2tcl (s);
  if (tcl == NULL)
    return;
  CTcl::timer_type *buf = tcl->timers;
  while (buf != NULL)
    {
      if (s->time_now >= buf->time)
        {
          if (Tcl_Eval (tcl->interp, buf->command) != 0)
            tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
          tcl->timer_del (buf->timerid);
        }
      buf = buf->next;
    }
}

// !tcl <load | eval> [args]
static void
tcl_cmd_tcl (CNetServer *s)
{
  CTcl *tcl = server2tcl (s);
  if (tcl == NULL)
    return;
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !tcl <load | eval> [args]");
      return;
    }
  if (strcasecmp (BUF[1], "load") == 0)
    {
      if (tcl->loadscript (BUF[2]))
        SEND_TEXT (DEST, "Script loaded.");
      else
        SEND_TEXT (DEST, "Error loading script.");
    }
  else if (strcasecmp (BUF[1], "eval") == 0)
    {
      if (Tcl_Eval (tcl->interp, BUF[2]) != 0)
        tcl->s->write_botlog ("%s", Tcl_GetVar (tcl->interp, "errorInfo", TCL_GLOBAL_ONLY));
    }
  else
    SEND_TEXT (DEST, "Unknown tcl option.");
}

// configuration file's local parser
static void
tcl_conf (CNetServer *s, c_char bufread)
{
  char buf[3][MSG_SIZE+1];

  strsplit (bufread, buf, 2);

  if (strcasecmp (buf[0], "bot") == 0)
    {
      CTcl *tcl = server2tcl (s);
      if (tcl == NULL)		// if there's no tcl yet for this server
        return;
      if (!tcl_add (s))
        s->bot->conf_error ("failed tcl initialization");
      s->script.events.add ((void *)tcl_event);
      s->script.replies.add ((void *)tcl_reply);
      s->script.timers.add ((void *)tcl_timer);
      s->script.bind_cmd (tcl_cmd_tcl, LEVEL_TCL, "!tcl");
    }
  else if (strcasecmp (buf[0], "tcl") == 0)
    {
      CTcl *tcl = server2tcl (s);
      if (tcl != NULL)
        {
          if (!tcl->loadscript (buf[1]))
            s->bot->conf_error ("can't load tcl script");
        }
      else
        s->bot->conf_error ("tcl interpreter not set for this server");
    }
}

// module termination
static void
tcl_stop (CModule *m)
{
  tcl_type *buf = tcl_list, *buf2;
  while (buf != NULL)
    {
      delete buf->tcl;
      buf->server->script.events.del ((void *)tcl_event);
      buf->server->script.replies.del ((void *)tcl_reply);
      buf->server->script.timers.del ((void *)tcl_timer);
      buf->server->script.unbind_cmd ("!tcl");
      buf2 = buf->next;
      delete buf;
      buf = buf2;
    }
}

// module initialization
static void
tcl_start (CModule *m)
{
  tcl_list = NULL;
}

struct CModule::module_type module = {
  MODULE_VERSION,
  "tcl",
  tcl_start,
  tcl_stop,
  tcl_conf,
  NULL
};
