/*
 * memory.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"

struct activeuser *addauser (char *nick, char *username, char *hostname)
{
  struct activeuser *newuser;

  pthread_mutex_lock(&globalusermutex);
  newuser = (struct activeuser *) malloc (sizeof (struct activeuser));
  if (newuser == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return newuser;
    }
  memset (newuser, 0, sizeof (struct activeuser));
  // printf("Debug: Space allocated for %s.\n", nick); 

  strncpy (newuser->nick, nick, NICKLEN);

  newuser->username = strdup(username);
  newuser->hostname = strdup(hostname);

  newuser->auth = 0;
  newuser->userid = 0;
  newuser->lastseen = time(NULL);
  newuser->isinhome = 0;
  newuser->requestok = 0;
#ifdef Q_AUTOAUTH
  newuser->check = 0;
#endif

  newuser->firstlink = NULL;
  newuser->lastlink = NULL;

  newuser->last = lastauser;
  newuser->next = NULL;
  if (lastauser != NULL)
    {
      lastauser->next = newuser;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      firstauser = newuser;
    }
  lastauser = newuser;
  pthread_mutex_unlock(&globalusermutex);
    
  return newuser;
}

struct aculink *addaculink (struct activeuser *u, struct activechanuser *acu)
{
  struct aculink *newaculink;

  newaculink = (struct aculink *) malloc (sizeof (struct aculink));
  if (newaculink == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return newaculink;
    }
  memset (newaculink, 0, sizeof (struct aculink));

  newaculink->link = acu;

  newaculink->last = u->lastlink;
  newaculink->next = NULL;
  if (u->lastlink != NULL)
    {
      u->lastlink->next = newaculink;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      u->firstlink = newaculink;
    }
  u->lastlink = newaculink;
   
  return newaculink;
}

/* Add an arm */
void addarm (char *nick, int aid, int type)
{
  struct arm *newarm;
 
  if (findarm(nick) == NULL)
    {
      
      newarm = (struct arm *) malloc (sizeof (struct arm));
      if (newarm == NULL)
	{
	  printf ("Debug: malloc() FAILED!\a\n");
	  return;
	}
      memset (newarm, 0, sizeof (struct arm));
#ifdef DEBUG
      printf("Debug: Space allocated for arm: %s.\n", nick); 
#endif
      strncpy (newarm->nick, nick, NICKLEN);
      strncpy (newarm->server, SERVER, 50);
#ifdef OUTGOING_VHOST
      strncpy (newarm->vhost, DEFAULT_VHOST, 150);
#endif
      newarm->id = aid;
      newarm->fd = 0;
      newarm->datasent = 0;
      newarm->datainbuff = 0;
      newarm->lastreset = 0;
      newarm->nextsend = 0;
      newarm->status = 0;
      newarm->channels = 0;
      newarm->activethread = 0;
      newarm->type = type;
      newarm->connect = 1;
      
      /* Parsevar malloc() */
      newarm->parsevars = (struct parsevars *) malloc (sizeof (struct parsevars));
      
      /* Mutex Inits */
      pthread_mutex_init(&newarm->sendqmutex, NULL);

      newarm->last = lastarm;
      newarm->next = NULL;
      if (lastarm != NULL)
	{
	  lastarm->next = newarm;
	}
      else
	{
	  /* 
	   * No final user yet defined, so this one must be the
	   * very first one.
	   */
	  firstarm = newarm;
	}
      lastarm = newarm;
    } 
}

/* Loadmodule */
void loadmodule (char path[100], short fatal)
{
  struct module *newmodule = NULL;
  const char *err;
  char msg[512];
  void (*cmd)(struct module *m);

  if (findmodulebypath(path) == NULL)
    {
      newmodule = (struct module *) malloc (sizeof (struct module));
      if (newmodule == NULL)
	{
	  printf ("Debug: malloc() FAILED!\a\n");
	  return;
	}
      memset(newmodule, 0, sizeof (struct module));
      strncpy(newmodule->path, path, 100);
      
      newmodule->fl = dlopen(path, RTLD_NOW);
      err = dlerror();
      if(err) {
	snprintf(msg, 512, "%c4Warning:%c Module load failed (%c%s%c). Error: %s", COLORS, COLORS, BOLD, path, BOLD, err);
	privmsg(HOMECHAN, msg);
	
	printf("Error opening module: %s.\n", err);
	free(newmodule);
	if(fatal == 1) exit(1);
	return;
      } else {
	strncpy(newmodule->name, "UnNamed", 20);
      }
          
      newmodule->last = lastmodule;
      newmodule->next = NULL;
      if (lastmodule != NULL)
	{
	  lastmodule->next = newmodule;
	}
      else
	{
	  /* 
	   * No final user yet defined, so this one must be the
	   * very first one.
	   */
	  firstmodule = newmodule;
	}
      lastmodule = newmodule;
    } 

  cmd = dlsym(newmodule->fl, "_module_init");

  err = dlerror();
  if(err) {
    snprintf(msg, 512, "%c4Error:%c Init function does not exist in module. Module load aborted.", COLORS, COLORS);
    privmsg(HOMECHAN, msg);
    unloadmodule("UnNamed");
    return;
  }

  cmd(newmodule);

  printf("Successfully loaded: %s version: %0.2f\n", newmodule->name, newmodule->version);
}

void unloadmodule (char *name)
{
  struct module *m;
  const char *err;
  void (*cmd)(struct module *m);

  m = findmodule(name);

  if (m == NULL) return;

  cmd = dlsym(m->fl, "_module_destroy");

  err = dlerror();
  if(err) printf("Warning: destroy function in module: %s is missing\n", name);
  else cmd(m);
  
  dlclose(m->fl);

  if (m->last != NULL)
    {
      m->last->next = m->next;	/* Reset the next pointer */
    }
  else
    {
      firstmodule = m->next;
    }
  if (m->next != NULL)
    {
      m->next->last = m->last;	/* and the last pointer */
    }
  else
    {
      lastmodule = m->last;
    }
  free(m);			/* And remove the module from memory. */

}

/* Add an active channel user */

struct activechanuser *addachanuser (char *nick, char *channel)
{
  struct activechanuser *newactivechanuser;
  struct activeuser *u;
  
  u = findauser(nick);

  if(findchannel(channel) == NULL || u == NULL) {
    if(u != NULL && strcasecmp(channel, HOMECHAN) == 0) {
      u->isinhome = 1;
      return NULL;
    }
    return NULL;
  }

  if(findachanuser(nick, channel) != NULL) return NULL;

  pthread_mutex_lock(&globalacumutex);

  newactivechanuser = (struct activechanuser *) malloc (sizeof (struct activechanuser));
  if (newactivechanuser == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return NULL;
    }
  memset (newactivechanuser, 0, sizeof (struct activechanuser));
  
#ifdef DEBUG
  printf("Debug: Space allocated for active channel user %s.\n", nick); // Debugging code
#endif

  newactivechanuser->channel = findchannel(channel);
  newactivechanuser->user = u;

  newactivechanuser->op = 0;
  newactivechanuser->voice = 0;
  newactivechanuser->warned = 0;
  newactivechanuser->lastreset = time(NULL);
  newactivechanuser->rows = 0;
  
  newactivechanuser->last = lastacu;
  newactivechanuser->next = NULL;

  if (lastacu != NULL)
    {
      lastacu->next = newactivechanuser;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      firstacu = newactivechanuser;
    }
  lastacu = newactivechanuser;

  addaculink(u, newactivechanuser);

  pthread_mutex_unlock(&globalacumutex);

  return newactivechanuser;
}


/* Add a ban on a channel */

void addban (char *channel, char *host, char *reason, int setby, int expire)
{
  struct banlist *newban;
  struct channel *c;
  char msg[CHANNELLEN+10+BANLEN];
  
  c = findchannel(channel);

  if(c == NULL || findban(host, channel) != NULL) return;

  newban = (struct banlist *) malloc (sizeof (struct banlist));

  if (newban == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return;
    }

  memset (newban, 0, sizeof (struct banlist));

  strcpy(newban->host, host);
  strcpy(newban->reason, reason);
  newban->setby = setby;
  newban->expire = expire;

  newban->last = c->lastbanlist;
  newban->next = NULL;
      
  if (c->lastbanlist != NULL)
    {
      c->lastbanlist->next = newban;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      c->firstbanlist = newban;
    }
  c->lastbanlist = newban;
      
  /* We ban from channel */
  sprintf(msg, "MODE %s +b %s\n\r", channel, host);
  putserver(msg, c->arm);
}

/* Add a row to sendqueue */
void putserver (char *data, struct arm *a)
{
  struct sendqueue *newsendqueue;

  if(a == NULL) {
    printf("FATAL: arm == NULL in putserver()\n");
    return;
  }

  a->datainbuff = a->datainbuff + strlen(data);

  pthread_mutex_lock(&a->sendqmutex);
  
  newsendqueue = (struct sendqueue *) malloc (sizeof (struct sendqueue));

  if (newsendqueue == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      pthread_mutex_unlock(&a->sendqmutex);
      return;
    }

  memset (newsendqueue, 0, sizeof (struct sendqueue));

  strcpy(newsendqueue->data, data);

  newsendqueue->last = a->lastsendqueue;
  newsendqueue->next = NULL;
      
  if (a->lastsendqueue != NULL)
    {
      a->lastsendqueue->next = newsendqueue;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      a->firstsendqueue = newsendqueue;
    }
  a->lastsendqueue = newsendqueue;

  pthread_mutex_unlock(&a->sendqmutex);
}

/* Add a channel */

void addchannel (char *channel, short id, char *modes, char *key, short autoop, short autovoice, short bitch, short ftopic, char *mvoice, short advertise, char *flood, short peak, short greet, short tv, short vote, short infobot, short stats, struct arm *arm, char *onjoin)
{
  struct channel *newchan;
  
  
  if(findchannel(channel) != NULL || arm == NULL) return;

  newchan = (struct channel *) malloc (sizeof (struct channel));
  if (newchan == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return;
    }

  memset (newchan, 0, sizeof (struct channel));
#ifdef DEBUG
  printf("Debug: Space allocated for channel %s.\n", channel); 
#endif
  newchan->id = id;
  newchan->isop = 1; // Assume we have ops
  newchan->ison = 0; // Assume we are not on the channel
#ifdef SPAMSCAN
  newchan->nextspam = time(NULL);
#endif
  strncpy(newchan->topic, "", TOPICLEN);
  
  strcpy(newchan->name, channel);
  strcpy(newchan->modes, modes);
  strcpy(newchan->key, key);
  newchan->autoop = autoop;
  newchan->autovoice = autovoice;
  newchan->bitch = bitch;
  newchan->ftopic = ftopic;
  strcpy(newchan->mvoice, mvoice);
  strcpy(newchan->onjoin, onjoin);
  newchan->advertise = advertise;
  sscanf(flood, "%i:%i", &newchan->floodrep, &newchan->floodtime);
  newchan->peak = peak;
  newchan->greet = greet;
  newchan->tv = tv;
  newchan->vote = vote;
  newchan->infobot = infobot;
  newchan->stats = stats;

  newchan->votestruct = NULL;

  newchan->arm = arm;

  newchan->firstbanlist = NULL;
  newchan->lastbanlist = NULL;

  newchan->last = lastchan;
  newchan->next = NULL;
  if (lastchan != NULL)
    {
      lastchan->next = newchan;
    }
  else
    {
      /* 
       * No final channel yet defined, so this one must be the
       * very first one.
       */
      firstchan = newchan;
    }
  lastchan = newchan;
}

/* Add a channel user */

void addchanuser (struct channel *channel, int uid, int level, char *greet)
{
  struct chanuser *newchanuser;

  newchanuser = (struct chanuser *) malloc (sizeof (struct chanuser));
  if (newchanuser == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return;
    }
  memset (newchanuser, 0, sizeof (struct chanuser));

#ifdef DEBUG
  printf("Debug: Space allocated for channel user %d.\n", uid); // Debugging code
#endif

  newchanuser->channel = channel;
  newchanuser->uid = uid;
  newchanuser->level = level;
  strcpy(newchanuser->greet, greet);

  newchanuser->last = lastchanuser;
  newchanuser->next = NULL;
  if (lastchanuser != NULL)
    {
      lastchanuser->next = newchanuser;
    }
  else
    {
      /* 
       * No final user yet defined, so this one must be the
       * very first one.
       */
      firstchanuser = newchanuser;
    }
  lastchanuser = newchanuser;
}

struct arm *findarm (char *nick)
{
  struct arm *a;

  a = firstarm;
  while ((a != NULL) && (strcasecmp (a->nick, nick) != 0))
    {
      a = a->next;
    }
  return a;
}

struct module *findmodule (char name[20])
{
  struct module *m;

  m = firstmodule;
  while ((m != NULL) && (strcasecmp (m->name, name) != 0))
    {
      m = m->next;
    }
  return m;
}

struct module *findmodulebypath (char path[100])
{
  struct module *m;

  m = firstmodule;
  while ((m != NULL) && (strcasecmp (m->path, path) != 0))
    {
      m = m->next;
    }
  return m;
}

struct arm *findarmbyid (int id)
{
  struct arm *a;

  a = firstarm;
  while (a != NULL && a->id != id)
    {
      a = a->next;
    }
  return a;
}

struct arm *findarmbytype (int type)
{
  struct arm *a;

  a = firstarm;
  while (a != NULL && a->type != type)
    {
      a = a->next;
    }
  return a;
}

/*
 * Find active user by nick. Handy for deleting them, checking authentication
 * and so on.
 */

struct activeuser *findauser (char *nick)
{
  struct activeuser *u;

  u = firstauser;
  while ((u != NULL) && (irc_strcmp (u->nick, nick) != 0))
    {
      u = u->next;
    }
#ifdef DEBUG
  //  if (u == NULL && strcmp(nick, remote_server) != 0) printf("Didn't find user: %s Segfault may be eminent\n", nick); // Debug
#endif

  return u;
}

/* Find a channel by name */

struct channel *findchannel (char *name)
{
  struct channel *c;
  c = firstchan;

  while ((c != NULL) && (irc_strcmp (c->name, name) != 0))
    {
      c = c->next;
    }
#ifdef DEBUG
  //  if (c == NULL) printf("Didn't find channel: %s Segfault may be approaching\n", name); // Debug
#endif
  return c;
}

struct channel *findchannelbyid (int id) {
  struct channel *c;
  c = firstchan;

  while ((c != NULL) && (id != c->id))
    {
      c = c->next;
    }
  return c;
}

/* Find a channel user by uid */

struct chanuser *findchanuser (int uid, int cid)
{
  struct chanuser *cu;
  struct channel *c;

  if ((c = findchannelbyid(cid)) == NULL) return NULL;

  cu = firstchanuser;
  while (cu != NULL)
    {
      if(cu->channel == c && cu->uid == uid) {
	//	printf("Found and returning. uid: %i cid: %i\n", uid, cid);
	return cu;
      }
      cu = cu->next;
    }
#ifdef DEBUG
  //    if (cu == NULL) printf("Didn't find chanuser Segfault may be approaching\n"); // Debug
#endif
  return NULL;
}

struct activechanuser *findachanuser (char *nick, char *channel)
{
  struct aculink *aculink;
  struct activeuser *u;

  if((u = findauser(nick)) == NULL) return NULL; // To be refactored
  
  aculink = u->firstlink;
  
  while (aculink != NULL)
    {
      /* Dirty debug code
      if(aculink->link == NULL) printf("link == NULL\n");
      else printf("link != NULL\n");
      if(aculink->link->channel == NULL) printf("link->channel == NULL\n");
      else printf("link->channel != NULL\n");
      if(aculink->link->channel->name == NULL) printf("link->channel->name == NULL\n");
      else printf("link->channel->name != NULL, name: %s\n", nick);
      printf("acurun: %s %s\n", nick, aculink->link->channel->name);
      */

      if(strcasecmp(aculink->link->channel->name, channel) == 0) return aculink->link;
      aculink = aculink->next;
    }
  return NULL;
}

struct banlist *findban (char *host, char *channel)
{
  struct banlist *ban;
  struct channel *c;
  
  c = findchannel(channel);

  if(c == NULL) return NULL;

  ban = c->firstbanlist;

  while (ban != NULL && strcasecmp(ban->host, host) != 0)
    {
      ban = ban->next;
    }

  return ban;
}

void delarm (char *arm)
{
  struct arm *a;
  a = findarm (arm);

  if (a == NULL)
    {
      return;
    }
  else
    {
      if(a->status == 1) {
	puttoserver("QUIT :This ARM is being removed\n\r", a);
	linkbreak(a);
      }

      /* Free parsevars */

      free(a->parsevars);

      /* Mutex destructs */
      pthread_mutex_destroy(&a->sendqmutex);

      /* Join thread */
      if(pthread_join(a->thread, NULL)) printf("Error joining thread for (by delete) %s.", a->nick);
    }


  if (a->last != NULL)
    {
      a->last->next = a->next;	/* Reset the next pointer */
    }
  else
    {
      firstarm = a->next;
    }
  if (a->next != NULL)
    {
      a->next->last = a->last;	/* and the last pointer */
    }
  else
    {
      lastarm = a->last;
    }
  free(a);			/* And remove the user from memory. */
}

void deluser (struct activeuser *u)
{
  if (u == NULL)
    {
      /* printf("Debug: Failed to find user %s.\n",nick); */
      return;
    }
  
  if (u->last != NULL)
    {
      u->last->next = u->next;	/* Reset the next pointer */
    }
  else
    {
      firstauser = u->next;
    }

  if (u->next != NULL)
    {
      u->next->last = u->last;	/* and the last pointer */
    }
  else
    {
      lastauser = u->last;
    }

  free(u->username);            /* Free submallocs */
  free(u->hostname);
  free(u);			/* And remove the user from memory. */
}

void delaculink (struct activeuser *u, struct activechanuser *acu)
{
  struct aculink *aculink;
  short done = 0;

  if (u == NULL)
    {
      /* printf("Debug: Failed to find user %s.\n",nick); */
      return;
    }
  
  aculink = u->firstlink;

  while (aculink != NULL && done == 0)
    {
      if(aculink->link == acu) done = 1;
      else aculink = aculink->next;
    }

  if (aculink->last != NULL)
    {
      aculink->last->next = aculink->next;	/* Reset the next pointer */
    }
  else
    {
      u->firstlink = aculink->next;
    }

  if (aculink->next != NULL)
    {
      aculink->next->last = aculink->last;	/* and the last pointer */
    }
  else
    {
      u->lastlink = aculink->last;
    }

  free(aculink);			/* And remove the user from memory. */
}

/* Delete user on channel */

int delachanuser (char *nick, char *channel)
{
  struct activechanuser *acu;
  struct activeuser *u;
  struct channel *c;

  pthread_mutex_lock(&globalacumutex);

  acu = findachanuser(nick, channel);
  u = findauser(nick);
  c = findchannel(channel);

  if (acu == NULL)
    {
      if(u != NULL && strcasecmp(channel, HOMECHAN) == 0) {
	u->isinhome = 0;
      }
#ifdef DEBUG
       else printf("Debug: Failed to find user(1) for acudel %s.\n", nick);     
#endif
    }
  else
    {
      //     printf ("Debug: Deleting active channel user nick %s\n", nick); 

      delaculink(u, acu); // Delete the link

      if (acu->last != NULL)
	{
	  acu->last->next = acu->next;	/* Reset the next pointer */
	}
      else
	{
	  firstacu = acu->next;
	}
      if (acu->next != NULL)
	{
	  acu->next->last = acu->last;	/* and the last pointer */
	}
      else
	{
	  lastacu = acu->last;
	}
      free(acu);			/* And remove the user from memory. */
    }

  /* Check if user is on any chans now */
  
  if(u != NULL && u->auth == 0) {
  
    if(u->firstlink == NULL && u->isinhome == 0) {
#ifdef DEBUG
      printf("User: %s not seen on any channel, deleted. (now parted: %s)\n", u->nick, channel);
#endif
      deluser(u);
      pthread_mutex_unlock(&globalacumutex);
      return 1;
    }
  }
  pthread_mutex_unlock(&globalacumutex);
  return 0;
}

/* Delete ban from channel */
void delban (char *host, char *channel)
{
  struct channel *c;
  struct banlist *ban;
  char msg[CHANNELLEN+10+BANLEN];
 
  c = findchannel(channel);
  ban = findban(host, channel);

  if (ban != NULL)
    {

      sprintf(msg, "MODE %s -b %s\n\r", channel, ban->host);
      putserver(msg, c->arm);

      if (ban->last != NULL)
	{
	  ban->last->next = ban->next;	/* Reset the next pointer */
	}
      else
	{
	  c->firstbanlist = ban->next;
	}
      if (ban->next != NULL)
	{
	  ban->next->last = ban->last;	/* and the last pointer */
	}
      else
	{
	  c->lastbanlist = ban->last;
	}
      free(ban);			/* And remove the user from memory. */
    }
}

/* Delete first sendqueue row from arm */
void delsendqueue (struct arm *a)
{
  struct sendqueue *sendqueue;
 
  sendqueue = a->firstsendqueue;
  
  a->datainbuff = a->datainbuff - strlen(sendqueue->data);

  // while (sendqueue != NULL) sendqueue = sendqueue->next; This is wrong, we need to start sending the queue from back to front

  pthread_mutex_lock(&a->sendqmutex);

  if (sendqueue != NULL)
    {

      if (sendqueue->last != NULL)
	{
	  //sendqueue->last->next = sendqueue->next;	/* Reset the next pointer */ This should never be true
	  printf("BUG BUG BUG BUG!!!!!!!!\n");
	}
      else
	{
	  a->firstsendqueue = sendqueue->next;
	}
      if (sendqueue->next != NULL)
	{
	  sendqueue->next->last = sendqueue->last;	/* and the last pointer */
	}
      else
	{
	  a->lastsendqueue = sendqueue->last;
	}
      free(sendqueue);			/* And remove the sendqueue from memory. */
    }
  pthread_mutex_unlock(&a->sendqmutex);
}

/* Delete channel */

void delchannel (struct channel *c)
{
  if (c->last != NULL)
    {
      c->last->next = c->next;	/* Reset the next pointer */
    }
  else
    {
      firstchan = c->next;
    }
  if (c->next != NULL)
    {
      c->next->last = c->last;	/* and the last pointer */
    }
  else
    {
      lastchan = c->last;
    }
  free(c);			/* And remove the channel from memory. */

}

struct vote *AllocateVote(struct channel *c) {
  struct vote *newvote;
  
  if(c->votestruct != NULL) return NULL; // Vote already running

  newvote = (struct vote *) malloc (sizeof (struct vote));
  if (newvote == NULL)
    {
      printf ("Debug: malloc() FAILED!\a\n");
      return NULL;
    }

  memset (newvote, 0, sizeof (struct vote));

  newvote->yes = 0;
  newvote->no = 0;

  newvote->lastvid = 0;
  
  c->votestruct = newvote;

  return newvote;
}

int FreeVote (struct channel *c) {
  if(c->votestruct == NULL) return 0; /* No vote on channel */

  free(c->votestruct);

  c->votestruct = NULL;

  return 1;
}
