/*callback.c - dynamically extensible multi-way, multi-level
   switch facility*/

/*OSLib---efficient, type-safe, transparent, extensible,\n"
   register-safe A P I coverage of RISC O S*/
/*Copyright  1994 Jonathan Coxhead*/

/*
      OSLib 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 1, or (at your option)
   any later version.

      OSLib 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 programme; if not, write to the Free Software
   Foundation, Inc, 675 Mass Ave, Cambridge, MA 02139, U S A.
*/

   /* First implementation: hold callbacks in a tree structure reflecting
      their structure.

      Note: since all callbacks are called back with non-null |unclaimed|
      pointer, and this is initialised to TRUE, you never need to assign TRUE
      to |unclaimed|. You can assign FALSE to it without checking for
      nullness, too.
   */

/*From CLib*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

/*From OSLib*/
#include "oslib/types.h"

/*From Support*/
#include "callback.h"
#include "m.h"
#include "riscos.h"
#include "trace.h"

#ifdef TRACE
/*#define XTRACE*/
#endif

/* Useful types needed by |callback_|

      Type Fn == (the type of a callback function);
      Type SH == (the type of the static handle);
      Type DH == (the type of the dynamic handle)
      Type Level == List [Fn  SH]  List [Int  Level];

   implemented in C via

      Type Function == List [Fn  SH];
      Type Entry == List [Int  Level];

   (where List [T] == Opt [T  List [T]])
*/

struct callback_l {struct Level *root;};

typedef
   struct Function
   {
      callback_fn *fn;
      void *sh;
      struct Function *next;
   }
   *Function;

typedef
   struct Entry
   {
      int key;
      struct Level *level;
      struct Entry *next;
   }
   *Entry;

typedef
   struct Level
   {
      struct Function *fns;
      struct Entry *list;
   }
   *Level;

#if TRACE
/*------------------------------------------------------------------------*/

/*The name of a function.*/

static char *Function_Name
(
   callback_fn *fn
)
{
   char *name = (char *) fn - 4;

   static char Fn [80 + 1];

   if (name [3] == 0xFF)
      riscos_strncpy (Fn, name - *name, 80);
   else
      sprintf (Fn, "(unnamed at 0x%X)" _ (unsigned) fn);

   return Fn;
}
#endif
/*------------------------------------------------------------------------*/

os_error *callback_new
(
   callback_l *l_out
)
{
   callback_l l;
   os_error *error = NULL;

   tracef ("callback_new\n");
   if ((l = m_ALLOC (sizeof *l)) == NULL)
   {
      error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
      goto finish;
   }

   l->root = NULL;

   if (l_out != NULL) *l_out = l;

finish:
   return error;
}
/*------------------------------------------------------------------------*/

static void Delete_Function
(
   Function f
)
{
   if (f != NULL)
   {
#ifdef XTRACE
      tracef ("Delete_Function\n");
#endif

      Delete_Function (f->next);
      m_FREE (f, sizeof *f);
   }
}
/*------------------------------------------------------------------------*/

static void Delete_Level (Level);

/*------------------------------------------------------------------------*/
void Delete_Entry
(
   Entry e
)
{
   if (e != NULL)
   {
#ifdef XTRACE
      tracef ("Delete_Entry\n");
#endif

      Delete_Level (e->level);
      Delete_Entry (e->next);
      m_FREE (e, sizeof *e);
   }
}
/*------------------------------------------------------------------------*/

void Delete_Level
(
   Level level
)
{
   if (level != NULL)
   {
#ifdef XTRACE
      tracef ("Delete_Level\n");
#endif

      Delete_Function (level->fns);
      Delete_Entry (level->list);
      m_FREE (level, sizeof *level);
   }
}
/*------------------------------------------------------------------------*/

os_error *callback_delete
(
   callback_l l
)
{
   tracef ("callback_delete\n");

   if (l != NULL)
   {
      Delete_Level (l->root);
      m_FREE (l, sizeof *l);
   }

   return NULL;
}
/*------------------------------------------------------------------------*/
static os_error *Callback
(
   void  *dh,
   Level  level,
   osbool  *unclaimed,
   int    limit,
   int   *k
)
{
   Function  f;
   osbool    u = TRUE;
   os_error *error = NULL;

#ifdef XTRACE
   {
      int i;

      tracef ("Callback: dh 0x%X, level 0x%X, limit %d, key {" _ dh _ level _
            limit);

      for (i = 0; i < limit; i++)
         trace_f (NULL _ 0 _ "%s%d" _ i == 0? "": ", " _ k [i]);
      trace_f (NULL _ 0 _ "} ...\n");
   }
#endif

   if (level != NULL)
   {
      if (limit > 0)
      {
         /*Try to match this key.*/
         Entry e;

         /*It's important that we process the deepest ones first, as they are
            the most specialised for their purpose (all in a highly abstract
            sense ...).*/
         for (e = level->list; e != NULL; e = e->next)
            if (e->key == *k)
            {
               if ((error = Callback (dh, e->level, &u, limit - 1,
                     k + 1)) != NULL)
                  goto finish;

               if (!u) break;
            }
      }

      if (u)
         /*Reached the end of the line - call all the functions until one
            handles the keys.*/
         for (f = level->fns; f != NULL; f = f->next)
         {
            tracef ("callback %s (0x%X, 0x%X, 0x%X)\n" _
                  Function_Name (f->fn) _ f->sh _ dh _ &u);
            if ((error = (*f->fn) (f->sh, dh, &u)) != NULL)
               goto finish;
            tracef ("callback %s\n" _ u? "unclaimed": "claimed");

            if (!u) break;
         }
   }

   if (!u) *unclaimed = FALSE;

finish:
   return error;
}
/*------------------------------------------------------------------------*/

os_error *callback
(
   callback_l  l,
   void       *dh,
   osbool     *unclaimed,
   int         limit,
   ...
)
{
   /*Makes the non-portable assumption that the arguments after |limit| are
      on the stack in order of increasing address.*/

   osbool u = TRUE;
   os_error *error = NULL;

   tracef ("callback ...\n");
   if ((error = Callback (dh, l->root, &u, limit, &limit + 1)) != NULL)
      goto finish;

   if (unclaimed != NULL) *unclaimed = u;

finish:
   return error;
}
/*------------------------------------------------------------------------*/

os_error *callback_register
(
   callback_l   l,
   callback_fn *fn,
   void        *sh,
   int          limit,
    ...
)
{
   /*Makes the non-portable assumption that the arguments after |limit| are
      on the stack in order of increasing address.*/

   Level *level;
   int i, *k = &limit + 1;
   os_error *error = NULL;

   tracef ("callback_register\n");

   /*Descend the hierarchy, creating new |Level|'s if necessary.*/
   level = &l->root;

   for (i = 0; i < limit; i++)
   {
      Entry e;
      osbool found = FALSE;

      if (*level == NULL)
      {
         /*Need a new one for this level. First create the new |Level|.*/
         Level t;

         if ((t = m_ALLOC (sizeof *t)) == NULL)
         {
            error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
            goto finish;
         }
         t->fns  = NULL;
         t->list = NULL;

         *level = t;
      }

      /*Now look along the current level for the given key. This won't
         take long if the level was just created.*/
      for (e = (*level)->list; e != NULL; e = e->next)
         if (e->key == k [i])
         {
            found = TRUE;
            break;
         }

      if (!found)
      {
         /*Must create a new entry for this key.*/
         if ((e = m_ALLOC (sizeof *e)) == NULL)
         {
            error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
            goto finish;
         }
         e->key   = k [i];
         e->level = NULL;
         e->next  = (*level)->list;

         (*level)->list = e;
      }

      level = &e->level;
   }

   if (*level == NULL)
   {
      /*Need a new one here too.*/
      Level t;

      if ((t = m_ALLOC (sizeof *t)) == NULL)
      {
         error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
         goto finish;
      }
      t->fns  = NULL;
      t->list = NULL;

      *level = t;
   }

   /*Append the new function to the function list for |level|.*/
#if 1 /*this adds it at the end*/
   {
      Function *f;

      for (f = &(*level)->fns; *f != NULL; f = &(*f)->next)
         ;
      if ((*f = m_ALLOC (sizeof **f)) == NULL)
      {
         error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
         goto finish;
      }

      (*f)->fn   = fn;
      (*f)->sh   = sh;
      (*f)->next = NULL;
   }
#else /*this adds it at the beginning*/
   {
      Function f;

      if ((f = m_ALLOC (sizeof *f)) == NULL)
      {  error = riscos_error_lookup (os_GLOBAL_NO_MEM, "NoMem");
         goto finish;
      }
      f->fn   = fn;
      f->sh   = sh;
      f->next = (*level)->fns;

      (*level)->fns = f;
   }
#endif

finish:
   return error;
}
/*------------------------------------------------------------------------*/

static void Deregister_Level (Level *l, void *sh),
      Deregister_Entry (Entry *e, void *sh);

/*------------------------------------------------------------------------*/
static void Deregister_Function
(
   Function *ff,
   void     *sh
)
{
   Function f = *ff;

   if (f != NULL)
   {
#ifdef XTRACE
      tracef ("Deregister_Function\n");
#endif

      Deregister_Function (&f->next, sh);

      if (f->sh == sh)
      {
         *ff = f->next;
         m_FREE (f, sizeof *f);
      }
   }
}
/*------------------------------------------------------------------------*/

void Deregister_Entry (Entry *ee, void *sh)
{
   Entry e = *ee;

   if (e != NULL)
   {
#ifdef XTRACE
      tracef ("Deregister_Entry\n");
#endif

      Deregister_Level (&e->level, sh);
      Deregister_Entry (&e->next,  sh);

      if (e->level == NULL)
      {
         *ee = e->next;
         m_FREE (e, sizeof *e);
      }
   }
}
/*------------------------------------------------------------------------*/

void Deregister_Level
(
   Level *ll,
   void  *sh
)
{
   Level l = *ll;

   if (l != NULL)
   {
#ifdef XTRACE
      tracef ("Deregister_Level\n");
#endif

      Deregister_Function (&l->fns,  sh);
      Deregister_Entry    (&l->list, sh);

      if (l->fns == NULL && l->list == NULL)
      {
         *ll = NULL;
         m_FREE (l, sizeof *l);
      }
   }
}
/*------------------------------------------------------------------------*/

os_error *callback_deregister
(
   callback_l  l,
   void       *sh,
   int         limit,
   ...
)
{
   /*Makes the non-portable assumption that the arguments after |limit| are
      on the stack in order of increasing address.*/

   /*No error is flagged if the given (fn, handle, keys) combination does
      not exist.*/

   /*We only free the |Function|'s that record this callback, though it
      would be possible to free the |Level| that they depend on (provided
      that there were no sub-|Level|'s or other |Function|'s).*/

   Level    *ll;
   Entry    *ee = NULL;
   int       i, *k = &limit + 1;
   os_error *error = NULL;

   tracef ("callback_deregister\n");

   /*Descend the hierarchy.*/
   ll = &l->root;

   for (i = 0; i < limit && *ll != NULL; i++)
   {
      /*Look along the current level for the given key.*/
      for (ee = &(*ll)->list; *ee != NULL; ee = &(*ee)->next)
         if ((*ee)->key == k [i])
            break;

      if (*ee == NULL)
         goto finish;

      ll = &(*ee)->level;
   }

   Deregister_Level (ll, sh);

   if (*ll == NULL && ee != NULL)
   {
      /*We've just killed the level of a certain entry, so we should remove
         it too.*/
      Entry e = *ee;

      *ee = e->next;
      m_FREE (e, sizeof *e);
   }

finish:
   return error;
}
/*------------------------------------------------------------------------*/

#if TRACE
static void Trace_Level (Level, int);

void Trace_Entry
(
   Entry entry,
   int   indent
)
{
   if (entry != NULL)
   {
      trace_f (NULL _ 0 _ "%*s%d:\n" _ indent _ "" _ entry->key);
      Trace_Level (entry->level, indent + 3);
      Trace_Entry (entry->next, indent);
   }
}
/*------------------------------------------------------------------------*/

static void Trace_Function
(
   Function fns,
   int indent
)
{
   Function f;
   osbool   first = TRUE;

   for (f = fns; f != NULL; f = f->next)
   {
      if (first)
      {
         trace_f (NULL _ 0 _ "%*sFunctions:" _ indent _ "");
         first = FALSE;
      }
      else
         trace_f (NULL _ 0 _ ", ");

      trace_f (NULL _ 0 _ " %s (0x%X,)" _ Function_Name (f->fn) _ f->sh);
   }

   if (first)
      trace_f (NULL _ 0 _ "%*sNo functions" _ indent _ "");

   trace_f (NULL _ 0 _ "\n");
}
/*------------------------------------------------------------------------*/
void Trace_Level
(
   Level level,
   int indent
)
{
   if (level != NULL)
   {
      Trace_Function (level->fns, indent);
      Trace_Entry (level->list, indent);
   }
}
/*------------------------------------------------------------------------*/

void callback_trace
(
   callback_l l
)
{
   tracef ("callback_trace\n");

   if (l != NULL) Trace_Level (l->root, 0);
}
#endif
