/*

  sshadt.c

  Author: Antti Huima <huima@ssh.fi>

  Copyright (c) 1999, 2000 SSH Communications Security, Finland
  All rights reserved.

  Created Wed Sep  8 17:41:05 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"
#include "sshdebug.h"

#define SSH_DEBUG_MODULE "SshADT"

/********************************************************** Default methods. */

int ssh_adt_default_compare(const void *obj1, const void *obj2,
                            void *context)
{
  if (obj1 < obj2) return -1;
  if (obj1 > obj2) return 1;
  return 0;
}

void *ssh_adt_default_duplicate(void *obj, void *context)
{
  return obj;
}

void ssh_adt_default_copy(void *dst, size_t d_size,
                          const void *src, void *context)
{
  SSH_DEBUG(9, ("Copying %d bytes from %p to %p.", d_size));

  memcpy(dst, src, d_size);
}

void ssh_adt_default_destroy(void *obj, void *context)
{
  return;
}

void ssh_adt_default_init(void *obj, size_t size, void *context)
{
  memset(obj, 0, size);
}

void ssh_adt_no_init(void *data, void *context)
{
  return;
}

SshUInt32 ssh_adt_default_hash(const void *obj, void *context)
{
  return (SshUInt32)obj;
}

/**************************************** Initialize and destroy containers. */

static void set_default_values(SshADTStandardFields *f)
{
  f->app_methods.compare = ssh_adt_default_compare;
  f->app_methods.copy = ssh_adt_default_copy;
  f->app_methods.duplicate = ssh_adt_default_duplicate;
  f->app_methods.destr = ssh_adt_default_destroy;
  f->app_methods.init = ssh_adt_default_init;
  f->app_methods.hash = ssh_adt_default_hash;
  f->app_methods.context = NULL;
}

static void init_toplevel_container(SshADTContainer c,
                                    SshADTContainerType type,
                                    va_list args)
{
  SshADTArgumentType t;
  SshADTParameters pars;
  SshADTContainerPars *ptr;
  SshADTStaticData *static_data;

  SSH_PRECOND(type != NULL);

  memset(&pars, 0, sizeof(pars));

  set_default_values(&(pars.top.f));
  set_default_values(&(pars.domain.f));
  set_default_values(&(pars.range.f));

  ptr = &(pars.top);

  ptr->type = type;

  while ((t = va_arg(args, SshADTArgumentType)) != SSH_ADT_ARGS_END)
    {
      switch (t)
        {
        case SSH_ADT_CONTEXT:
          ptr->f.app_methods.context = va_arg(args, void *);
          break;

        case SSH_ADT_COMPARE:
          ptr->f.app_methods.compare = va_arg(args, SshADTCompareFunc);
          break;

        case SSH_ADT_COPY:
          ptr->f.app_methods.copy = va_arg(args, SshADTCopyFunc);
          break;

        case SSH_ADT_DUPLICATE:
          ptr->f.app_methods.duplicate = va_arg(args, SshADTDuplicateFunc);
          break;

        case SSH_ADT_DESTROY:
          ptr->f.app_methods.destr = va_arg(args, SshADTDestroyFunc);
          break;

        case SSH_ADT_INIT:
          ptr->f.app_methods.init = va_arg(args, SshADTInitFunc);
          break;

        case SSH_ADT_HASH:
          ptr->f.app_methods.hash = va_arg(args, SshADTHashFunc);
          break;

        case SSH_ADT_SIZE:
          ptr->flags |= SSH_ADT_FLAG_ALLOCATE;
          ptr->f.default_object_size = va_arg(args, size_t);
          break;

        case SSH_ADT_FASTALLOC:
          (void)va_arg(args, int);
          break;
          
        case SSH_ADT_SIZE_HINT:
          (void)va_arg(args, long);
          break;
          
        case SSH_ADT_HEADER:
          ptr->flags |= SSH_ADT_FLAG_CONTAINED_HEADER;     
          ptr->f.header_offset = va_arg(args, SshUInt32);
          break;

        default:
          SSH_NOTREACHED;
        }
    }

#ifdef _KERNEL
  SSH_ASSERT(ptr->flags & SSH_ADT_FLAG_CONTAINED_HEADER);
#endif

  static_data = pars.top.type;
  
  (*(static_data->methods.container_init))(c, ptr);
}

SshADTContainer ssh_adt_create_generic(SshADTContainerType type, ...)
{
  va_list args;
  SshADTContainer c;

#ifdef _KERNEL
  if (!(c = ssh_kmalloc(sizeof(*c))))
    return NULL;
#else /* _KERNEL */
  c = ssh_xmalloc(sizeof(*c));
#endif /* _KERNEL */

  va_start(args, type);
  init_toplevel_container(c, type, args);
  va_end(args);

  return c;
}

void ssh_adt_destroy(SshADTContainer container)
{
  (*(container->static_data->methods.destr))(container);
#ifdef _KERNEL
  ssh_kfree(container);
#else /* _KERNEL */
  ssh_xfree(container);
#endif /* _KERNEL */
}

#if 0
void ssh_adt_init_generic(SshADTContainer container,
                          SshADTContainerType type, ...)
{
  va_list args;

  va_start(args, type);
  init_toplevel_container(container, type, args);
  va_end(args);
}

void ssh_adt_uninit(SshADTContainer container)
{
  SSH_ADT_CALL_DESTROY_HOOK(container, destr);
  SSH_ADT_CALL(container, FALSE, container_uninit, (container));
  ssh_xfree(container->hooks);
}
#endif

/************************************************************* Record magic. */

void *ssh_adt_duplicate_object(SshADTContainer container, void *object)
{
  return
    SSH_ADT_CALL_APP_MANDATORY(container, duplicate,
                               (object, SSH_ADT_APPCTX(container)));
}

/* Should be included in the shortcuts. */
#if 0
void *ssh_adt_detach_object(SshADTContainer container, void *object)
{
  SshADTHandle handle = ssh_adt_get_handle_to(container, object);
  return ssh_adt_detach(container, handle);
}

void ssh_adt_delete_object(SshADTContainer container, void *object)
{
  SshADTHandle handle = ssh_adt_get_handle_to(container, object);
  ssh_adt_delete(container, handle);
}
#endif

/************************* Genlist functions, for some internal use perhaps. */

void ssh_adt_genlist_merge(void *from_list, void **to_list,
                           int next_ptr_offset)
{
  while (*to_list != NULL)
    {
      *to_list = (unsigned char *)(*to_list) + next_ptr_offset;
    }
}

void ssh_adt_genlist_add(void **listptr, void *object, int next_ptr_offset)
{
  unsigned char *o = object;
  *((void **)(o + next_ptr_offset)) = *listptr;
  *listptr = object;
}

void ssh_adt_genlist_remove(void **listptr, void *object, int next_ptr_offset)
{
  while ((*listptr) != object)
    {
      listptr = (void **)((unsigned char *)(*listptr) + next_ptr_offset);
    }
  *listptr = *((void **)((unsigned char *)object + next_ptr_offset));  
}

/******************************************************************** Hooks. */

void ssh_adt_initialize_hooks(SshADTContainer container)
{
  ssh_xfree(container->hooks);
  container->hooks = ssh_xmalloc(sizeof(*container->hooks));
  container->hooks->insert      = NULL;
  container->hooks->detach      = NULL;
  container->hooks->map         = NULL;
  container->hooks->unmap       = NULL;
  container->hooks->reallocated = NULL;
  container->hooks->destr       = NULL;
}

void ssh_adt_uninitialize_hooks(SshADTContainer container)
{
  ssh_xfree(container->hooks);
  container->hooks = NULL;
}

#ifndef SSH_ADT_WITH_MACRO_INTERFACE

#define SSH_ADT_INTERNAL_MACROS
#include "sshadt_impls.h"
#undef SSH_ADT_INTERNAL_MACROS

/****************************************************** Non-macro Interface. */

#define SSH_ADT_ASSERT_CONTAINER SSH_PRECOND(container != NULL)
#define SSH_ADT_ASSERT_EXTERNAL  \
SSH_PRECOND(SSH_ADT_DEFAULT_SIZE(container) == 0)
#define SSH_ADT_ASSERT_INTERNAL  \
SSH_PRECOND(SSH_ADT_DEFAULT_SIZE(container) != 0)
#define SSH_ADT_ASSERT_HANDLE    SSH_PRECOND(handle != SSH_ADT_INVALID)
#define SSH_ADT_ASSERT_OBJECT    SSH_PRECOND(object != NULL)

size_t ssh_adt_default_size(SshADTContainer container)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  return SSH_ADT_DEFAULT_SIZE(container);
}

void ssh_adt_clear(SshADTContainer container)
{
  SSH_ADT_ASSERT_CONTAINER;
  ssh_adt_clear__(container);
}

SshADTHandle ssh_adt_insert_at(SshADTContainer container,
                               SshADTRelativeLocation location,
                               SshADTHandle handle,
                               void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert_at__(container, location, handle, object);
}

SshADTHandle ssh_adt_insert_to(SshADTContainer container,
                               SshADTAbsoluteLocation location,
                               void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert_to__(container, location, object);
}

SshADTHandle ssh_adt_insert(SshADTContainer container,
                            void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert_to(container, SSH_ADT_DEFAULT, object);
}

SshADTHandle ssh_adt_duplicate_at(SshADTContainer container,
                                  SshADTRelativeLocation location,
                                  SshADTHandle handle,
                                  void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert_at(container, location, handle,
                           ssh_adt_duplicate_object(container, object));
}

SshADTHandle ssh_adt_duplicate_to(SshADTContainer container,
                                  SshADTAbsoluteLocation location,
                                  void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert_to(container, location,
                           ssh_adt_duplicate_object(container, object));
}

SshADTHandle ssh_adt_duplicate(SshADTContainer container,
                               void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_insert(container,
                        ssh_adt_duplicate_object(container, object));
}

/* 2. Internally allocated objects. */

SshADTHandle ssh_adt_alloc_n_at(SshADTContainer container,
                                SshADTRelativeLocation location,
                                SshADTHandle handle,
                                size_t size)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_alloc_n_at__(container, location, handle, size);
}

SshADTHandle ssh_adt_alloc_n_to(SshADTContainer container,
                                SshADTAbsoluteLocation location,
                                size_t size)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  return ssh_adt_alloc_n_to__(container, location, size);
}

SshADTHandle ssh_adt_alloc_at(SshADTContainer container,
                              SshADTRelativeLocation location,
                              SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_alloc_n_at(container, location,
                            handle, SSH_ADT_DEFAULT_SIZE(container));
}

SshADTHandle ssh_adt_alloc_to(SshADTContainer container,
                              SshADTAbsoluteLocation location)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  return ssh_adt_alloc_n_to(container, location,
                            SSH_ADT_DEFAULT_SIZE(container));
}

SshADTHandle ssh_adt_alloc_n(SshADTContainer container,
                             size_t size)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  return ssh_adt_alloc_n_to(container, SSH_ADT_DEFAULT, size);
}

SshADTHandle ssh_adt_alloc(SshADTContainer container)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  return ssh_adt_alloc_n(container, SSH_ADT_DEFAULT_SIZE(container));
}
SshADTHandle ssh_adt_put_n_at(SshADTContainer container,
                              SshADTRelativeLocation location,
                              SshADTHandle handle,
                              size_t size,
                              void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n_at__(container, location, handle, size, object);
}

SshADTHandle ssh_adt_put_n_to(SshADTContainer container,
                              SshADTAbsoluteLocation location,
                              size_t size,
                              void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n_to__(container, location, size, object);
}

SshADTHandle ssh_adt_put_at(SshADTContainer container,
                            SshADTRelativeLocation location,
                            SshADTHandle handle,
                            void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n_at(container, location, handle,
                          SSH_ADT_DEFAULT_SIZE(container), object);
}

SshADTHandle ssh_adt_put_to(SshADTContainer container,
                            SshADTAbsoluteLocation location,
                            void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n_to(container, location,
                          SSH_ADT_DEFAULT_SIZE(container), object);
}

SshADTHandle ssh_adt_put_n(SshADTContainer container,
                           size_t size,
                           void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n_to(container, SSH_ADT_DEFAULT, size, object);
}

SshADTHandle ssh_adt_put(SshADTContainer container,
                         void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_INTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_put_n(container, SSH_ADT_DEFAULT_SIZE(container), object);
}

void *ssh_adt_get(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_get__(container, handle);
}

size_t ssh_adt_num_objects(SshADTContainer container)
{
  SSH_ADT_ASSERT_CONTAINER;
  return ssh_adt_num_objects__(container);
}

SshADTHandle ssh_adt_get_handle_to(SshADTContainer container,
                                   void *object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_get_handle_to__(container, object);
}

SshADTHandle ssh_adt_get_handle_to_equal(SshADTContainer container, SshADTHandle object)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_OBJECT;
  return ssh_adt_get_handle_to_equal__(container, object);
}

SshADTHandle ssh_adt_next(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_next__(container, handle);
}

SshADTHandle ssh_adt_previous(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_previous__(container, handle);
}

SshADTHandle ssh_adt_get_handle_to_location(SshADTContainer container,
                                            SshADTAbsoluteLocation location)
{
  SSH_ADT_ASSERT_CONTAINER;
  return ssh_adt_get_handle_to_location__(container, location);
}

void *ssh_adt_detach(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_detach__(container, handle);
}

/* This is called from inside the container implementations when
   objects are detached just prior to destroying them. So do not
   assert for externally allocated objects. */
void *ssh_adt_detach_i(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_detach__(container, handle);
}

void *ssh_adt_detach_from(SshADTContainer container,
                          SshADTAbsoluteLocation location)
{
  SshADTHandle handle;
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  handle = ssh_adt_get_handle_to_location(container, location);
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_detach(container, handle);
}

void *ssh_adt_detach_object(SshADTContainer container,
                            void *object)
{
  SshADTHandle handle;
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_EXTERNAL;
  SSH_ADT_ASSERT_OBJECT;
  handle = ssh_adt_get_handle_to(container, object);
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_detach(container, handle);
}

void ssh_adt_delete(SshADTContainer container, SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  ssh_adt_delete__(container, handle);
}

void ssh_adt_delete_from(SshADTContainer container,
                         SshADTAbsoluteLocation location)
{
  SshADTHandle handle;
  SSH_ADT_ASSERT_CONTAINER;
  handle = ssh_adt_get_handle_to_location(container, location);
  SSH_ADT_ASSERT_HANDLE;
  ssh_adt_delete(container, handle);
}

void ssh_adt_delete_object(SshADTContainer container,
                           void *object)
{
  SshADTHandle handle;
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_OBJECT;
  handle = ssh_adt_get_handle_to(container, object);
  SSH_ADT_ASSERT_HANDLE;
  ssh_adt_delete(container, handle);
}

SshADTHandle ssh_adt_enumerate_start(SshADTContainer container)
{
  SSH_ADT_ASSERT_CONTAINER;
  return ssh_adt_enumerate_start__(container);
}

SshADTHandle ssh_adt_enumerate_next(SshADTContainer container,
                                    SshADTHandle handle)
{
  SSH_ADT_ASSERT_CONTAINER;
  SSH_ADT_ASSERT_HANDLE;
  return ssh_adt_enumerate_next__(container, handle);
}

#endif
