#include <stdio.h>
#include <iodef.h>
#include <file.h>
#include <ssdef.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#ifdef __GNUC__
#else
#include <unixio.h>
#endif
#include "global.h"
#include "command.h"
#include "constants.h"
#include "dcc.h"
#include "window.h"
#include "defines.h"

extern int sys$clref(), lib$get_ef(), qio_status;
extern CmdList *find_command();

void erase_dcc_entry();

#define MAXSEND 512   /* Max number of bytes to send on a dcc send */
                      /* Must be smaller than buffer size */
 
char dcc_type_list[3][10] = {{"RECEIVE"}, {"SEND"}, {"CHAT"}};
char dcc_status_list[2][10] = {{"WAITING"}, {"ACTIVE"}};

extern unsigned long myaddress;
extern server_ptr gsrv;
extern void say();
int dcc_complete(connect_node *);
int dcc_infile_data(connect_node *), dcc_outfile_data(connect_node *),
    dcc_chat_data(connect_node *);

void dcc_userchat(char *string), dcc_userclose(char *string),
     dcc_userget(char *string), dcc_userlist(char *string),
     dcc_usersend(char *string);
  
/* Warning:  Before modifying the varpar portion of dcc_opt,  *
 *           look at its use in dcc_split                     */
 
CmdList dcc_opt[] =
{
 {""  ,      dcc_userlist, -1 },
 {"CHAT",    dcc_userchat,  2 },
 {"CLOSE",   dcc_userclose,-1 },
 {"GET",     dcc_userget,   0 },
 {"LIST",    dcc_userlist, -1 },
 {"SEND",    dcc_usersend,  1 },
};
 
#define DCC_COMMANDS sizeof(dcc_opt)/sizeof(CmdList) - 1
 
/* ----------------------------------------------------------------------- */
typedef struct _dcc_waiting dcc_waiting;
struct _dcc_waiting {
  short chan;
  SIN *sin;
  dcc_ptr data;
  dcc_waiting *next;
};
dcc_waiting *dcc_list = NULL;
dcc_waiting *logdcc_request();

/* ----------------------------------------------------------------------- */
void user_dcc(char *str)
{
  char *word;
  int cmd_cnt;
  CmdList *command;
 
  word = m_grab_word(&str, ' ');
  command = find_command(dcc_opt, DCC_COMMANDS, word, &cmd_cnt);
  
  if (cmd_cnt == 0)
    say("DCC option \"%s\" is unknown.", word?word:"(null)");
  else if (((cmd_cnt < 0) || (cmd_cnt == 1)) && command)
    command->cmdfcn(str);
  else
    say("DCC option \"%s\" is ambiguous.", word?word:"(null)");
  free(word);
}
 
/* ----------------------------------------------------------------------- */
/* Look up a dcc entry in the queue.                                       */
/* ----------------------------------------------------------------------- */
dcc_waiting *dcc_lookup_waiting(char *nick, char *file, int type)
{
  dcc_waiting *wait;
  for (wait=dcc_list; wait; wait = wait->next)
    if ((wild_match(nick, wait->data->from) >= 0) &&
        (wait->data->type == type) && (wild_match(file, wait->data->file) >= 0))
      return wait;
  return NULL;
}

/* ----------------------------------------------------------------------- */
extern connect_node *master_connect_list;
/* ----------------------------------------------------------------------- */
connect_node *dcc_lookup(char *nick, char *file, int type)
{
  dcc_ptr temp;
  connect_node *node;
  for (node = master_connect_list; node; node = node->next)
  {
    temp = (dcc_ptr)node->specific;
    if ((wild_match(nick, temp->from) >= 0) && (temp->type == type) &&
        (wild_match(file, temp->file) >= 0))
      return node;
  }
  return NULL;
}
 
/* ----------------------------------------------------------------------- */
short dcc_lookup_chat_chan(char *nick)
{
  connect_node *temp;
  temp = dcc_lookup(nick, "CHAT", DCC_CHAT);
  if (temp) return temp->iochan;
  return -1;
}
/* ----------------------------------------------------------------------- */
dcc_ptr remove_dcc_entry(dcc_waiting *temp, int do_free)
{
  dcc_waiting *n, *p;
  dcc_ptr ret;
  for (p=NULL, n = dcc_list; n && (n != temp); p = n, n = n->next);
  if (!n) return;
  if (p) p->next = n->next;
  else dcc_list = n->next;
  ret = (dcc_ptr)temp->data;
  free(temp);
  if (do_free) {
    free(ret);
    return NULL;
  }
  return ret;
}

/* ----------------------------------------------------------------------- */ 
short make_connection(dcc_waiting *temp)
{
  char msg[80];
  int status;
  short chan;
  SIN sin, *new_sin;
  dcc_ptr data;
  (void)memset(&sin, 0, sizeof(SIN));
  sin.sin_family = AF_INET;
  sin.sin_port = tcp_htons(temp->data->port);
  sin.sin_address = tcp_htonl(temp->data->hostid);
 
  chan = qio_socket_and_connect(&sin, sizeof(SIN));
  if (chan == -1) {
    get_socket_error(msg);
    say("*** connent int dcc_make_connection: %s", msg);
    return 0;
  }

  data = remove_dcc_entry(temp, FALSE);
  new_sin = (SIN *)malloc(sizeof(SIN));
  if (new_sin) memcpy(new_sin, &sin, sizeof(SIN));
  add_connect_to_list(chan, new_sin, (char *)data, CONNECT_DCC);
  if (data->type == DCC_CHAT)
    start_read(chan, chan, dcc_chat_data, dcc_complete);
  else if (data->type == DCC_INFILE)
    start_read(chan, chan, dcc_infile_data, dcc_complete);
  else say("*** Attemping to make a dcc connection to an unknown type");
  return 1;
}
 
/* ----------------------------------------------------------------------- */
int dcc_split(str, type, nick, file, close, altfile)
  char *str;
  int *type;
  char *nick, *file;
  int close;
  char *altfile;
{
  int cmd_cnt, count = 0;
  char word[MAXLEN];
  CmdList *command;
 
  if (close) {
    *type = 1;
    (void)grab_word(&str, ' ', word);
    command = find_command(dcc_opt, DCC_COMMANDS, word, &cmd_cnt);
    if (command) *type = command->varpar;
    if (*type >= 0) count++;
  }
  (void)grab_word(&str, ' ', nick);
  if (nick[0]) count++;
  (void)grab_word(&str, ' ', file);
  if (file[0]) count++;
  if (altfile) {
    (void)grab_word(&str, ' ', altfile);
    if (altfile[0]) count++;
  }
  return count;
}
 
/* ----------------------------------------------------------------------- */
int dcc_accept(short chan)
{
  SIN *sin;
  int num;
  unsigned short nchan;
  dcc_waiting *walk;
  dcc_ptr temp;
  char msg[80];

  for (walk = dcc_list; walk && (walk->chan != chan) ; walk = walk->next);
  if (!walk) {
    say("*** So sorry, we lost your dcc connection");
    return 0;
  }
  if (!(sin = (SIN *)malloc(sizeof(SIN)))) {
    say("*** Could not malloc for dcc accept");
    return 0;
  }
  
  add_connect_to_list(chan, sin, (char *)walk->data, CONNECT_DCC);
  nchan = qio_accept_after_wait(chan, chan, sin, sizeof(sin));
  remove_dcc_entry(walk, FALSE);
  temp = walk->data;
  if (temp->type == DCC_CHAT) {
    say("*** DCC CHAT connection to %s established.", temp->from);
    start_read(chan, nchan, dcc_chat_data, dcc_complete);
  } else {
    say("*** DCC connection to %s/%s established %s.", temp->from,
        temp->file, temp->stream?"(binary)":"(ascii)");     
    start_read(chan, nchan, dcc_outfile_data, dcc_complete);
    if (temp->stream)
      temp->output = open(temp->file, O_RDONLY, 0, "ctx=stm");
    else
      temp->output = open(temp->file, O_RDONLY, 0);
    if (temp->output==-1) {
      say("*** %s could not be opened.", temp->file);
      erase_dcc_entry(temp);
      return FALSE;
    }
    if ((num = read(temp->output, temp->savebuffer, MAXSEND)) == -1)
    {
      say("*** DCC read failed - aborting.");
      erase_dcc_entry(temp);
      return FALSE;
    }
    temp->transmit = num;
    if (tcp_send(nchan, temp->savebuffer, num) == -1) {
      get_socket_error(msg);
      say("*** send in accept: %s", msg);
      erase_dcc_entry(temp);
      return FALSE;
    }
  }
  return TRUE;
}
 
/* ----------------------------------------------------------------------- */
SIN *dcc_bind(short *chan)
{
  SIN *sin;
  char msg[80];
  sin = qio_bind_and_listen(chan, dcc_accept);
  if (sin) return sin;
  get_socket_error(msg);
  say("*** dcc bind and listen: %s", msg);
  return NULL;
}
 
/* ----------------------------------------------------------------------- */
void dcc_userchat(char *string)
{
  SIN sin, *new_sin;
  short chan;
  int type, ret;
  char file[MAXLEN], nick[MAXLEN], msg[MAXLEN];
  dcc_waiting *temp;
 
  ret = dcc_split(string, &type, nick, file, FALSE, NULL);
  if (ret<1) {
    say("*** Nickname required for DCC CHAT.");
    return;
  }
  temp = dcc_lookup_waiting(nick, "chat", DCC_CHAT);
  if (temp) {
    if ((temp->chan = make_connection(temp)) > 0)
      say("*** DCC chat connection started.");
    else {
      say("*** DCC link could not be established.");
      erase_dcc_entry(temp);
    }
  } else {
    if ((new_sin = dcc_bind(&chan)))
    {
      if (logdcc_request(0, 0, nick, "chat", DCC_CHAT, new_sin, chan))
        (void)new_send(gsrv, "PRIVMSG %s :\001DCC CHAT chat %u %u\001\n", nick, 
                       tcp_htonl(myaddress), tcp_htons(new_sin->sin_port));
      else say("*** Could not log dcc chat request");
    }
  }
}
 
/* ----------------------------------------------------------------------- */
void dcc_userclose(char *string)
{
  int type, ret;
  char file[MAXLEN], nick[MAXLEN];
  dcc_waiting *temp;
  connect_node *con;

  ret = dcc_split(string, &type, nick, file, TRUE, NULL);
  if ((ret < 2))
    say("*** You must specify a type and nick for DCC CLOSE");
  else if (ret == 2) (void)strcpy(file, "*");
  temp = dcc_lookup_waiting(nick, file, type);
  if (temp) {
    erase_dcc_entry(temp);
    say("*** DCC entry deleted.");
  } else {
    con = dcc_lookup(nick, file, type);
    if (con) tcp_shutdown(con->iochan);
    else say("*** No matching entries for DCC close.");
  }
}
 
/* ----------------------------------------------------------------------- */
void dcc_userlist(char *string)
{
  dcc_ptr b;
  connect_node *w;
  dcc_waiting *temp;
  int off, loop;
 
  temp = dcc_list;
  say("Type    Nick      Status     Transmit  Received  Port  Arguments");
  while (temp!=NULL)
  {
    say("%-7s %-9s %-10s %-9d %-9d %-5d %-25s", dcc_type_list[temp->data->type],
        temp->data->from, "WAITING", 0, 0,
        temp->sin?tcp_htons(temp->sin->sin_port):-1, temp->data->file);
    temp = temp->next;
  }
  for (w = master_connect_list; w; w = w->next)
    if (w->type == CONNECT_DCC)
    {
      b = (dcc_ptr)w->specific;
      say("%-7s %-9s %-10s %-9d %-9d %-5d %-25s", dcc_type_list[b->type],
          b->from, "ACTIVE", b->transmit, b->received,
          w->sin?tcp_htons(w->sin->sin_port):-1, b->file);
    }
  say("*** End DCC listing.");
}
 
/* ----------------------------------------------------------------------- */
void erase_dcc_entry(dcc_waiting *entry)
{
  dcc_ptr front, back = NULL;
  front = remove_dcc_entry(entry, FALSE);
  if (front)
    free(front);
  else say("*** should be deleteing from active connection");
}
 
/* ----------------------------------------------------------------------- */
void dcc_usersend(char *string)
{
  int dummy, num;
  unsigned long host_id;
  short chan;
  dcc_waiting *temp;
  struct stat stat_buff;
  SIN *new_sin;

  char nick[MAXLEN], file[MAXLEN], msg[MAXLEN], tempfile[MAXLEN], *where;
 
  num = dcc_split(string, &dummy, nick, file, FALSE, NULL);
  if (num<2) {
    say("*** Nick and file required for DCC SEND.");
    return;
  }
  if (where = strstr(file, "]")) strcpy(tempfile, ++where);
  else strcpy(tempfile, file);
  if (stat(file, &stat_buff)==-1) {
    say("*** Could not access that file.");
    return;
  }
  if ((new_sin = dcc_bind(&chan)))
  {
    if ((temp = logdcc_request(0, 0, nick, file, DCC_OUTFILE, new_sin, chan)))
    {
      if (stat_buff.st_fab_rat == 2) temp->data->stream = 0;
      (void)new_send(gsrv, "PRIVMSG %s :\001DCC SEND %s %u %u\001\n", nick,
                     tempfile, tcp_htonl(myaddress),
                     tcp_htons(new_sin->sin_port));
    } else say("*** Could not logdcc request");
  }
}
 
/* ----------------------------------------------------------------------- */
/* When the user tries to get a file, this routine is called.              */
/* ----------------------------------------------------------------------- */
extern int errno;
void dcc_userget(char *string)
{
  char nick[MAXLEN], file[MAXLEN], msg[MAXLEN], altfile[MAXLEN], *w;
  dcc_waiting *temp;
  int num, type, c;
  SIN sin;

  num = dcc_split(string, &type, nick, file, FALSE, altfile);
  if (num<1) {
    say("*** Nickname required for DCC GET.");
    return;
  } else if (num==1) (void)strcpy(file, "*");
  temp = dcc_lookup_waiting(nick, file, DCC_INFILE);
  if (temp) {
    if (num<=2) strcpy(altfile, temp->data->file);
    temp->data->stream = -1;
    for (w = altfile, c = 0; *w; w++)
    {
      if (*w=='.') { c++; if (c>1) *w = '_'; }
      if (*w=='/') *w = '$';
    }
    strcpy(temp->data->file, altfile);
    if ((temp->chan = make_connection(temp)) > 0)
      say("*** DCC connection started.");
    else {
      say("*** DCC link could not be established.");
      erase_dcc_entry(temp);
    }
  } else
    say("*** No file (%s) offerred in SEND mode by %s", file, nick);
}
 
/* ----------------------------------------------------------------------- */
int dcc_complete(connect_node *node)
{
  dcc_ptr temp;
  char msg[MAXLEN];
 
  temp = (dcc_ptr)node->specific;
  if (temp->type == DCC_INFILE)
    (void)sprintf(msg, "*** DCC SEND/%s from %s completed. (%u bytes)",
            temp->file, temp->from, temp->received);
  else if (temp->type == DCC_OUTFILE)
    (void)sprintf(msg, "*** DCC SEND/%s to %s completed. (%u bytes)",
            temp->file, temp->from, temp->transmit);
  else if (temp->type == DCC_CHAT)
    (void)sprintf(msg, "*** DCC CHAT to %s completed.", temp->from);
  say("%s", msg);
  (void)close(temp->output);
  return 0;
}
 
/* ----------------------------------------------------------------------- */
int dcc_bad_complete(connect_node *node, char *str)
{
  char msg[80];
  get_socket_error(msg);
  say("%s: %s", str, msg);
  dcc_complete(node);
  return 0;
}

/* ----------------------------------------------------------------------- */
extern int errno;
int dcc_infile_data(connect_node *node)
{
  char msg[80];
  dcc_ptr temp;
  int size, status, loop;
  temp = (dcc_ptr)node->specific;
  if (!temp) return dcc_bad_complete(node, "lost connection data");
  if (temp->stream == -1)
  {
    temp->stream = 0;
    for (loop=0; loop < node->iosb.length; loop++)
      if (node->buffer[loop] <= 0) {
        temp->stream = 1;
	loop = node->iosb.length;
      }
    if (temp->stream)
      temp->output = open(temp->file, O_WRONLY|O_CREAT|O_TRUNC, 0, "rfm=fix",
                          "mrs=512", "ctx=stm");
    else
      temp->output = open(temp->file, O_WRONLY|O_CREAT|O_TRUNC, 0, "rfm=var",
                          "rat=cr");
    if (temp->output==-1) {
      sprintf(msg, "%s: %s(%d)(stream: %d)", temp->file, strerror(errno),
              errno, temp->stream);
      return dcc_bad_complete(node, msg);
    }
  }
  temp->received += node->iosb.length;
  (void)write(temp->output, node->buffer, node->iosb.length);
  size = tcp_htonl(temp->received);
  if ((status = tcp_send(node->iochan, (char *)&size, sizeof(size))) == -1)
    return dcc_bad_complete(node, "getdata1 status");
  return 1;
}

/* ----------------------------------------------------------------------- */
int dcc_outfile_data(connect_node *node)
{
  dcc_ptr temp;
  int size, status, cl;
  char read_buff[MAXSEND];
  temp = (dcc_ptr)node->specific;
  if (!temp) return dcc_bad_complete(node, "lost connection data");
    (void)memcpy(&size, node->buffer, node->iosb.length);
  if (size == tcp_htonl(temp->transmit))
  {
    cl = 0;
    *node->buffer = 0;
    do {
      size = read(temp->output, read_buff, MAXSEND);
      cl += size;
      strncat(node->buffer, read_buff, size);
      node->buffer[cl] = 0;
    } while (size && (cl < 300));
    if (cl>0) {
      temp->transmit += cl;
      if ((status = tcp_send(node->iochan, node->buffer, cl)) == -1)
        return dcc_bad_complete(node, "getdata2 status");
    } else return dcc_complete(node);
  } else return dcc_bad_complete(node, "*** size mismatch");
  return 1;
}

/* ----------------------------------------------------------------------- */
extern win_ptr awins;
int dcc_chat_data(connect_node *node)
{
  char *dummy, *sline, line[MAXLEN], *walk;
  int old_loglevel, len;
  dcc_ptr temp;

  temp = (dcc_ptr)node->specific;
  if (!temp) return dcc_bad_complete(node, "lost connection data");
  temp->received += node->iosb.length;
  (void)strcat(temp->savebuffer, node->buffer);
  dummy = temp->savebuffer;
  do {
    sline = line;
    for (walk=dummy, len = 0; (*walk) && (*walk!='\012'); len++)
    *sline++ = *walk++;
    if (*walk=='\012') {
      dummy = ++walk;
      *sline = 0;
      if (line[len-1] == 13) line[len-1] = 0;
      if (*line) {
        win_ptr oldwin = NW, index;
        for (index=awins; index; index = index->next)
           if ((index->server == gsrv) &&
              (strcasecmp(&index->query[1], temp->from) == 0))
            oldwin = set_outwin(index);
        old_loglevel = set_loglevel(DCC);
        say("=%s= %s", temp->from, line);
        set_loglevel(old_loglevel);
        if (oldwin) set_outwin(oldwin);
      }
    } else len = -1;
  } while (len!=-1);
  (void)strcpy(temp->savebuffer, dummy);
  return 1;
}

/* ----------------------------------------------------------------------- */
char *hostid_to_string(unsigned hostid, char *str)
{
  int a[4], loop;
  for (loop=3; loop>=0; loop--) {
     a[loop] = hostid & 0xff;
     hostid = hostid/256;
  }
  sprintf(str, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]);
  return str;
}

/* ----------------------------------------------------------------------- */
/* Log an entry into the dcc queue so the user can get retrieve it later   */
/* ----------------------------------------------------------------------- */
 
dcc_waiting *logdcc_request(hostid, port, from, file, type, sin, chan)
  unsigned long hostid;
  unsigned short port;
  char *from, *file;
  int type;
  SIN *sin;
  short chan;
{
  unsigned status;
  char msg[100];
  dcc_ptr temp;
  char *start, dummy[MAXLEN];
  dcc_waiting *wtemp = NULL;

  if ((temp=(dcc_ptr)malloc(sizeof(struct dcc_node))) == NULL)
    return NULL;

  (void)strcpy(temp->from, from);
  if ((start = strrchr(file, '/')) == NULL) start = file;
  else start++;
  (void)strcpy(temp->file, start);
  temp->type = type;
  temp->output = 0;
  temp->stream = 1;
  temp->hostid = hostid;
  temp->port = port;
  temp->transmit = temp->received = 0;
  temp->savebuffer[0] = 0;

  wtemp = (dcc_waiting *)malloc(sizeof(dcc_waiting));
  if (!wtemp) {
    free(temp);
    return NULL;
  }
  wtemp->next = dcc_list;
  dcc_list = wtemp;
  wtemp->chan = chan;
  wtemp->sin = sin;
  if (!sin) {
    wtemp->sin = (SIN *)malloc(sizeof(SIN));
    if (wtemp->sin) {
      memset(wtemp->sin, 0, sizeof(SIN));
      wtemp->sin->sin_family = AF_INET;
      wtemp->sin->sin_port = tcp_htons(port);
      wtemp->sin->sin_address = hostid;
    }
  }
  wtemp->data = temp;

  if (temp->type == DCC_INFILE)
    (void)sprintf(msg, "*** DCC SEND request from %s for %s on %s[%d] queued.",
                   from, file, hostid_to_string(hostid, dummy), port);
  else if (temp->type == DCC_OUTFILE)
    (void)sprintf(msg, "*** DCC SEND request to %s for %s queued.", from, file);
  else if (temp->type == DCC_CHAT)
    (void)sprintf(msg, "*** DCC CHAT request from %s for %s queued.", from, file);
  say("%s", msg);
  return wtemp;
}
