/*
  thevent library, a more robust and reentrant replacement for Acorn's eventlib

  By Tony Houghton <tonyh@tcp.co.uk>
  271 Upper Weston Lane
  Southampton
  SO19 9HY


  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA 02111-1307, USA
*/

/*

$Header: ADFS::Nisu.\044.AcornC_C++.Libraries.thevent.RCS.thevent,v.c 1.1 1998/05/11 21:45:23 root Exp root $

$Log: thevent,v.c $
Revision 1.1  1998/05/11 21:45:23  root
Initial revision


*/

#include <stdlib.h>

#include "wimplib.h"

#include "thevent.h"

typedef union {
  thevent_WimpHandler *wimp;
  thevent_ToolboxHandler *toolbox;
  thevent_MessageHandler *message;
} HandlerFn;

typedef struct Handler {
  struct Handler *next, *prev;
  ObjectId id;
  int code;
  HandlerFn fn;
  void *handle;
  int active;
} Handler;

typedef struct {
  Handler *first, *last;
} HandlerList;

static HandlerList specific_wimp = {0, 0};
static HandlerList any_wimp = {0, 0};
static HandlerList specific_toolbox = {0, 0};
static HandlerList any_toolbox = {0, 0};
static HandlerList any_message = {0, 0};

static IdBlock *global_id_block;

static unsigned int Mask = Wimp_Poll_NullMask;

static _kernel_oserror err_Already = {0, "thevent already initialised"};
static _kernel_oserror err_UnInit = {0, "thevent not initialised"};
static _kernel_oserror err_HandlerMem = {0, "No memory for event handler"};
static _kernel_oserror err_PollMem = {0, "No memory for poll block"};
static _kernel_oserror err_BadRelease = {0, "No handler matches the"
                                            " release request"};

/* For reentrancy locking when releasing handlers */
static int Poll_Depth = 0;

#define CHECK() if (!global_id_block) return &err_UnInit

_kernel_oserror *thevent_poll(int *code, WimpPollBlock *block, void *poll_word)
{
  int lcode;
  WimpPollBlock *lblock;
  IdBlock id_block;
  _kernel_oserror *e;

  lblock = block ? block : malloc(sizeof(WimpPollBlock));
  if (!lblock)
    return &err_PollMem;
  e = wimp_poll((int) Mask, lblock, poll_word, &lcode);
  /* Why is mask int in wimplib.h and unsigned in event.h ? */
  if (e)
  {
    if (!block)
      free(lblock);
    return e;
  }
  if (code)
    *code = lcode;
  id_block = *global_id_block;
  e = thevent_process(lcode, lblock, &id_block);
  if (!block)
    free(lblock);
  return e;
}

_kernel_oserror *thevent_poll_idle(int *code, WimpPollBlock *block,
                                   unsigned int earliest, void *poll_word)
{
  int lcode;
  WimpPollBlock *lblock;
  IdBlock id_block;
  _kernel_oserror *e;

  lblock = block ? block : malloc(sizeof(WimpPollBlock));
  if (!lblock)
    return &err_PollMem;
  e = wimp_pollidle((int) Mask, lblock, (int) earliest, poll_word, &lcode);
  /* ...and the same goes for time/earliest <sigh> */
  if (e)
  {
    if (!block)
      free(lblock);
    return e;
  }
  if (code)
    *code = lcode;
  id_block = *global_id_block;
  e = thevent_process(lcode, lblock, &id_block);
  if (!block)
    free(lblock);
  return e;
}

typedef enum {
  HandleWimp, HandleToolbox, HandleMessage
} handle_type;

static int compare_handler(Handler *handler, ObjectId id, int code,
                           HandlerFn fn, void *handle)
{
  /* inherently platform specific so OK to use dodgy handler comparison */
  return !(handler->id == id && handler->code == code &&
           handler->fn.wimp == fn.wimp && handler->handle == handle &&
           handler->active);
}

static int find_handler(int code, WimpPollBlock *event, IdBlock *id_block,
                        HandlerList *list, handle_type which)
{
  Handler *handler;

  for (handler = list->first; handler; handler = handler->next)
  {
    if ((handler->id == id_block->self_id || handler->id == -1) &&
        handler->code == code && handler->active)
    {
      switch (which)
      {
        case HandleWimp:
          if ((*handler->fn.wimp)(code, event, id_block, handler->handle))
          {
            return 1;
          }
          break;
        case HandleToolbox:
          if ((*handler->fn.toolbox)(code, (ToolboxEvent *) event,
              id_block, handler->handle))
          {
            return 1;
          }
          break;
        case HandleMessage:
          if ((*handler->fn.message)(&event->user_message, handler->handle))
          {
            return 1;
          }
          break;
      }
    }
  }
  return 0;
}

static void release_disused(HandlerList *list)
{
  Handler *handler;

  for (handler = list->first; handler; )
  {
    Handler *next = handler->next;

    if (!handler->active)
    {
      if (handler->prev)
        handler->prev->next = handler->next;
      else
        list->first = handler->next;
      if (handler->next)
        handler->next->prev = handler->prev;
      else
        list->last = handler->prev;
      free(handler);
    }
    handler = next;
  }
}

_kernel_oserror *thevent_process(int code, WimpPollBlock *block,
                                 IdBlock *id_block)
{
  ++Poll_Depth;

  switch (code)
  {
    case Wimp_EToolboxEvent:
      if (!find_handler(((ToolboxEvent *) block)->hdr.event_code,
                        block, id_block, &specific_toolbox, HandleToolbox))
        find_handler(((ToolboxEvent *) block)->hdr.event_code,
                     block, id_block, &any_toolbox, HandleToolbox);
        break;
    case Wimp_EUserMessage:
    case Wimp_EUserMessageRecorded:
      id_block->self_id = -1;
      find_handler(block->user_message.hdr.action_code, block, id_block,
                   &any_message, HandleMessage);
      break;
    default:
      if (!find_handler(code, block, id_block, &specific_wimp, HandleWimp))
        find_handler(code, block, id_block, &any_wimp, HandleWimp);
      break;
  }

  if (!--Poll_Depth)
  {
    release_disused(&specific_wimp);
    release_disused(&any_wimp);
    release_disused(&specific_toolbox);
    release_disused(&any_toolbox);
    release_disused(&any_message);
  }

  return 0;
}


_kernel_oserror *thevent_set_mask(unsigned int mask, unsigned int value)
{
  Mask = Mask & ~mask | value;
  return 0;
}

_kernel_oserror *thevent_get_mask(unsigned int *mask)
{
  if (mask)
    *mask = Mask;
  return 0;
}

static _kernel_oserror *prepare_handler(ObjectId id, int code, HandlerFn fn,
                                        void *handle, HandlerList *list, int lp)
{
  Handler *handler = malloc(sizeof(Handler));

  if (!handler)
    return &err_HandlerMem;


  handler->id = id;
  handler->code = code;
  handler->fn = fn;
  handler->handle = handle;
  handler->active = 1;

  if (lp)
  {
    handler->prev = list->last;
    if (handler->prev)
      handler->prev->next = handler;
    handler->next = 0;
    list->last = handler;
    if (!list->first)
      list->first = list->last;
  }
  else
  {
    handler->next = list->first;
    if (handler->next)
      handler->next->prev = handler;
    handler->prev = 0;
    list->first = handler;
    if (!list->last)
      list->last = list->first;
  }

  return 0;
}

_kernel_oserror *thevent_claim_wimp(ObjectId id, int event_code,
                                    thevent_WimpHandler *fn, void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.wimp = fn;
  return prepare_handler(id, event_code, genfn, handle,
                         id == -1 ? &any_wimp : &specific_wimp, 0);
}

_kernel_oserror *thevent_release_wimp(ObjectId id, int event_code,
                                      thevent_WimpHandler *fn, void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.wimp = fn;
  for (handler = (id == -1 ? &any_wimp : &specific_wimp)->first;
       handler; handler = handler->next)
  {
    if (!compare_handler(handler, id, event_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

_kernel_oserror *thevent_claim_toolbox(ObjectId id, int event_code,
                                       thevent_ToolboxHandler *fn, void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.toolbox = fn;
  return prepare_handler(id, event_code, genfn, handle,
                         id == -1 ? &any_toolbox : &specific_toolbox, 0);
}

_kernel_oserror *thevent_release_toolbox(ObjectId id, int event_code,
                                         thevent_ToolboxHandler *fn,
                                         void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.toolbox = fn;
  for (handler = (id == -1 ? &any_toolbox : &specific_toolbox)->first;
       handler; handler = handler->next)
  {
    if (!compare_handler(handler, id, event_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

_kernel_oserror *thevent_claim_message(int message_code,
                                       thevent_MessageHandler *fn,
                                       void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.message = fn;
  return prepare_handler(-1, message_code, genfn, handle, &any_message, 0);
}

_kernel_oserror *thevent_release_message(int message_code,
                                         thevent_MessageHandler *fn,
                                         void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.message = fn;
  for (handler = any_message.first; handler; handler = handler->next)
  {
    if (!compare_handler(handler, -1, message_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

#ifdef thevent_EXTRA
_kernel_oserror *thevent_claim_wimp_lp(ObjectId id, int event_code,
                                       thevent_WimpHandler *fn, void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.wimp = fn;
  return prepare_handler(id, event_code, genfn, handle,
                         id == -1 ? &any_wimp : &specific_wimp, 1);
}

_kernel_oserror *thevent_release_wimp_lp(ObjectId id, int event_code,
                                         thevent_WimpHandler *fn, void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.wimp = fn;
  for (handler = (id == -1 ? &any_wimp : &specific_wimp)->last;
       handler; handler = handler->prev)
  {
    if (!compare_handler(handler, id, event_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

_kernel_oserror *thevent_claim_toolbox_lp(ObjectId id, int event_code,
                                          thevent_ToolboxHandler *fn,
                                          void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.toolbox = fn;
  return prepare_handler(id, event_code, genfn, handle,
                         id == -1 ? &any_toolbox : &specific_toolbox, 1);
}

_kernel_oserror *thevent_release_toolbox_lp(ObjectId id, int event_code,
                                            thevent_ToolboxHandler *fn,
                                            void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.toolbox = fn;
  for (handler = (id == -1 ? &any_toolbox : &specific_toolbox)->last;
       handler; handler = handler->prev)
  {
    if (!compare_handler(handler, id, event_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

_kernel_oserror *thevent_claim_message_lp(int message_code,
                                          thevent_MessageHandler *fn,
                                          void *handle)
{
  HandlerFn genfn;

  CHECK();
  genfn.message = fn;
  return prepare_handler(-1, message_code, genfn, handle, &any_message, 1);
}

_kernel_oserror *thevent_release_message_lp(int message_code,
                                            thevent_MessageHandler *fn,
                                            void *handle)
{
  Handler *handler;
  HandlerFn genfn;

  genfn.message = fn;
  for (handler = any_message.last; handler; handler = handler->prev)
  {
    if (!compare_handler(handler, -1, message_code, genfn, handle))
    {
      handler->active = 0;
      return 0;
    }
  }
  return &err_BadRelease;
}

_kernel_oserror *thevent_release_object(ObjectId id)
{
  Handler *handler;

  for (handler = specific_wimp.first; handler; handler = handler->next)
  {
    if (handler->id == id)
      handler->active = 0;
  }
  for (handler = specific_toolbox.first; handler; handler = handler->next)
  {
    if (handler->id == id)
      handler->active = 0;
  }

  return 0;
}

#endif


_kernel_oserror *thevent_initialise(IdBlock *block)
{
  if (global_id_block)
    return &err_Already;

  global_id_block = block;
  return 0;
}
