#include "CScript.h"

CScript::CScript (CNetServer *server) : ctcp_version (CTCP_VERSION, MSG_SIZE),
  ctcp_finger (CTCP_FINGER, MSG_SIZE), ctcp_source (CTCP_SOURCE, MSG_SIZE),
  s (server)
{
  for (int i = 0; i <= LEVEL_MAX; i++)
    cmds[i] = NULL;
  source[0] = 0;
  dest[0] = 0;
  bind_cmd (scriptcmd_modlist, LEVEL_MODLIST, "!modlist");
  bind_cmd (scriptcmd_modadd, LEVEL_MODADD, "!modadd");
  bind_cmd (scriptcmd_moddel, LEVEL_MODDEL, "!moddel");
  bind_cmd (scriptcmd_quit, LEVEL_QUIT, "!quit");
  bind_cmd (scriptcmd_set, LEVEL_SET, "!set");
  bind_cmd (scriptcmd_loadconf, LEVEL_LOADCONF, "!loadconf");
  bind_cmd (scriptcmd_away, LEVEL_AWAY, "!away");
  bind_cmd (scriptcmd_nick, LEVEL_NICK, "!nick");
  bind_cmd (scriptcmd_unbind, LEVEL_UNBIND, "!unbind");
  bind_cmd (scriptcmd_join, LEVEL_JOIN, "!join");
  bind_cmd (scriptcmd_part, LEVEL_PART, "!part");
  bind_cmd (scriptcmd_status, LEVEL_STATUS, "!status");
  bind_cmd (scriptcmd_topic, LEVEL_TOPIC, "!topic");
  bind_cmd (scriptcmd_opme, LEVEL_OPME, "!opme");
  bind_cmd (scriptcmd_deopme, LEVEL_DEOPME, "!deopme");
  bind_cmd (scriptcmd_op, LEVEL_OP, "!op");
  bind_cmd (scriptcmd_deop, LEVEL_DEOP, "!deop");
  bind_cmd (scriptcmd_kick, LEVEL_KICK, "!kick");
  bind_cmd (scriptcmd_ban, LEVEL_BAN, "!ban");
  bind_cmd (scriptcmd_unban, LEVEL_UNBAN, "!unban");
  bind_cmd (scriptcmd_bk, LEVEL_BK, "!bk");
  bind_cmd (scriptcmd_invite, LEVEL_INVITE, "!invite");
  bind_cmd (scriptcmd_voice, LEVEL_VOICE, "!voice");
  bind_cmd (scriptcmd_devoice, LEVEL_DEVOICE, "!devoice");
  bind_cmd (scriptcmd_say, LEVEL_SAY, "!say");
  bind_cmd (scriptcmd_help, LEVEL_HELP, "!help");
  bind_cmd (scriptcmd_dcckill, LEVEL_DCCKILL, "!dcckill");
  bind_cmd (scriptcmd_dcclist, LEVEL_DCCLIST, "!dcclist");
  bind_cmd (scriptcmd_chat, LEVEL_CHAT, "!chat");
  bind_cmd (scriptcmd_randkick, LEVEL_RANDKICK, "!randkick");
  bind_cmd (scriptcmd_time, LEVEL_TIME, "!time");
  bind_cmd (scriptcmd_rose, LEVEL_ROSE, "!rose");
  bind_cmd (scriptcmd_reverse, LEVEL_REVERSE, "!reverse");
  bind_cmd (scriptcmd_cop, LEVEL_COP, "!cop");
  bind_cmd (scriptcmd_cdeop, LEVEL_CDEOP, "!cdeop");
  bind_cmd (scriptcmd_useradd, LEVEL_USERADD, "!useradd");
  bind_cmd (scriptcmd_userdel, LEVEL_USERDEL, "!userdel");
  bind_cmd (scriptcmd_userpass, LEVEL_USERPASS, "!userpass");
  bind_cmd (scriptcmd_usermsg, LEVEL_USERMSG, "!usermsg");
  bind_cmd (scriptcmd_usermask, LEVEL_USERMASK, "!usermask");
  bind_cmd (scriptcmd_userlevel, LEVEL_USERLEVEL, "!userlevel");
  bind_cmd (scriptcmd_id, LEVEL_ID, "!id");
  bind_cmd (scriptcmd_userlist, LEVEL_USERLIST, "!userlist");
  bind_cmd (scriptcmd_setpass, LEVEL_SETPASS, "!setpass");
  bind_cmd (scriptcmd_setmsg, LEVEL_SETMSG, "!setmsg");
  bind_cmd (scriptcmd_pass, LEVEL_PASS, "!pass");
  bind_cmd (scriptcmd_level, LEVEL_LEVEL, "!level");
  replies.add ((void *)scriptcmd_dcc_event);
}

CScript::~CScript (void)
{
  delete_cmds ();
}

void
CScript::bind_cmd (void (*action)(CNetServer *), int level, const char *cmd)
{
  if (level <= LEVEL_MAX)
    {
      cmd_type *c = cmds[level];
      cmds[level] = new cmd_type;
      cmds[level]->action = action;
      my_strncpy (cmds[level]->cmd, cmd, 15);
      cmds[level]->size = strlen (cmds[level]->cmd);
      cmds[level]->next = c;
    }
}

bool
CScript::unbind_cmd (const char *cmd)
{
  cmd_type *c, *c_previous;
  for (int i = 0; i <= LEVEL_MAX; i++)
    if (cmds[i] != NULL)
      {
        c = cmds[i];
        if (strcasecmp (cmd, c->cmd) == 0)	// if it's the first
          {
            cmds[i] = c->next;
            delete c;
            return 1;
          }
        c_previous = c;
        c = c->next;
        while (c != NULL)			// else checks the next
          if (strcasecmp (cmd, c->cmd) == 0)
            {
              c_previous->next=c->next;
              delete c;
              return 1;
            }
          else 
            {
              c_previous=c;
              c=c->next;
            }
      } 
  return 0;
}

void
CScript::send_partyline (const char *msg)
{
  for (int i = 0; i < DCC_NUM; i++)		// check all dccs
    if (DCCS[i]->status == DCC_CHAT)		// if it's a dcc chat
      DCCS[i]->dcc_chat_write (msg);		// send to it
}

void
CScript::send_text (const char *to, const char *format, ...)
{
  char buftext[MSG_SIZE+1];
  va_list args;
  va_start (args, format);
  vsnprintf (buftext, MSG_SIZE, format, args);
  va_end (args);
  if (CMD[2][0] >= '0' && CMD[2][0] <= '9' && strcmp (source, dest) == 0)
    DCCS[atoi(CMD[2])]->dcc_chat_write (buftext);
  else
    s->irc_privmsg (to, "%s", buftext);
}

void
CScript::irc_reply (void)
{
  if (strcmp (CMD[1], INIT) == 0)
    reply_init ();
  else if (strcmp (CMD[1], RPL_NOTOPIC) == 0)
    reply_notopic ();
  else if (strcmp (CMD[1], RPL_TOPIC) == 0)
    reply_topic ();
  else if (strcmp (CMD[1], RPL_WHOREPLY) == 0)
    reply_whoreply ();
  else if (strcmp (CMD[1], RPL_ENDOFWHO) == 0)
    reply_endofwho ();
  else if (strcmp (CMD[1], ERR_NOSUCHNICK) == 0)
    reply_nosuchnick ();
  else if (strcmp (CMD[1], ERR_NOSUCHCHANNEL) == 0)
    reply_nosuchchannel ();
  else if (strcmp (CMD[1], ERR_TOOMANYCHANNELS) == 0)
    reply_toomanychannels ();
  else if (strcmp (CMD[1], ERR_UNKNOWNCOMMAND) == 0)
    reply_unknowncommand ();
  else if (strcmp (CMD[1], ERR_NICKNAMEINUSE) == 0)
    reply_nicknameinuse ();
  else if (strcmp (CMD[1], ERR_INVITEONLYCHAN) == 0)
    reply_inviteonlychan ();
  else if (strcmp (CMD[1], ERR_BANNEDFROMCHAN) == 0)
    reply_bannedfromchan ();
  else if (strcmp (CMD[1], ERR_CHANOPRIVSNEEDED) == 0)
    reply_chanoprivsneeded ();
  else if (strcmp (CMD[1], RPL_WATCHOFFLINE) == 0)
    reply_watchoffline ();

  void (*f)(CNetServer *);
  replies.rewind ();
  while ((f = (void (*)(CNetServer *))replies.next ()) != NULL)
    f (s);
}

void
CScript::reply_init (void)
{
  // identify, join channels, set away
  if (s->services.exist && !s->services.identified)
    {
      s->services.nick_identify ();
      s->services.identified = 1;
    }
  for (int i = 0; i < s->channel_num; i++)
    CHANNELS[i]->irc_join ();
  if (s->away)
    s->irc_away (s->away);
}

void
CScript::reply_ison (void)
{
  // check if the services are active
  if (strcmp (CMD[3], NICKSERV) == 0)
    s->services.exist = 1;
}

void
CScript::reply_notopic (void)
{
  int i = CHANNEL_INDEX (CMD[3]);
  if (i != -1)
    CHANNELS[i]->topic = NULL;		// channel has no topic set
}

void
CScript::reply_topic (void)
{
  int i = CHANNEL_INDEX (CMD[3]);
  if (i != -1)
    CHANNELS[i]->topic = CMD[4];	// channel's topic
}

void
CScript::reply_whoreply (void)
{
  int i = CHANNEL_INDEX (CMD[3]);
  if (i == -1)		// add user to channel
    return;
  snprintf (buf[0], MSG_SIZE, "%s!%s@%s", CMD[7], CMD[4], CMD[5]);
  CHANNELS[i]->user_add (buf[0], CMD[8][1] == '@', CMD[8][1] == '+');

  if (CHANNELS[i]->kick_nick == CMD[7])	// if the bot was kicked by him
    {
      CHANNELS[i]->irc_kick (CMD[7], NULL);       // kick him back
      CHANNELS[i]->kick_nick = NULL;
    }
}

void
CScript::reply_endofwho (void)
{
  int i = CHANNEL_INDEX (CMD[3]);
  if (i != -1)
    if (CHANNELS[i]->kick_nick)
      CHANNELS[i]->kick_nick = NULL;	// stop waiting for that nick
}

void
CScript::reply_nosuchnick (void)
{
  // check if services are missing
  if (strcmp (CMD[3], NICKSERV) == 0
      || strcmp (CMD[3], CHANSERV) == 0
      || strcmp (CMD[3], MEMOSERV) == 0
      || strcmp (CMD[3], OPERSERV) == 0)
    s->services.exist = 0;
}

void
CScript::reply_nosuchchannel (void)
{
  // happens with an invalid name in !join or .conf file
  s->channel_del (CMD[3]);
}

void
CScript::reply_toomanychannels (void)
{
  s->channel_del (CMD[3]);
}

void
CScript::reply_unknowncommand (void)
{
  if (strcmp (CMD[3], NICKSERV) == 0
      || strcmp (CMD[3], CHANSERV) == 0
      || strcmp (CMD[3], MEMOSERV) == 0
      || strcmp (CMD[3], OPERSERV) == 0)
    s->services.exist = 0;
}

void
CScript::reply_nicknameinuse (void)
{
  // nick in use, therefore another copy of the bot is running, so exit
  s->write_botlog ("warning: nick %s already in use, reconnecting", (c_char)s->nick);
  s->irc_restart ();
  s->irc_connect ();
}

void
CScript::reply_inviteonlychan (void)
{
  if (s->services.exist)
    {
      int i = CHANNEL_INDEX (CMD[3]);
      if (i != -1)
        {
          s->services.chan_invite (CMD[3]);
          sleep (1);
          CHANNELS[i]->irc_join ();
        }
    }
}

void
CScript::reply_bannedfromchan (void)
{
  if (s->services.exist)
    {
      int i = CHANNEL_INDEX (CMD[3]);
      if (i != -1)
        {
          s->services.chan_unban (CMD[3]);
          CHANNELS[i]->irc_join ();
        }
    }
}

void
CScript::reply_chanoprivsneeded (void)
{
  if (s->services.exist)
    s->services.chan_op (CMD[3], s->nick);
}

void
CScript::reply_watchoffline (void)
{
  snprintf (buf[0], MSG_SIZE, "%s!%s@*", CMD[3], CMD[4]);
  if (s->users != NULL)
    s->users->match_set_id (buf[0], 0);
  s->irc_watch (CMD[3], 0);
}

void
CScript::irc_event (void)
{
  if (strcmp (CMD[0], "PING") == 0)
    event_ping ();
  else if (strcmp (CMD[0], "ERROR") == 0)
    event_error ();
  else if (strcmp (CMD[1], "NOTICE") == 0)
    event_notice ();
  else if (strcmp (CMD[1], "JOIN") == 0)
    event_join ();
  else if (strcmp (CMD[1], "QUIT") == 0)
    event_quit ();
  else if (strcmp (CMD[1], "NICK") == 0)
    event_nick ();
  else if (strcmp (CMD[1], "KICK") == 0)
    event_kick ();
  else if (strcmp (CMD[1], "PART") == 0)
    event_part ();
  else if (strcmp (CMD[1], "MODE") == 0)
    event_mode ();
  else if (strcmp (CMD[1], "PRIVMSG") == 0)
    event_privmsg ();
  else if (strcmp (CMD[1], "TOPIC") == 0)
    event_topic ();

  void (*f)(CNetServer *);
  events.rewind ();
  while ((f = (void (*)(CNetServer *))events.next ()) != NULL)
    f (s);
}

void
CScript::event_ping (void)
{
  s->irc_pong (CMD[1]);
  if (s->services.exist && !s->services.identified)
    {
      s->services.nick_identify ();
      s->services.identified = 1;
    }
  for (int i = 0; i < s->channel_num; i++)
    if (!CHANNELS[i]->joined)
      CHANNELS[i]->irc_join ();
}

void
CScript::event_error (void)
{
  s->write_botlog ("error from server: %s", CMD[1]);
  s->irc_restart ();
  s->host_current = s->host_current->next;
  s->last_try = s->time_now;	// wait TIME_RETRY before connecting
  s->irc_connect ();
}

void
CScript::event_notice (void)
{
  // to when nickserv restarts
  if (s->services.nickserv_auth
      && (s->services.nickserv_mask |= CMD[0])
      && strncasecmp (CMD[3], s->services.nickserv_auth, s->services.nickserv_auth.getlen ()) == 0)
    {
      s->services.exist = 1;
      s->services.nick_identify ();
    }
}

void
CScript::event_join (void)
{
  mask2nick (CMD[0], buf[0], NICK_SIZE);
  int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
  int chan_num = CHANNEL_INDEX (CMD[2]);

  if (chan_num != -1)
    {
      CHANNELS[chan_num]->user_add (CMD[0], 0, 0);	// add it to the list

      if (strcmp(buf[0], s->nick) == 0)			// when the bot joins
        {
          CHANNELS[chan_num]->joined = 1;
          s->irc_who (CMD[2]);			// get users
        }

      if (level < 0)				// shitlist
        {
          struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
          CHANNELS[chan_num]->irc_deopban (buf[0], u->mask);
          CHANNELS[chan_num]->irc_kick (buf[0], u->msg);
        }

      if (level > 0)				// welcome msg
        {
          struct CListUsers::user_type *u = USERS->match_mask2user (CMD[0]);
          if (u->msg)
            send_text (CMD[2], "%s", (c_char)u->msg);
        }

      if (level >= 5)				// op
        CHANNELS[chan_num]->irc_op (buf[0]);
    }
}

void
CScript::event_quit (void)
{
  int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
  int i, i2;
  mask2nick (CMD[0], buf[0], NICK_SIZE);

  for (i = 0; i < s->channel_num; i++)		// search in all channels
    {
      i2 = CHANNELS[i]->user_index (buf[0]);
      if (i2 != -1)				// if it was here  
        CHANNELS[i]->user_del (buf[0]);		// delete
    }

  if (level > 0)				// deactivate id
    USERS->match_set_id (CMD[0], 0);
}

void
CScript::event_nick (void)
{
  int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
  int i;
  // build new mask
  mask2nick (CMD[0], buf[0], NICK_SIZE);
  mask2user (CMD[0], buf[1]);
  mask2host (CMD[0], buf[2]);
  snprintf (buf[3], MSG_SIZE, "%s!%s@%s", CMD[2], buf[1], buf[2]);

  for (i = 0; i < s->channel_num; i++)	// refresh in all channels
    CHANNELS[i]->user_change_nick (buf[0], buf[3]);

  if (level != 0)				// deactivate id
    USERS->match_set_id (CMD[0], 0);

  if (strcasecmp (buf[0], s->nick) == 0)	// check if bot's nick changed
    my_strncpy (s->nick, CMD[2], NICK_SIZE);

  if ((USERS == NULL ? 0 : USERS->match_mask_level (buf[3])) < 0) // shitlist
    {
      struct CListUsers::user_type *u = USERS->match_mask2user (buf[3]);
      for (i = 0; i < s->channel_num; i++)		// search all channels
        if (CHANNELS[i]->user_index (CMD[2]) != -1)	// if it's there
          {
            CHANNELS[i]->irc_deopban (CMD[2], u->mask);
            CHANNELS[i]->irc_kick (CMD[2], u->msg);
          }
    }
}

void
CScript::event_kick (void)
{
  int chan_num = CHANNEL_INDEX (CMD[2]);

  if (chan_num == -1)				// should never fail
    return;

  mask2nick(CMD[0], buf[0], NICK_SIZE);

  CHANNELS[chan_num]->user_del (CMD[3]);	// delete kicked user

  if (strcmp (CMD[3], s->nick) == 0)		// if bot is kicked
    {
      for (int i = 0; CHANNELS[chan_num]->user_num > i; i++)
        delete CHANNELS[chan_num]->users[i];	// delete all users
      CHANNELS[chan_num]->user_num = 0;
      CHANNELS[chan_num]->joined = 0;		// the bot is out
      CHANNELS[chan_num]->kick_nick = buf[0];	// kick the other
      CHANNELS[chan_num]->irc_join ();		// join channel
    }
}

void
CScript::event_part (void)
{
  int chan_num = CHANNEL_INDEX (CMD[2]);
  if (chan_num != -1)			// doesn't happen when the bot !part
    {
      mask2nick (CMD[0], buf[0], NICK_SIZE);
      if (strcmp (buf[0], s->nick) == 0)		// if the bot parts
        {
          for (int i = 0; CHANNELS[chan_num]->user_num > i; i++)
            delete CHANNELS[chan_num]->users[i];	// delete all users
          CHANNELS[chan_num]->user_num = 0;
          CHANNELS[chan_num]->joined = 0;		// the bot is out
        }
      else
        CHANNELS[chan_num]->user_del (buf[0]);		// delete the user
    }
}

void
CScript::event_mode (void)
{
  int chan_num = CHANNEL_INDEX (CMD[2]);

  if (chan_num == -1)				// should never fail
    return;

  u_char mode = 0, pos = 0;
  mask2nick (CMD[0], buf[0], NICK_SIZE);

  for (int i = 0; i < (int)strlen (CMD[3]); i++)
    switch (CMD[3][i])
      {
        case '-':
          mode = 0;
          break;
        case '+':
          mode = 1;
          break;
        case 'o':
          if (!mode)
            {
              CHANNELS[chan_num]->user_change_op (CMD[4+pos], 0);
              if (strcasecmp (CMD[4+pos], s->nick) == 0)	// op the bot
                {
                  s->services.chan_op (CMD[2], s->nick);
                  mask2nick (CMD[0], buf[0], NICK_SIZE);
                  s->services.chan_deop (CMD[2], buf[0]);// deop the other
                }
            }
          else
            CHANNELS[chan_num]->user_change_op (CMD[4+pos], 1);
        pos++;
        break;
      case 'b':		// XXX update ban list in channel...
        pos++; 
        break;
      case 'v':
        if (!mode)
          CHANNELS[chan_num]->user_change_voice (CMD[4+pos], 0);
        else
          CHANNELS[chan_num]->user_change_voice (CMD[4+pos], 1);
        pos++;
        break;
      default:		// irc protocol changed?
        pos++;
   }
}

void
CScript::event_privmsg (void)
{
  int level = (USERS == NULL ? 0 : USERS->match_mask_level(CMD[0]));
  mask2nick (CMD[0], source, HOST_SIZE);
  if (CMD[2][0] != '#')
    mask2nick (CMD[0], dest, CHANNEL_SIZE);
  else
    my_strncpy (dest, CMD[2], CHANNEL_SIZE);
  strcpy (CMD[3], CMD[3]+num_spaces (CMD[3]));	// remove spaces from beginning

  if (CMD[3][0] == '\001')
    {
      if (strcmp (CMD[3], "\001VERSION\001") == 0 && ctcp_version)
        s->irc_notice (dest, "\001VERSION %s\001", (char *)ctcp_version);
      else if (strcmp (CMD[3], "\001FINGER\001") == 0 && ctcp_finger)
        s->irc_notice (dest, "\001FINGER %s\001", (char *)ctcp_finger);
      else if (strcmp (CMD[3], "\001USERINFO\001") == 0)
        s->irc_notice (dest, "\001USERINFO %s running on %s\001", VERSION_STRING, HOST);
      else if (strncmp (CMD[3], "\001PING ", 6) == 0)
        s->irc_notice (dest, CMD[3]);
      else if (strcmp (CMD[3], "\001TIME\001") == 0)
        s->irc_notice (dest, "\001TIME %s\001", get_asctime (s->time_now, buf[2], MSG_SIZE));
      else if (strcmp (CMD[3], "\001SOURCE\001") == 0 && ctcp_source)
        s->irc_notice (dest, "\001SOURCE %s\001", (char *)ctcp_source);
      else if (strncmp (CMD[3], "\001ECHO ", 6) == 0)
        s->irc_notice (dest, CMD[3]);
      else if (strcmp (CMD[3], "\001CLIENTINFO\001") == 0)
        s->irc_notice (dest, "\001CLIENTINFO VERSION FINGER USERINFO PING TIME SOURCE ECHO CLIENTINFO\001");
    }

  u_int size = num_notspaces (CMD[3]);
  for (int i = 0; i <= level; i++)		// check the binded commands
    {
      cmd_type *c = cmds[i];
      while (c != NULL)
        // if found
        if (strncasecmp (c->cmd, CMD[3], c->size) == 0 && size == c->size)
          {
            c->action (s);				// execute
            return;
          }
        else
          c = c->next;
    }

  if (CMD[2][0] >= '0' && CMD[2][0] <= '9')	// if it's a dcc chat
    {
      snprintf (buf[8], MSG_SIZE, "<%s> %s", source, CMD[3]);
      send_partyline (buf[8]);
    }
}

void
CScript::event_topic (void)
{
  int i = CHANNEL_INDEX (CMD[2]);
  if (i != -1)
    CHANNELS[i]->topic = CMD[3];	// channel's topic
}

// delete all commands
void
CScript::delete_cmds (void)
{
  cmd_type *c, *c2;
  for (int i = 0; i <= LEVEL_MAX; i++)
    {
      c = cmds[i];
      while (c != NULL)
        {
          c2 = c->next;
          delete c;
          c = c2;
        }
      cmds[i] = NULL; 
    }
}

