/*
 * request.c
 * (C) Peter Salanki 2002
 * This program is copyright, and covered by the Gnu Public License.
 * The Natasha bot.
 * sorcer@linux.se
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <time.h>
#include "../../settings.h"
#include "../../globals.h"
#include "../../bottypes.h"
#include "settings.h"

#define NAME "Request"
#define VERSION 1.00

#define END_LINE 0x0A   /* \n */

void parseline(char *line);
void onchan(struct arm *a);
void response(int code);
void handlerequest(char *chan, char *nick, char *arm);
void handleaccept(char *chan, char *nick, char *arm, int uid);
void handledecline(char *chan, char *nick, char *arm, int uid);
void spewchan(void);

int read_line(char *line_to_return);
void *listener(void* ptr);
int checksip(struct sockaddr_in *cliAddr);
int putsocket(char *text, int fd);
void cleanup(void);

MODULE_INIT _module_init(MODULE m);
MODULE_DESTROY _module_destroy(MODULE m);

int nextspew;
char glarm[NICKLEN];
char glrequester[NICKLEN];
int linkfd = 0;
int listenfd = 0;
BOOL destroylistener = 0;
pthread_t listenerthread;

MODULE_INIT _module_init(MODULE m) {
  strncpy(m->name, NAME, 20);
  strncpy(m->compiledate, __DATE__ " " __TIME__, 30);
  m->version = VERSION;

  if(findarmbytype(BOT_CHECK) == NULL) {
    printf("Error: There is no checker service added to database. The Request module loading is aborted.\n");
    return;
  }

  cleanup();

  /* Create listener thread */
  if(pthread_create(&listenerthread, NULL, listener, NULL)) printf("Error creating thread for request listener.");
} 

MODULE_DESTROY _module_destroy(MODULE m) {
  destroylistener = 1;
  close(linkfd);
  close(listenfd);

  /* Join listener thread */
  if(pthread_join(listenerthread, NULL)) printf("Error joining thread for request listener.");
}

void cleanup (void) {
  /* This function will clean up all channels that the check service may have left in the channels db */
  char query[MAX_QUERY];

  snprintf(query, MAX_QUERY, "DELETE FROM `channels` WHERE `arm` = '%i'", findarmbytype(BOT_CHECK)->id);
  dbquery(query);
  pthread_mutex_unlock(&mysqlmutex); // UnLock mutex
}
 
/* Network functions */
int putsocket (char *text, int fd)
{
  int l, j;
 
  l = strlen (text);
  
  j = write (fd, text, l);
#ifdef DEBUG
  write (1, text, l);
#endif

  if (j != l)
    {
      perror ("write:");
      return 0;
    }
  return 1;
}

void *listener(void* ptr) {
  int cliLen;
  struct sockaddr_in cliAddr, servAddr;
  char line[MAX_NETDATA];


  /* create the socket */
  listenfd = socket(AF_INET, SOCK_STREAM, 0);
  if(listenfd<0) {
    perror("Error: listener cannot open socket ");
    return NULL;
  }

  /* bind our socket to server port */
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servAddr.sin_port = htons(REQUEST_PORT);

  if(bind(listenfd, (struct sockaddr *) &servAddr, sizeof(servAddr))<0) {
    perror("Error: request-listener cannot bind port ");
    return NULL;
  }

  listen(listenfd, 5);

  printf("Request is waiting for connections on %u\n", REQUEST_PORT);

  while(1) {
 
    cliLen = sizeof(cliAddr);
    linkfd = accept(listenfd, (struct sockaddr *) &cliAddr, &cliLen);
    if(checksip(&cliAddr)) {

      if(linkfd < 0) { /* an error occured */
	perror("Error: listener cannot accept connection ");
	return NULL;
      }

      /* init line */
      memset(line,0x0,MAX_NETDATA);

      /* receive segments */
      while(read_line(line)!=1) {

#ifdef DEBUG
	printf("Request-Received: %s\n", line);
#endif

	parseline(line); /* Parse the data */ 

	memset(line,0x0,MAX_NETDATA);	/* init line */
      
      }
    } else {
      putsocket("You are not allowed to connect from this host.\n", linkfd);
      close(linkfd);
      sprintf(line, "%c4Warning:%c Request-Unauthorized connection from: %c%s%c", COLORS, COLORS, BOLD, inet_ntoa(cliAddr.sin_addr), BOLD);
      privmsg(HOMECHAN, line);
    }
    if(destroylistener) return NULL;
  }

}

int read_line(char *line_to_return) {

  static int rcv_ptr=0;
  static char rcv_msg[MAX_NETDATA];
  static int n;
  int offset;

 offset=0;

 while(1) {
  if(rcv_ptr==0) {
   /* read data from socket */
   memset(rcv_msg,0x0,MAX_NETDATA); /* init buffer */
   n = recv(linkfd, rcv_msg, MAX_NETDATA, 0); /* wait for data */
   if (n<0) {     /* error! */
    return 1;
   } else if (n==0) { /* the connection was closed */
    close(linkfd);
    return 1;
   }
  }

  /* if new data read on socket */
  /* OR */
  /* if another line is still in buffer */

  /* copy line into 'line_to_return' */
  while(*(rcv_msg+rcv_ptr)!=END_LINE && rcv_ptr<n) {
   memcpy(line_to_return+offset,rcv_msg+rcv_ptr,1);
   offset++;
   rcv_ptr++;
  }

  /* end of line + end of buffer => return line */
  if(rcv_ptr==n-1) {
   /* set last byte to END_LINE */
   *(line_to_return+offset)=END_LINE;
   rcv_ptr=0;
   return ++offset;
  }

  /* end of line but still some data in buffer => return line */
  if(rcv_ptr <n-1) {
   /* set last byte to END_LINE */
   *(line_to_return+offset)=END_LINE;
   rcv_ptr++;
   return ++offset;
  }

  /* end of buffer but line is not ended => */
  /*  wait for more data to arrive on socket */
  if(rcv_ptr == n) {
   rcv_ptr = 0;
  }

 } /* end while */
}

int checksip(struct sockaddr_in *cliAddr) {
  return 1;
}

/* Request functions */
void parseline(char *line) {  
  char chan[CHANNELLEN];
  char nick[NICKLEN];
  char arm[NICKLEN];
  char command[30];
  int uid;
  
  sscanf(line, "%s", command);
  
  if(strcmp(command, "REQUEST") == 0) {
    sscanf(line, "%s %s %s %s", command, chan, nick, arm);
    handlerequest(chan, nick, arm);
    return;
  } else if(strcmp(command, "HANDLE") == 0) {
    sscanf(line, "%s %s %s %s %i", command, chan, nick, arm, &uid);
    handleaccept(chan, nick, arm, uid);
    return;
  } else if(strcmp(command, "DECLINE") == 0) {
    sscanf(line, "%s %s %s %s %i", command, chan, nick, arm, &uid);
    handledecline(chan, nick, arm, uid);
    return;
  }
 
}

void handlerequest(char *chan, char *nick, char *arm) {
  // Return codes: 0 = banned 1 = Not on chan/no op 2 = No L, placed in queue 3 = Auto accepted 4 = Not auth'd 5 = No such user 6 = Internal error 7 = Not requestok
  
  printf("Handleing request for %s to %s nick: %s\n", arm, chan, nick);

  if(findarmbytype(40)->status == 0 || findchannel(chan) != NULL || strcmp(chan, HOMECHAN) == 0) {
    printf("Warning: Internal error in request.\n");
    response(6);
    return;
  }

  if(findauser(nick) != NULL && userlevel(nick) == 0) {
    add(nick);
    response(4);
    return;
  } else if(findauser(nick) == NULL) {
    add(nick);
    response(5);
    return;
  } else if(findauser(nick)->requestok == 0) {
    response(7);
    return;
  }

  findauser(nick)->requestok = 0;

  strncpy(glarm, arm, NICKLEN);
  strncpy(glrequester, nick, NICKLEN);

  c_join(chan, findarmbytype(40));
  
}

void spewchan(void) {
  int i;
  char queuen, queuea[4];
  char msg[512];

  if(time(NULL) >= nextspew) {
    dbquery("SELECT count(*) FROM `request` WHERE status = '0'");
    row = mysql_fetch_row(res);
    i = atoi(row[0]);
    pthread_mutex_unlock(&mysqlmutex); // UnLock mutex

    if(i > 1 || i == 0) {
      strcpy(queuea, "are");
      queuen = 's';
    } else {
      strcpy(queuea, "is");
      queuen = ' ';
    }

    snprintf(msg, 512, "There %s %c%i%c item%c in queue.", queuea, BOLD, i, BOLD, queuen);
    privmsg(PUBCHAN, msg);

    nextspew = time(NULL) + QUEUEREPEAT;
  }
}

void response(int code) {
  char msg[MAX_NETDATA];

  sprintf(msg, "%i\n", code);
  putsocket(msg, linkfd);
}

void onchan(struct arm *a) {
#ifdef AUTOACCEPT
  char msg[512];
#endif
  char query[MAX_QUERY];
  char *channel;
  char service = ' ';
  int tmp;
  struct activechanuser *acu;
  struct activeuser *mu;
  struct channel *c;

  // SYNTAX: chan nick bot socket
  // Return codes: 0 = banned 1 = Not on chan/no op 2 = No L, placed in queue 3 = Auto accepted 4 = Not auth'd 5 = No such user

  channel = a->parsevars->args[1];
  
  c = findchannel(channel);
  mu = findauser(glrequester);
  
  acu = firstacu;

  tmp = -1;

  while (acu != NULL)
    {
      if(acu->channel == c) ++tmp;
      acu = acu->next;
    } 
 
#ifdef Q
#ifdef L
  if(findachanuser(L_NICK, channel) != NULL) service = 'L';
#endif
  if(findachanuser(Q_NICK, channel) != NULL) service = 'Q';
#endif

  acu = findachanuser(glrequester, channel);
  if(acu == NULL || acu->op == 0) {
    response(1);
    snprintf(query, MAX_QUERY, "INSERT INTO `request` (`channel`, `users`, `topic`, `uid`, `nick`, `time`, `type`, `status`, `huid`, `reason`, `htime`, `bot`) VALUES ('%s', '%i', '%s', '%i', '%s', UNIX_TIMESTAMP(), '0', '2', '0', 'Requester doesen\\'t have op', UNIX_TIMESTAMP(), '%s')", channel, tmp, escapequery(c->topic), mu->userid, escapequery(mu->nick), glarm);
    dbquery(query);
    pthread_mutex_unlock(&mysqlmutex); // UnLock mutex
    c_part(channel, "Request declined - Requester is not on channel/is not op");
    return;
  }

#ifdef AUTOACCEPT
  if(findachanuser(L_NICK, channel) == NULL && findachanuser(Q_NICK, channel) == NULL) {
#endif
    // Put in queue
    response(2);
    snprintf(query, MAX_QUERY, "INSERT INTO `request` (`channel`, `users`, `topic`, `uid`, `nick`, `time`, `type`, `status`, `service`, `bot`) VALUES ('%s', '%i', '%s', '%i', '%s', UNIX_TIMESTAMP(), '0', '0', '%c', '%s')", channel, tmp, escapequery(c->topic), mu->userid, escapequery(mu->nick), service, glarm);
    dbquery(query);
    pthread_mutex_unlock(&mysqlmutex); // UnLock mutex
    c_part(channel, "Request placed in queue");

    snprintf(query, MAX_QUERY, "There is a new request in queue for channel: %c%s%c", BOLD, channel, BOLD);
    privmsg(HOMECHAN, query);

    return;
#ifdef AUTOACCEPT
  }

  response(3);
  c_part(channel, "Request accepted");

  snprintf(query, MAX_QUERY, "INSERT INTO `request` (`channel`, `users`, `topic`, `uid`, `nick`, `time`, `type`, `status`, `huid`, `htime`, `service`, `bot`) VALUES ('%s', '%i', '%s', '%i', '%s', UNIX_TIMESTAMP(), '0', '1', '0', UNIX_TIMESTAMP(), '%c', '%s')", channel, tmp, escapequery(c->topic), mu->userid, escapequery(mu->nick), service, glarm);
  dbquery(query);
  pthread_mutex_unlock(&mysqlmutex); // UnLock mutex

  sprintf(msg, "Auto accept: %c%s%c requested by: %c%s%c", BOLD, channel, BOLD, BOLD, glrequester, BOLD); 
  privmsg(HOMECHAN, msg);

  // join arm and that crap
  handleaccept(channel, glrequester, glarm, mu->userid);
  return;
#endif
}

void handleaccept(char *chan, char *nick, char *arm, int uid) {
  char msg[512];
  
  c_join(chan, findarm(arm));
  c_setlev(uid, chan, 5);

  snprintf(msg, MAX_QUERY, "Request is accepted. If you have any channel service, you need to set me to +ao. I.e /msg L chanlev %s %s +ao  More information and manuals can be found at: %c%s%c  The channel owner is: %c%s%c.", chan, arm, BOLD, HELPSITE, BOLD, BOLD, nick, BOLD);
  privmsg(chan, msg);
}

void handledecline(char *chan, char *nick, char *arm, int uid) {
  char msg[512];

  snprintf(msg, 512, "Your request for %c%s%c has been declined. Go to our homepage or %s for more information.", BOLD, chan, BOLD, PUBCHAN);
  privmsg(nick, msg);
}
