#include "CNetServer.h"

CNetServer::CNetServer (CBot *b) : CNet (b), virtualhost (HOST_SIZE),
  nick (NICK_SIZE), nick_orig (NICK_SIZE), user (USER_SIZE),
  name (NAME_SIZE), away (AWAY_SIZE), quit (QUIT_SIZE), dcc_file (PATH_MAX),
  dcc_motdfile (PATH_MAX), vars (this), services (this), script (this)
{
  bot = b;
  hosts = host_current = NULL;
  last_try = 0;
  change_time = 0;
  time_now = bot->time_now + change_time;
  cmd_i = 0;
  users = NULL;
  for (int i = 0; i < CHANNEL_MAX; i++)
    channels[i] = NULL;
  channel_num = 0;
  for (int i = 0; i < DCC_MAX; i++)
    dccs[i] = NULL;
  dcc_num = 0;
  dcc_port = 0;
  bufread[0] = 0;
  dcc_motd = NULL;
  bufpos = 0;
  bytesin = bytesout = 0;
  time_read = 0;
  uptime = 0;
}

CNetServer::~CNetServer (void)
{
  irc_restart ();				// turn off networking
  if (dcc_motd != NULL)
    delete dcc_motd;
  if (users != NULL)
    delete users;
}

void
CNetServer::irc_pong (c_char pong)
{
  writeln ("PONG :%s", pong);
}

// if msg is NULL, use the default
void
CNetServer::irc_quit (c_char msg)
{
  writeln ("QUIT :%s", msg == NULL ? quit : msg);
}

void
CNetServer::irc_privmsg (c_char to, c_char format, ...)
{
  char bufprivmsg[MSG_SIZE+1];
  va_list args;
  va_start (args, format);
  vsnprintf (bufprivmsg, MSG_SIZE, format, args);
  va_end (args);
  writeln ("PRIVMSG %s :%s", to, bufprivmsg);
}

void
CNetServer::irc_notice (c_char to, c_char format, ...)
{
  char bufnotice[MSG_SIZE+1];
  va_list args;
  va_start (args, format);
  vsnprintf (bufnotice, MSG_SIZE, format, args);
  va_end (args);
  writeln ("NOTICE %s :%s", to, bufnotice);
}

void
CNetServer::irc_nick (c_char new_nick)
{
  writeln ("NICK %s", new_nick);
}

void
CNetServer::irc_user (c_char new_user, c_char new_name)
{
  writeln ("USER %s 0 %s :%s", new_user, (char *)host_current->host, new_name);
}

void
CNetServer::irc_whois (c_char what)
{
  writeln ("WHOIS %s", what);
}

void
CNetServer::irc_ison (c_char nick_list)
{
  writeln ("ISON %s", nick_list);
}

void
CNetServer::irc_watch (c_char nick, bool add)
{
  writeln ("WATCH %c%s", (add ? '+' : '-'), nick);
}

void
CNetServer::irc_who (c_char what)
{
  writeln ("WHO %s", what);
}

void
CNetServer::irc_away (c_char reason)
{
  writeln ("AWAY :%s", reason);
}

void
CNetServer::irc_oper (c_char what_nick, c_char pass)
{
  writeln ("OPER %s %s", what_nick, pass);
}

void
CNetServer::irc_kill (c_char what_nick, c_char reason)
{
  writeln ("KILL %s :%s", what_nick, reason);
}

void
CNetServer::write_botlog (c_char format, ...)
{
  char st_time[TIME_SIZE+1];
  get_asctime (bot->time_now, st_time, TIME_SIZE);
  cerr << st_time << " " << nick << " " << host_current->host << ": ";
  va_list args;
  va_start (args, format);
  char buf[MSG_SIZE+1];
  vsnprintf (buf, MSG_SIZE, format, args);
  va_end (args);
  cerr << buf << endl;
}

void
CNetServer::irc_connect (void)
{
  if (host_current == NULL)
    {
      // don't use this->write_botlog() because it would crash (host_current..)
      string_time (bot->time_now, buf);	// don't use this server's local time
      bot->write_botlog ("FATAL: %s: No host defined, can't connect!", (c_char)nick);
      mbot_exit ();
    }

  if (!connected && !connecting)
    {
      time_read = time_now;
      if (difftime (time_now, last_try) < TIME_RETRY)
        return;
      last_try = time_now;
      write_botlog ("connecting to server");

      if (!resolvehost (host_current->host))
        {
          write_botlog ("error connecting to server: can't resolve %s: %s",
                        (c_char)host_current->host, strerror (errno));
          host_current = host_current->next;
          return;
        }
      if (!create_tcp (host_current->port))
        {
          write_botlog ("error connecting to server: failed socket(): %s", strerror (errno));
          return;
        }
    }
  else		// connecting
    {
      if (!openhost ())
        {
          write_botlog ("error connecting to server: %s",
                        strerror (errno));
          host_current = host_current->next;
        }
      if (!connected)
        return;
      // we're connected
      uptime = time_now;
      irc_nick (nick);
      irc_user (user, name);
    }
}

// close socket, dccs and delete users from channels
void
CNetServer::irc_restart (void)
{
  int i,i2;
  close (sock);
  sock = -1;
  connected = connecting = 0;
  nick = nick_orig;
  services.exist = services.nickserv_pass;
  services.identified = 0;
  for (i = 0; i < channel_num; i++)
    {
      for (i2 = 0; i2 < channels[i]->user_num; i2++)
        delete channels[i]->users[i2];
      channels[i]->user_num = 0;
      channels[i]->joined = 0;
    }
  for (i = 0; i < dcc_num; i++)
    delete dccs[i];
  dcc_num = 0;
  *bufread = 0;
  bufpos = 0;
  bytesin = bytesout=0;
}

void
CNetServer::work (void)
{
  time_t time_last = time_now;
  time_now = bot->time_now + change_time;

  if (connected)
    {
      int i = readln ();
      if (i == 1)			// there's a new msg to parse
        {
          irc_parse ();
          script.irc_reply ();
          script.irc_event ();
        }
      else
        if (i == -1)			// error while reading
          {
            write_botlog ("ERROR reading from socket: %s", strerror (errno));
            irc_restart ();
            host_current = host_current->next;
            last_try = time_now;    // wait TIME_RETRY before connecting
            return;
          }
        else
          if (difftime (time_now, time_read) > TIME_STONED)
            {
              write_botlog ("ERROR: server timed out, disconnecting");
              irc_restart ();
              host_current = host_current->next;
              last_try = time_now;    // wait TIME_RETRY before connecting
              return;
            }

      // big mess with dcc's, gotta clean it up sometime
      for (i = 0; i < dcc_num; i++)
        if (dccs[i] != NULL)
          if (dccs[i]->dcc_work () == 0)	// inactive dcc, delete it
            {
              int i2;
              for (i2 = 0; i2 < dcc_num; i2++)	// delete dependencies
                {
                  if (dccs[i2]->dcc_from_index == i)
                    dccs[i2]->dcc_from_index = -1;
                  if (dccs[i2]->dcc_from_index > i)
                    dccs[i2]->dcc_from_index--;
                }
              delete dccs[i];
              for (i2 = i; i2 < dcc_num; i2++)
                {
                  dccs[i2] = dccs[i2+1];
                  if (dccs[i2] != NULL)
                    dccs[i2]->index--;
                }
              dcc_num--;
            }

      // run the timers
      if (time_last != time_now)
        {
          void (*f)(CNetServer *);
          script.timers.rewind ();
          while ((f = (void (*)(CNetServer *))script.timers.next ()) != NULL)
            f (this);
        }

      // pass control to the workers
      void (*f)(CNetServer *);
      works.rewind ();
      while ((f = (void (*)(CNetServer *))works.next ()) != NULL)
        f (this);
    }
  else
    irc_connect ();
}

// return 0 if ok, 1 if limit exceeded, 2 if it already exists
int
CNetServer::channel_add (c_char chan_name, c_char chan_key)
{
  if (channel_num < CHANNEL_MAX)			// limit
    {
      if (channel_index (chan_name) != -1)			// exists
        return 2;
      channels[channel_num] = new CChannel (chan_name, chan_key, this);
      channel_num++;
      return 0;
    }
  else
    return 1;
}

// return 0 if ok, 1 if nonexistent
int
CNetServer::channel_del (c_char chan_name)
{
  int i = channel_index (chan_name);
  if (i != -1)
    {
      delete channels[i];
      for (; i+1 < channel_num; i++)
        channels[i] = channels[i+1];
      channel_num--;
      return 0;
    }
  else
    return 1;
}

// return channel's table index, -1 if nonexistent
int
CNetServer::channel_index (c_char chan_name)
{
  for (int i = 0; i < channel_num; i++)
    if (channels[i] != NULL)
      if (channels[i]->name |= chan_name)
        return i;
  return -1;
}

void
CNetServer::irc_parse (void)
{
  size_t i = 0, i2 = 0;
  cmd_i = 0;
  memset (cmd, 0, sizeof (cmd));

  if (bufread[0] == ':')
    my_strncpy (bufread, bufread + 1, MSG_SIZE);

  size_t buflen = strlen (bufread);

  if (buflen != 0)
    while (bufread[i] != ':' && i < buflen && cmd_i < CMD_SIZE)
      {
        while (bufread[i] != ' ' && bufread[i] != '\n' && i != buflen)
          i++;
        my_strncpy (cmd[cmd_i], bufread + i2, i - i2);
        cmd_i++;
        i2 = ++i;
      }

  if (bufread[i] == ':')
    {
      i++;
      strncpy (cmd[cmd_i], bufread + i, strlen (bufread + i));
    }

}

// add a server to h_list, return this bot's server list (recursive function)
CNetServer::host_type *
CNetServer::host_add (host_type *h_list, c_char host, int port)
{
  if (h_list == NULL)					// if it's the first
    {
      h_list = new host_type (host, port);
      host_current = h_list;
    }
  else if (h_list->next == host_current)		// if it's the last
    h_list->next = new host_type (host, port, host_current);
  else
    host_add (h_list->next, host, port);		// try next one
  return h_list;
}

