/*
 * events.c     -- Events fonctions
 *
 * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
 * Copyright (C) 2010      Myhailo Danylenko <isbear@ukrpost.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <string.h>
#include "events.h"
#include "screen.h"

typedef struct {
  char           *id;
  char           *description;
  time_t          timeout;
  guint           source;
  evs_callback_t  callback;
  gpointer        data;
  GDestroyNotify  notify;
} evs_t;

static GSList *evs_list; // Events list

static evs_t *evs_find(const char *evid);

static gboolean evs_check_timeout (gpointer userdata)
{
  evs_t *event = userdata;
  if (event->callback &&
      !event->callback(EVS_CONTEXT_TIMEOUT, NULL, event->data)) {
    evs_del(event->id);
    return FALSE;
  }
  return TRUE; // XXX
}

//  evs_new(type, timeout)
// Create new event. If id is omitted, generates unique
// numerical id (recommended). If timeout is specified, sets
// up timeout source, that will call handler in timeout
// context after specified number of seconds. If supplied id
// already exists, returns NULL, calling destroy notifier, if
// one is specified.
const char *evs_new(const char *desc, const char *id, time_t timeout, evs_callback_t callback, gpointer udata, GDestroyNotify notify)
{
  static guint evs_idn;
  evs_t *event;
  char *stridn;

  if (!id) {
    if (!++evs_idn)
      evs_idn = 1;
    /* Check for wrapping, we shouldn't reuse ids */
    stridn = g_strdup_printf("%d", evs_idn);
    if (evs_find(stridn))  {
      g_free(stridn);
      // We could try another id but for now giving up should be fine...
      if (notify)
        notify(udata);
      return NULL;
    }
  } else if (!evs_find(id))
    stridn = g_strdup(id);
  else {
    if (notify)
      notify(udata);
    return NULL;
  }

  event = g_new(evs_t, 1);

  event->id          = stridn;
  event->description = g_strdup(desc);
  event->timeout     = timeout;
  event->callback    = callback;
  event->data        = udata;
  event->notify      = notify;

  if (timeout)
    event->source = g_timeout_add_seconds(timeout, evs_check_timeout, event);
  else
    event->source = 0;

  evs_list = g_slist_append(evs_list, event);
  return stridn;
}

static evs_t *evs_find(const char *evid)
{
  GSList *p;

  if (!evid)
    return NULL;

  for (p = evs_list; p; p = g_slist_next(p)) {
    evs_t *i = p->data;
    if (!strcmp(evid, i->id))
      return i;
  }
  return NULL;
}

//  evs_del(evid)
// Deletes event.
// This will not call event handler, however this will
// call destroy notify function.
// Returns 0 in case of success, -1 if the evid hasn't been found.
int evs_del(const char *evid)
{
  evs_t *event = evs_find(evid);

  if (!event)
    return -1;

  if (event->notify)
    event->notify(event->data);
  if (event->source)
    g_source_remove(event->source);

  evs_list = g_slist_remove(evs_list, event);
  g_free(event->id);
  g_free(event->description);
  g_free(event);

  return 0; // Ok, deleted
}

//  evs_callback(evid, evcontext, argument)
// Callback processing for the specified event.
// If event handler will return FALSE, event will be destroyed.
// Return 0 in case of success, -1 if the evid hasn't been found.
// evcontext and argument are transparently passed to event handler.
int evs_callback(const char *evid, guint context, const char *arg)
{
  evs_t *event;

  event = evs_find(evid);
  if (!event)
    return -1;

  if (event->callback &&
      !event->callback(context, arg, event->data))
    evs_del(evid);
  return 0;
}

//  evs_display_list()
// Prints list of events to mcabber log window.
void evs_display_list(void)
{
  guint count = 0;
  GSList *p;

  scr_LogPrint(LPRINT_NORMAL, "Events list:");
  for (p = evs_list; p; p = g_slist_next(p)) {
    evs_t *i = p->data;
    scr_LogPrint(LPRINT_NORMAL,
                 "Id: %-3s %s", i->id,
                 (i->description ? i->description : ""));
    count++;
  }
  scr_LogPrint(LPRINT_NORMAL, "End of events list.");
  if (count+2 > scr_getlogwinheight()) {
    scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
    scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
                                   ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
  }
}

//  evs_geteventslist()
// Return a singly-linked-list of events ids.
// Data in list should not be modified and can disappear,
// you must strdup them, if you want them to persist.
// Note: the caller should free the list after use.
GSList *evs_geteventslist(void)
{
  GSList *evidlist = NULL, *p;

  for (p = evs_list; p; p = g_slist_next(p)) {
    evs_t *i = p->data;
    evidlist = g_slist_append(evidlist, i->id);
  }

  return evidlist;
}

//  evs_deinit()
// Frees all events.
void evs_deinit(void)
{
  GSList *eel;
  for (eel = evs_list; eel; eel = eel->next) {
    evs_t *event = eel->data;
    if (event->notify)
      event->notify(event->data);
    if (event->source)
      g_source_remove(event->source);

    evs_list = g_slist_remove(evs_list, event);
    g_free(event->id);
    g_free(event->description);
    g_free(event);
  }
  g_slist_free(evs_list);
  evs_list = NULL;
}

/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
