#include "CNetDCC.h"

CNetDCC::CNetDCC (CNetServer *server, c_char dcc_mask, int dcc_index, int dcc_from) :
  CNet (server->bot), mask (dcc_mask, MASK_SIZE), filename (PATH_MAX)
{
  s = server;
  mask2nick (mask, nick, NICK_SIZE);
  index = dcc_index;
  status = DCC_STOP;
  last_time = server->time_now;
  socklocal = sockremote = -1;
  dfd = NULL;
  filesent = 0;
  dcc_from_index = dcc_from;
  if (s->bot->debug)
    cout << "New DCC #" << index << " from " << mask << endl;
}

CNetDCC::~CNetDCC (void)
{
  dcc_stop ();
}

// stop the dcc and show the error to the nick
void
CNetDCC::dcc_error (c_char error)
{
  dcc_stop ();
  s->irc_privmsg (nick, "%s", error);
} 

// open the socket, return 0 on error
bool
CNetDCC::dcc_chat_start (void)
{
  if (!bindsock (s->dcc_port))
    {
      s->irc_privmsg (nick, "Error in DCC Chat: can't bind socket, try again later.");
      return 0;
    }
  socklocal = sock;
  status = DCC_CHAT_INIT;
  last_time = s->time_now;
  return 1;
}

// open socket and file, return 0 if there's an error with any of them
bool
CNetDCC::dcc_send_start (c_char file)
{
  static char errorfile[] = "Error in DCC Send: can't open file.";
  static char errorbind[] = "Error in DCC Send: can't bind socket.";
  filename = file;
  dfd = fopen (filename, "r");
  if (dfd == NULL)
    {
      if (dcc_from_index == -1)
        s->irc_privmsg (nick, "%s", errorfile);
      else
        s->dccs[dcc_from_index]->dcc_chat_write (errorfile);
      return 0;
    }
  if (!bindsock (s->dcc_port))
    {
      fclose (dfd);
      dfd = NULL;
      if (dcc_from_index == -1)
        s->irc_privmsg (nick, "%s", errorbind);
      else
        s->dccs[dcc_from_index]->dcc_chat_write (errorbind);
      return 0;
    }
  socklocal = sock;
  filesize = lseek (fileno (dfd), 0, SEEK_END);
  fseek (dfd, 0, SEEK_SET);
  status = DCC_SEND_INIT;
  last_time = s->time_now;
  return 1;
}

// check when it can start sendind or chating
void
CNetDCC::dcc_start_check (void)
{
  int i = readok (socklocal);
  if (i == -1)						// in case of error
    dcc_error ("Error in starting DCC.");
  if (i) 
    {
      socklen_t size = sizeof (struct sockaddr_in);
      struct sockaddr_in remote;
      sockremote = accept (socklocal, (struct sockaddr *)&remote, &size);
      close (socklocal);
      socklocal = -1;

      sock_linger (sockremote, 5*100);
      fcntl (sockremote, F_SETFL, O_NONBLOCK);

      if (status == DCC_CHAT_INIT)	// if it's a dcc chat, ask for password
        {
          dcc_chat_write ("Enter your password.");
          status = DCC_CHAT_AUTH;
        }
      else
        status = DCC_SEND;

      last_time = s->time_now;
    }
  if (difftime(s->time_now, last_time) > 50)	// timeout in 50 seconds
    dcc_error ("Timeout waiting for connection.");
}

void
CNetDCC::dcc_chat_write (c_char msg)
{
  if (status != DCC_STOP)			// sometimes it happens
    {
      if (writeok (sockremote) == -1)
        dcc_error ("Error during DCC Chat.");
      else
        {
          if (s->bot->debug)
            printf ("dccwrite: %s (%d): %s\n", (char *)s->nick, index, msg);
          write (sockremote, msg, strlen (msg));
          write (sockremote, "\r\n", 2);
        }
    }
}

void
CNetDCC::dcc_chat_pass (void)
{
  int i = readok (sockremote);
  if (i == -1)
    dcc_error ("Error during DCC Chat.");
  if (i)
    {
      if (readsock (sockremote, buf, MSG_SIZE) == -1)
        dcc_stop ();		// probably the client closed the dcc
      else
        {
          strip_crlf (buf);
          if (s->bot->debug)
            printf ("dccread: %s (%d): %s\n", (char *)s->nick, index, buf);

          // check user's password
          if (!s->users->match_check_pass (mask, buf))
            {
              dcc_chat_write ("Password incorrect.");
              dcc_stop ();
              return;
            }

          // activate user's id
          s->users->match_set_id (mask, 1);
          s->irc_watch (nick, 1);

          // show motd, if one is defined
          if (s->dcc_motd != NULL)
            {
              s->dcc_motd->rewind_text ();
              c_char bufline;
              while ((bufline = s->dcc_motd->get_line ()) != NULL)
                dcc_chat_write (bufline);
            } 
          // otherwise just say this
          else
            dcc_chat_write ("Password correct.");

          // tell the others on the partyline
          snprintf (buf, BUF_SIZE, "%s joined the partyline.", nick);
          s->script.send_partyline (buf);

          // dcc chat mode
          status = DCC_CHAT;
          last_time = s->time_now;
        }
    }
  if (difftime (s->time_now, last_time) > 60)	// timeout in one minute
    {
      dcc_chat_write ("Closing idle DCC Chat.");
      dcc_stop ();
    }
}

void
CNetDCC::dcc_chat (void)
{
  int i = readok (sockremote);
  if (i == -1)
    dcc_error ("Error during DCC Chat.");
  if (i)
    {
      if (readsock (sockremote, buf, MSG_SIZE) == -1)
        dcc_stop ();			// probably the client closed the dcc
      else
        {
          if (s->bot->debug)
            printf ("dccread: %s (%d): %s", (char *)s->nick, index, buf);

          char buf2[20];		// should do to keep dcc chat's index
          snprintf (s->bufread, MSG_SIZE, ":%s PRIVMSG %s :%s", (char *)mask,
                    itoa (index, buf2, 19), buf);
          strip_crlf (s->bufread);

          // fake a privmsg
          s->irc_parse ();
          s->script.irc_event ();

          last_time = s->time_now;
        }
    }
  if (difftime (s->time_now, last_time) > 600)	// timeout in ten minutes
    {
      dcc_chat_write ("Closing idle DCC Chat.");
      dcc_stop ();
    }
}

void
CNetDCC::dcc_send (void)
{
  int i = writeok (sockremote);
  if (i == -1)
    dcc_error ("Error during DCC Send.");
  if (i)
    {
      i = fread (dcc_buf, 1, DCC_BUF_SIZE, dfd);

      if (readok(sockremote) != 0)			// if it's 1 or -1
        if (read (sockremote, buf, sizeof(buf)) == -1)
          {
            dcc_stop ();		// probably the client closed the dcc
            return;
          }

      if (write (sockremote, dcc_buf, i) != -1)
        {
          filesent += i;
          if (filesent == filesize)
            dcc_stop ();
          last_time = s->time_now;
        }
      else
        dcc_error ("Error during DCC Send.");
    }
  if (difftime (s->time_now, last_time) > 30)  // timeout in 30 seconds
    dcc_error ("Timeout in DCC Send.");
}

// close sockets and file, if open
void
CNetDCC::dcc_stop (void)
{
  if (status == DCC_CHAT)			// if it was a dcc chat
    {
      snprintf (buf, BUF_SIZE, "%s left the partyline.", nick);
      status = DCC_STOP; // must be changed right now to avoid a possible loop
      s->script.send_partyline (buf);		// tell the others
    }
  if (socklocal != -1)
    {
      close (socklocal);
      socklocal = -1;
    }
  if (sockremote != -1)
    {
      close (sockremote);
      sockremote = -1;
    }
  if (dfd != NULL)
    {
      fclose (dfd);
      dfd = NULL;
    }
  filesent = 0;
  status = DCC_STOP;
}

// manage the dcc, through its status
bool
CNetDCC::dcc_work (void)
{
  switch (status)
    {
      case DCC_STOP:
        return 0;
      case DCC_SEND_INIT:
        dcc_start_check ();
        break;
      case DCC_SEND:
        dcc_send ();
        break;
      case DCC_CHAT_INIT:
        dcc_start_check ();
        break;
      case DCC_CHAT_AUTH:
        dcc_chat_pass ();
        break;
      case DCC_CHAT:
        dcc_chat ();
        break;
      default:			// should never happen
        status=DCC_STOP;
    }
  return 1;
}

// build the ctcp dcc chat
char *
CNetDCC::dcc_make_ctcp_chat (char *ctcp_buf, size_t max)
{
  char portbuf[7];
  char addrbuf[12];

  snprintf (ctcp_buf, max, "DCC CHAT chat %s %s",
            u_itoa (sock2addr (s->sock), addrbuf),
            u_itoa (sock2port (socklocal), portbuf));

  return ctcp_buf;
}

// build the ctcp dcc send
char *
CNetDCC::dcc_make_ctcp_send (char *ctcp_buf, size_t max)
{
  char portbuf[50], addrbuf[50];

  snprintf (ctcp_buf, max, "DCC SEND %s %s %s %s",
            fullpath2file (filename),
            u_itoa (sock2addr (s->sock), addrbuf),
            u_itoa (sock2port (socklocal), portbuf),
            u_itoa (filesize, buf));

  return ctcp_buf;
}

