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

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <dlfcn.h>
#include <pthread.h>

#include "globals.h"
#include "bottypes.h"
#include "irc_chattr.h"

int userlevel (char *username)
{
  struct activeuser *u;
  u = findauser(username);
  if (u != NULL)
    {
      return (u->auth);
    }
  else
    {
      return 0;
    }
}

int chanuserlevel (char *username, char *channel)
{
  struct activeuser *u;
  struct channel *c;
  struct chanuser *cu;

  u = findauser(username);
  c = findchannel(channel);

#ifdef Q
  if(strcasecmp(username, Q_NICK) == 0) return 5;
#ifdef L
  if(strcasecmp(username, L_NICK) == 0) return 5;
#endif
#endif

  if (u != NULL && u->auth >= NORMAL)
    {
      if (c != NULL) {
	cu = findchanuser(u->userid, c->id);
	if(cu != NULL) return (cu->level);
	else return 0;
      } else return 0;
    } else {
      return 0;
    }
}

int chanusergreet (char *username, char *channel, char *ret)
{
  struct activeuser *u;
  struct channel *c;
  struct chanuser *cu;

  u = findauser(username);
  c = findchannel(channel);

  if (u != NULL && u->auth >= NORMAL)
    {
      if (c != NULL) {
	cu = findchanuser(u->userid, c->id);
	if(cu != NULL && strcmp(cu->greet, "") != 0) {
	  strncpy(ret, cu->greet, 512);
	  return 1;
	}
	else return 0;
      } else return 0;
    } else {
      return 0;
    }
}

char *authtotext(int auth) {

  char *text = malloc(18);

  if(auth == UNAUTHED) strcpy(text, "Not logged in");
  else if(auth == NORMAL) strcpy(text, "Normal user");
  else if(auth == SERVICE_FRIEND) strcpy(text, "Service friend");
  else if(auth == HELPER) strcpy(text, "Helper");
  else if(auth == QUEUE_WORKER) strcpy(text, "Queue worker");
  else if(auth == TECHNICIAN) strcpy(text, "Technician");
  else if(auth == MASTER_TECHNICIAN) strcpy(text, "Master Technician");
  else if(auth == IRC_OPERATOR) strcpy(text, "IRC Operator");
  else if(auth == BOT_NORMAL) strcpy(text, "Bot");
  else if(auth == BOT_PRIVATE) strcpy(text, "Private Bot");
  else if(auth == BOT_CHECK) strcpy(text, "Checker Service");
  else strcpy(text, "Undefined");
  
  return text;
}

void chanlevtotext(int level, char *text) {
  
  if(level == CHAN_NOACCESS) strcpy(text, "No access");
  else if(level == CHAN_FRIEND) strcpy(text, "Friend");
  else if(level == CHAN_VOICE) strcpy(text, "Voice");
  else if(level == CHAN_OP) strcpy(text, "Operator");
  else if(level == CHAN_MASTER) strcpy(text, "Master");
  else if(level == CHAN_OWNER) strcpy(text, "Owner");
  else strcpy(text, "Undefined");
}

int texttochanlev(char *text) {
  
  if(strcasecmp(text, "No") == 0) return CHAN_NOACCESS;
  else if(strcasecmp(text, "Friend") == 0) return CHAN_FRIEND;
  else if(strcasecmp(text, "Voice") == 0) return CHAN_VOICE;
  else if(strcasecmp(text, "Op") == 0) return CHAN_OP;
  else if(strcasecmp(text, "Master") == 0) return CHAN_MASTER;
  else if(strcasecmp(text, "Owner") == 0) return CHAN_OWNER;
  else return 99; // Level doesen't exist
}

int modfunc(char *module, char *function) {
  char msg[512];
  struct module *m;
  void (*cmd)();
  const char *err;

  m = findmodule(module);
  if(m != NULL) {
    cmd = dlsym(m->fl, function);
    
    err = dlerror();
    if(err) {
      snprintf(msg, 512, "%c4Warning:%c Call for function failed (%c%s%c in module %c%s%c). Error: %s", COLORS, COLORS, BOLD, function, BOLD, BOLD, module, BOLD, err);
      privmsg(HOMECHAN, msg);
      return 0;
    }

    cmd();
    return 1;
  } return 0;
}

int amodfunc(char *module, char *function, struct arm *arm) {
  char msg[512];
  struct module *m;
  void (*cmd)(struct arm *arm);
  const char *err;

  m = findmodule(module);
  if(m != NULL) {
    cmd = dlsym(m->fl, function);
    
    err = dlerror();
    if(err) {
      snprintf(msg, 512, "%c4Warning:%c Call for function failed (%c%s%c in module %c%s%c). Error: %s", COLORS, COLORS, BOLD, function, BOLD, BOLD, module, BOLD, err);
      privmsg(HOMECHAN, msg);
      return 0;
    }

    cmd(arm);
    return 1;
  } return 0;
}

/*
 * ircd_strcmp - case insensitive comparison of 2 strings
 * NOTE: see ircd_chattr.h for notes on case mapping.
 * NOTE: This function is a direct ripoff from the the Undernet IRCd.
 */
int irc_strcmp(const char *a, const char *b)
{
  const char* ra = a;
  const char* rb = b;
  
  while (ToLower(*ra) == ToLower(*rb)) {
    if (!*ra++)
      return 0;
    else
      ++rb;
  }
  return (*ra - *rb);
}

void TimerUserLoop(void) {
  /* This loop handles all timer based actions to the user structure */

  struct activeuser *u;
  struct activeuser *next;

  pthread_mutex_lock(&globalmutex); // Lock MuteX

  u = firstauser;
  while (u != NULL)
    {
      if((next = DelOld(u)) == NULL) {
#ifdef Q_AUTOAUTH
	ReCheckAuth(u);
#endif
	u = u->next;
      } else u = next;
      
    }
  pthread_mutex_unlock(&globalmutex); // UnLock MuteX
}

#ifdef Q_AUTOAUTH
int ReCheckAuth(struct activeuser *u) {
  /* Check if an unauthed user needs to be auth-checked again */

  if(u->auth == UNAUTHED && u->check != 0 && time(NULL) >= u->check) {
    add(u);
    u->check = 0;
    return 1;
  } else return 0;
}
#endif

struct activeuser *DelOld(struct activeuser *u)
{
  /* This function checks if a user is on any channels, and his idle time. If he isn't seen on any channels, and has exceeded the maximum idle time, he will be deleted from memory */

  struct activeuser *NextUser = NULL;

  if (u->lastseen+UEXPIRE <= time(NULL) && u->auth < BOT_NORMAL && u->firstlink == NULL && u->isinhome == 0) {
#ifdef DEBUG
    printf("User: %s not seen on any channel, deleted. (expire)\n", u->nick);
#endif
    NextUser = u->next;
    deluser(u);
    return NextUser;
  } else return NULL;
}

void TimerChannelLoop(void) {
   /* This loop handles all timer based actions to the user structure */
  
  struct channel *c;
  int rejoin;

  pthread_mutex_lock(&globalmutex); // Lock MuteX

  if(time(NULL) >= rejoint) {
    rejoin = 1;
    rejoint = time(NULL) + REJOINFREQ;
  }
  else rejoin = 0;

  c = firstchan;
  
  while (c != NULL)
    {
      DelOldChannelBans(c); /* Delete old channel bans */
      CloseEndedVote(c); /* Close votes that have ended */
      if(rejoin) ReJoinChannelCheck(c); /* Try to rejoin channels if rejoint(time variable) is exceeded */
     
      c = c->next;
    }
  
  pthread_mutex_unlock(&globalmutex); // Unlock MuteX
}

void ReJoinChannelCheck(struct channel *c) {
  /* Check if the bot is not on a channel and rejoin if TRUE. */
  
  if(c->ison == 0 && c->arm->status == 1) {
#ifdef DEBUG
    printf("Rejoining: %s\n", c->name);
#endif
    rejoinchannel(c->name, c->arm);
  }
}

void DelOldChannelBans(struct channel *c) {
  /* This function will remove bans from channels that have expired */

  struct banlist *ban;

  ban = c->firstbanlist;
  while (ban != NULL)
    {
      if(ban->expire <= time(NULL)) delban(ban->host, c->name);
      ban = ban->next;
    }
}

void CloseEndedVote(struct channel *c) {
  /* This function will close a vote for a channel if it has expired */

  char tmp[512];
  
  if(c->votestruct != NULL && c->votestruct->expiretime <= time(NULL)) {
    if(c->votestruct->yes > c->votestruct->no) snprintf(tmp, 512, "Vote has ended. %cYes%c won with %c%i%c votes against %c%i%c.", BOLD, BOLD, BOLD, c->votestruct->yes, BOLD, BOLD, c->votestruct->no, BOLD);
    else if(c->votestruct->no > c->votestruct->yes) snprintf(tmp, 512, "Vote has ended. %cNo%c won with %c%i%c votes against %c%i%c.", BOLD, BOLD, BOLD, c->votestruct->no, BOLD, BOLD, c->votestruct->yes, BOLD);
    else if(c->votestruct->no == c->votestruct->yes) snprintf(tmp, 512, "Vote has ended. Vote ended in tie with %c%i%c yes and no votes.", BOLD, c->votestruct->yes, BOLD);

    privmsg(c->name, tmp);

    FreeVote(c);
  }
}

struct arm *bestarm(void) {
  struct arm *a;
  int leastdatainbuf = 999999999;
  struct arm *bestarm = NULL;

  a = firstarm;
  while (a != NULL)
    {
      if((a->datainbuff <= leastdatainbuf) && a->status == 1) {
	bestarm = a;
	leastdatainbuf = a->datainbuff;
	if(leastdatainbuf == 0) return a;
      }
      a = a->next;
    }
  return bestarm;
}

void delallachanusers (struct channel *c) {
  struct activechanuser *acu;
 
  /* Delete all active channel users */
  acu = firstacu;
  while (acu != NULL)
    {
      if(acu->channel == c) delachanuser(acu->user->nick, c->name);
      acu = acu->next;
    }
#ifdef DEBUG
  printf("All active channel users for channel: %s deleted\n", c->name);
#endif
} 

int delallauserchans (struct activeuser *u) {
  struct aculink *aculink;
  struct aculink *next;
  
#ifdef DEBUG
  printf("Deleting all active channels for user: %s\n", u->nick);
#endif
  
  aculink = u->firstlink;

  while (aculink != NULL) {
    next = aculink->next;

    if(delachanuser(u->nick, aculink->link->channel->name)) return 1;

    aculink = next;
  }
  return 0;
}

void loadarms () {
 
  dbquery("UPDATE `arms` SET `status` = 0, `channels` = 1 WHERE `sid` = " SID "");
  EndDbQuery();

  dbquery("SELECT `id`, `nick`, `type` FROM `arms` WHERE `sid` = " SID " ORDER BY `id`");
  while((row = mysql_fetch_row(res))) {
    addarm (row[1], atoi(row[0]), atoi(row[2]));
  }
  EndDbQuery();
}

void loadchannellist () {
  dbquery("SELECT * FROM `channels` WHERE `sid` = " SID "");
  while((row = mysql_fetch_row(res))) {
    addchannel (row[1], atoi(row[0]), row[2], row[3], atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), row[8], atoi(row[9]), row[10], atoi(row[11]), atoi(row[12]), atoi(row[13]), atoi(row[14]), atoi(row[15]), atoi(row[16]), findarmbyid(atoi(row[18])), row[17]);
  }
  EndDbQuery();

  dbquery("SELECT * FROM `chanusers` WHERE `sid` = " SID " ");
  while((row = mysql_fetch_row(res))) {
    addchanuser (findchannelbyid(atoi(row[0])), atoi(row[1]), atoi(row[2]), row[3]);
  }  
  EndDbQuery();
}

void loadchannel (char *channame) {
  char query[MAX_QUERY];
 
  sprintf(query, "SELECT * FROM `channels` WHERE name = '%s'", channame);
  dbquery(query); 

  if((row = mysql_fetch_row(res))) {
 addchannel (row[1], atoi(row[0]), row[2], row[3], atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), row[8], atoi(row[9]), row[10], atoi(row[11]), atoi(row[12]), atoi(row[13]), atoi(row[14]), atoi(row[15]), atoi(row[16]), findarmbyid(atoi(row[18])), row[17]);
  } else printf("Something relly strange heappened, the channel (%s) I tried to load doesen't exist.\n", channame);
  EndDbQuery();
}

void joinchannels (struct arm *a)
{
  char msg[4096], query[MAX_QUERY];
  struct channel *c;

  strncpy(msg, "JOIN ", 4096);
		
  c = firstchan;
  while (c != NULL && strlen(msg) < 4096-CHANNELLEN)
    {
      if(c->arm == a) {
	strcat(msg, c->name);
	strcat(msg, ",");
	++a->channels;
	c->ison = 0; // Assume we are NOT on the channel
      }
      c = c->next;
    }

  sprintf(query, "UPDATE `arms` SET `channels` = '%i' WHERE `id` = '%i'", a->channels, a->id);
  dbquery(query);
  EndDbQuery();

  strcat(msg,"\n\r");
  putserver(msg, a);
}

void adddbchannel (char *channame, struct arm *a) {
  char query[MAX_QUERY];
  
  sprintf(query, "INSERT INTO channels (name, arm, sid) VALUES ('%s','%i','%i')", channame, a->id, atoi(SID));
  dbquery(query);
  EndDbQuery();
}

void deldbarm (char *arm) {
  struct arm *a;
  char query[MAX_QUERY];
  
  a = findarm(arm);

  sprintf(query, "DELETE FROM channels WHERE arm = '%i'", a->id);
  dbquery(query);
  EndDbQuery();

  sprintf(query, "DELETE FROM arms WHERE id = '%i'", a->id);
  dbquery(query);
  EndDbQuery();
}

void deldbchannel (char *channame) {
  struct channel *c;
  char query[MAX_QUERY];
  
  c = findchannel(channame);

  sprintf(query, "DELETE FROM channels WHERE id = '%i'", c->id);
  dbquery(query);
  EndDbQuery();
  
  if(c->arm->type != BOT_CHECK) {
    sprintf(query, "DELETE FROM chanusers WHERE cid = '%i'", c->id);
    dbquery(query);
    EndDbQuery();
    
    sprintf(query, "DELETE FROM log WHERE cid = '%d'", c->id);
    dbquery(query);
    EndDbQuery();
  }
}

void dbchanmode (int id, char option[], char value[]) {
  char query[MAX_QUERY];
  char *esvalue;

  esvalue = escapequery(value);
  sprintf(query, "UPDATE `channels` SET `%s` = '%s' WHERE id = '%d'", option, esvalue, id);
  free(esvalue);

  dbquery(query);
  EndDbQuery();
}

void addlog(char *nick, char *channame, char action[10], char *msg) {
  struct activeuser *u;
  struct channel *c;
  
  u = findauser(nick);
  c = findchannel(channame);
  // printf("Starting to log for: %s channel: %s action: %s msg: %s\n", nick, channame, action, msg);
  if((c == NULL || (c != NULL && c->stats == 0) || u == NULL) && strcasecmp(action, "QUIT") != 0 && strcasecmp(action, "NICK") != 0) return; 

  if(strcasecmp(action, "QUIT") != 0 && strcasecmp(action, "NICK") != 0) {
    log[lines].cid = c->id;
    strncpy(log[lines].msg, msg, 512);
  }
  else {
    log[lines].cid = 0;
    strncpy(log[lines].msg, channame, 512);
  }

  strncpy(log[lines].action, action, 10);
  strncpy(log[lines].nick, nick, NICKLEN);
  strncpy(log[lines].username, u->username, USERLEN);
  strncpy(log[lines].hostname, u->hostname, HOSTLEN);
  log[lines].uid = u->userid;
  log[lines].time = time(NULL);
  
  if(lines != LINESBEFORESAVE) ++lines;
  else flushlog(); 
}

void flushlog() {
  char query[MAX_QUERY+1024];
  int i = 0;
  char *esnick, *esusername, *eshost, *esmsg;

  printf("Flushing logs...\n");

  while (i < lines) {
   
    esnick = escapequery(log[i].nick);
    esusername = escapequery(log[i].username);
    eshost = escapequery(log[i].hostname);
    esmsg = escapequery(log[i].msg);

    sprintf(query, "INSERT INTO `log` (`cid`, `action`, `nick`, `username`, `host`, `uid`, `msg`, `time`) VALUES ('%d', '%s', '%s', '%s', '%s', '%d', '%s', '%i')", log[i].cid, log[i].action, esnick, esusername, eshost, log[i].uid, esmsg, log[i].time);
    
    free(esnick);
    free(esusername);
    free(eshost);
    free(esmsg);

    logdbquery(query);
    EndLogDbQuery(); 
    
    ++i;
  }

  lines = 0;   
}

int isOpLessChan(struct channel *c) {
  /* This function checks if a channel is opless */
  struct activechanuser *acu;
  
  if(c->isop == 1) return 0; // We are op, so the channel can't be opless

  acu = firstacu;
  
  while (acu != NULL)
    {
      if(acu->channel == c && acu->op == 1) return 0; // Found a guy that has op, so, why go on?
      acu = acu->next;
    }
  return 1;
}

int ServiceChannelCount (void) {
  /* This function returns the total number of channels that the service is on */
  int channels;

  dbquery("SELECT COUNT(*) FROM `channels` WHERE `sid` = '" SID "'");
  
  row = mysql_fetch_row(res);
  channels = atoi(row[0]);

  EndDbQuery();

  return channels;
}

int OnJoinVoteMsg (struct channel *c, struct activeuser *u) {
  char ret[512];

  if(c == NULL || c->votestruct == NULL) return 0;
  else {

    snprintf(ret, 512, "There is currently a vote going on in this channel. The question is: %c%s%c See more status and help with %c!vote status%c", BOLD, c->votestruct->question, BOLD, BOLD, BOLD);

#ifdef CNOTICE
    cnotice(u->nick, ret);
#else
    say(u->nick, ret, say);
#endif
    return 1;
  }
}

int StrDurationToSeconds (char *duration) {
  /* Parse a string with duration and return duration in seconds */

  char msg[512];
  int expire;
  
  if (strstr(duration, "h") != NULL) {
    STR_replace_c (duration, "h", "", msg);
    expire = time(NULL) + atoi(msg)*3600;
  }

  else if (strstr(duration, "m") != NULL) {
    STR_replace_c (duration, "m", "", msg);
    expire = time(NULL) + atoi(msg)*60;
  }

  else if (strstr(duration, "d") != NULL) {
    STR_replace_c (duration, "d", "", msg);
    expire = time(NULL) + atoi(msg)*86400;
  } 
  else {
    return -1;

    // Should be handled with something like: say(arm->parsevars->sender, "Wrong duration, duration can be day(d), hour(h), minute(m). I.e: 2h (two hours)", arm);
  }

  return expire;
}

int dbCount (char *query) {
  int count;

  dbquery(query);

  row = mysql_fetch_row(res);
  count = atoi(row[0]);

  EndDbQuery();

  return count;
}

int AddRegexBadChan (char *regex, char *reason, int finalduration, int adder) {
  /* Ban a regex from request, using regular expressions */

  char query[MAX_QUERY];
  char *esregex, *esreason;

  snprintf(query, MAX_QUERY, "SELECT COUNT(*) FROM `badchan` WHERE `channel` = '%s' AND `active` = '1'", regex);
  if(dbCount(query) > 0) return 0;

  esregex = escapequery(regex);
  esreason = escapequery(reason);

  sprintf(query, "INSERT INTO `badchan` (`channel`,`adder`,`reason`,`time`, `expire`) VALUES ('%s', '%i', '%s', UNIX_TIMESTAMP(), '%i')", esregex, adder, esreason, finalduration);

  free(esregex);
  free(esreason);

  dbquery(query);
  EndDbQuery();

  return 1;
}

int AddBadChan (char *channel, char *reason, int finalduration, int adder) {
  /* Ban a channel from request, apply regexp */

  char regex[CHANNELLEN+2];

  snprintf(regex, CHANNELLEN+2, "^%s$", channel);

  return AddRegexBadChan (regex, reason, finalduration, adder);
}

int DelBadChan (char *regex) {
  /* Remove regex from badchan */

  char query[MAX_QUERY];
  char *esregex;

  esregex = escapequery(regex);

  snprintf(query, MAX_QUERY, "SELECT COUNT(*) FROM `badchan` WHERE `channel` = '%s' AND `active` = '1'", esregex);
  if(dbCount(query) == 0) return 0;
 
  sprintf(query, "UPDATE `badchan` SET `active` = 0 WHERE `channel` = '%s' AND `active` = '1'", esregex);
  free(esregex);

  dbquery(query);
  EndDbQuery();

  return 1;
}

void CleanUpOldBadChans (void) {
  /* Remove old badchans from database */
  
  dbquery("UPDATE `badchan` SET `active` = 0 WHERE `expire` < UNIX_TIMESTAMP() AND `active` = '1'");
  EndDbQuery();
}

#ifdef SPAMSCAN
int SOnChannel(struct channel *c) {
  /* Will check if SpamScan is on a channel and return 1 if it is */
  
  if(findachanuser(SPAMSCANNICK, c->name) == NULL) return 0;
  else return 1;
}

int SpamChannel(struct channel *c) {
  /* Returns 1 if it is ok to spam into the channel */

  if(c->nextspam <= time(NULL)) return 1;
  else return 0;
}

int SpamChannelWait(struct channel *c) {
  /* Adds SPAMWAIT to nextspam */

  c->nextspam = time(NULL) + SPAMWAIT;
  return 1;
}
#endif

void EndDbQuery(void) {
  /* Unlock the mutex and cleanup used resources */
  
  if(res != NULL) mysql_free_result(res);
  pthread_mutex_unlock(&mysqlmutex); // UnLock mutex
}

void EndLogDbQuery(void) {
  /* Unlock the mutex and cleanup used resources */
  
  if(res != NULL) mysql_free_result(logres);
  pthread_mutex_unlock(&logmysqlmutex); // UnLock mutex
}

void OutOfChannel(struct channel *c) {
  /* Execute this when the bot is out from a channel, i.e a kick or a cycle */
  
  c->ison = 0;    
  delallachanusers(c);
}
