/*

  This module is the privmsg flood detector. It watches all channels in each
server and kicks/warns users if certain limits are reached. Note that it does
not bother users with level >= 5. These are the configuration options:

 Number of equal msgs to be considered flood (0 to turn off):
set flood_maxmsg <num>

  Number of msgs (may be different) with the same mask (0 to turn off):
set flood_maxmask <num>

  Number of msgs beyond the 2 defined before, to kick. If not 0, sends in pvt
the flood_kickmsg when flood is detected. Default is 0.
set flood_maxwarn <num>

  Reason of the flood warn/kick:
set flood_kickmsg <text>

*/

#include "mbot.h"

#define FLOOD_MSG 3			// equal lines with the same mask
#define FLOOD_MASK 10			// lines with the same mask
#define FLOOD_WARN 0	// lines with the same mask, after reaching the others, to kikar
		// if !0, sends flood_kickmsg to pvt, on the first time

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

struct flood_type {
  CNetServer *server;
  u_int flood_maxmsg;
  u_int flood_maxmask;
  u_int flood_maxwarn;
  CString flood_kickmsg;
  flood_type *next;
  flood_type (CNetServer *s, flood_type *n) : server (s),
    flood_maxmsg (FLOOD_MSG), flood_maxmask (FLOOD_MASK),
    flood_maxwarn (FLOOD_WARN), flood_kickmsg (KICK_SIZE), next (n) {}
};
struct flood_type *flood_list;

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

static void flood_add (CNetServer *);
static flood_type *server2flood (CNetServer *);
static void flood_event (CNetServer *);
static void flood_conf (CNetServer *, c_char);
static void flood_stop (CModule *);
static void flood_start (CModule *);
static void flood_var (CNetServer *, c_char, char *, size_t);

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

// add a flood to the list
static void
flood_add (CNetServer *s)
{
  flood_type *buf = flood_list;
  flood_list = new flood_type (s, buf);
}

// returns the flood for a given server, NULL if nonexistant
static flood_type *
server2flood (CNetServer *s)
{
  flood_type *buf = flood_list;
  while (buf != NULL)
    {
      if (buf->server == s)
        return buf;
      buf = buf->next;
    }
  return NULL;
}

// watch privmsgs to keep flood status, and take action when necessary
static void
flood_event (CNetServer *s)
{
  if (strcmp (CMD[1], "PRIVMSG") != 0 || CMD[2][0] != '#')
    return;
  flood_type *flood = server2flood (s);
  if (flood == NULL)
    return;
  int reallevel = (USERS == NULL ? 0 : USERS->match_mask_reallevel (CMD[0]));
  int i = CHANNEL_INDEX (CMD[2]);
  if (i == -1)
    return;

  // different mask, reset flood status
  if (strcasecmp (CMD[0], CHANNELS[i]->last_mask) != 0)
    {
      my_strncpy (CHANNELS[i]->last_mask, CMD[0], MASK_SIZE);
      my_strncpy (CHANNELS[i]->last_msg, CMD[3], MSG_SIZE);
      CHANNELS[i]->mask_num = 1;
      CHANNELS[i]->msg_num = 1;
      CHANNELS[i]->warn_num = 0;
      return;
    }

  // reached maximum different msgs in a row by the same mask
  if (CHANNELS[i]->mask_num == flood->flood_maxmask)
    {
      if (reallevel < 4)
        {
          if (flood->flood_maxwarn != 0
              && CHANNELS[i]->warn_num != flood->flood_maxwarn)
            {
              if (CHANNELS[i]->warn_num == 0 && flood->flood_kickmsg)
                SEND_TEXT (SOURCE, "%s", (c_char)flood->flood_kickmsg);
              CHANNELS[i]->warn_num++;
              return;
            }
          CHANNELS[i]->irc_kick (SOURCE, flood->flood_kickmsg);
        }
      CHANNELS[i]->mask_num = 1;
      CHANNELS[i]->warn_num = 0;
      return;
    }

  // same mask and same msg
  if (strcasecmp (CMD[3], CHANNELS[i]->last_msg) == 0)
    {
      if (CHANNELS[i]->msg_num == flood->flood_maxmsg)
        {
          if (reallevel < 4)
            {
              if (flood->flood_maxwarn != 0
                  && CHANNELS[i]->warn_num != flood->flood_maxwarn)
                {
                  if (CHANNELS[i]->warn_num == 0 && flood->flood_kickmsg)
                    SEND_TEXT (SOURCE, "%s", (c_char)flood->flood_kickmsg);
                  CHANNELS[i]->warn_num++;
                  return;
                }
              CHANNELS[i]->irc_kick (SOURCE, flood->flood_kickmsg);
            }
          CHANNELS[i]->msg_num = 1;
          CHANNELS[i]->warn_num = 0;
        } 
      else 
        CHANNELS[i]->msg_num++;
    }
  else
    {
      my_strncpy (CHANNELS[i]->last_msg, CMD[3], MSG_SIZE);
      CHANNELS[i]->msg_num = 1;
      CHANNELS[i]->warn_num = 0;
    }
  CHANNELS[i]->mask_num++;
}

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

  strsplit (bufread, buf, 1);

  if (strcasecmp (buf[0], "bot") == 0)
    {
      if (server2flood (s) == NULL)
        {
          flood_add (s);
          s->script.events.add ((void *)flood_event);
          s->vars.var_add ("flood_maxmsg", flood_var);
          s->vars.var_add ("flood_maxmask", flood_var);
          s->vars.var_add ("flood_maxwarn", flood_var);
          s->vars.var_add ("flood_kickmsg", flood_var);
        }
    }
}

// module termination
static void
flood_stop (CModule *m)
{
  flood_type *buf = flood_list, *buf2;
  while (buf != NULL)
    {
      buf->server->script.events.del ((void *)flood_event);
      buf2 = buf->next;
      delete buf;
      buf = buf2;
    }
  // try to remove from all servers, even if it's not there (we don't know)
  for (int i=0; i<m->b->server_num; i++)
    {
      m->b->servers[i]->vars.var_del ("flood_maxmsg");
      m->b->servers[i]->vars.var_del ("flood_maxmask");
      m->b->servers[i]->vars.var_del ("flood_maxwarn");
      m->b->servers[i]->vars.var_del ("flood_kickmsg");
    }
}

// module initialization
void
flood_start (CModule *m)
{
  flood_list = NULL;
}

static void
flood_var (CNetServer *s, c_char name, char *data, size_t n)
{
  flood_type *buf = server2flood (s);
  if (buf == NULL)
    return;
  if (strcasecmp (name, "flood_maxmsg") == 0)
    {
      if (n != 0)
        itoa (buf->flood_maxmsg, data, n);
      else
        buf->flood_maxmsg = atoi (data);
    }
  else if (strcasecmp (name, "flood_maxmask") == 0)
    {
      if (n != 0)
        itoa (buf->flood_maxmask, data, n);
      else
        buf->flood_maxmask = atoi (data);
    }
  else if (strcasecmp (name, "flood_maxwarn") == 0)
    {
      if (n != 0)
        itoa (buf->flood_maxwarn, data, n);
      else
        buf->flood_maxwarn = atoi (data);
    }
  else if (strcasecmp (name, "flood_kickmsg") == 0)
    {
      if (n != 0)
        my_strncpy (data, buf->flood_kickmsg, n);
      else
        buf->flood_kickmsg = data;
    }
}

struct CModule::module_type module = {
  MODULE_VERSION,
  "flood",
  flood_start,
  flood_stop,
  flood_conf,
  flood_var
};
