/* Functions for dealing with elt unions
   Copyright (C) 1993 Free Software Foundation, Inc.

   Written by:  R. Andrew McCallum <mccallum@cs.rochester.edu>
   Dept. of Computer Science, U. of Rochester, Rochester, NY  14627

   This file is part of the GNU Objective-C Collection library.

   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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ 

#include <coll/eltfuncs.h>
#include <objc/Object.h>
#include <objc/objc-api.h>
//#include <objc/hash.h>
//#include <coll/collstd.h>	/* for -compare: */

/* Is there a better (shorter) way to specify all this junk? */

unsigned int
elt_hash_int (elt key)
{
  return (key.int_u);
}

int
elt_compare_ints (elt k1, elt k2)
{
  if (k1.int_u == k2.int_u)
    return 0;
  else if (k1.int_u > k2.int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_unsigned_int (elt key)
{
  return (key.unsigned_int_u);
}

int
elt_compare_unsigned_ints (elt k1, elt k2)
{
  if (k1.unsigned_int_u == k2.unsigned_int_u)
    return 0;
  else if (k1.unsigned_int_u > k2.unsigned_int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_long_int (elt key)
{
  return ((unsigned int)key.long_int_u);
}

int
elt_compare_long_ints (elt k1, elt k2)
{
  if (k1.long_int_u == k2.long_int_u)
    return 0;
  else if (k1.long_int_u > k2.long_int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_unsigned_long_int (elt key)
{
  return ((unsigned int)key.unsigned_long_int_u);
}

int
elt_compare_unsigned_long_ints (elt k1, elt k2)
{
  if (k1.unsigned_long_int_u == k2.unsigned_long_int_u)
    return 0;
  else if (k1.unsigned_long_int_u > k2.unsigned_long_int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_char (elt key)
{
  return ((unsigned int)key.char_u);
}

int
elt_compare_chars (elt k1, elt k2)
{
  if (k1.char_u == k2.char_u)
    return 0;
  else if (k1.char_u > k2.char_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_unsigned_char (elt key)
{
  return ((unsigned int)key.unsigned_char_u);
}

int
elt_compare_unsigned_chars (elt k1, elt k2)
{
  if (k1.unsigned_char_u == k2.unsigned_char_u)
    return 0;
  else if (k1.unsigned_char_u > k2.unsigned_char_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_short (elt key)
{
  return ((unsigned int)key.short_int_u);
}

int
elt_compare_shorts (elt k1, elt k2)
{
  if (k1.short_int_u == k2.short_int_u)
    return 0;
  else if (k1.short_int_u > k2.short_int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_unsigned_short (elt key)
{
  return ((unsigned int)key.unsigned_short_int_u);
}

int
elt_compare_unsigned_shorts (elt k1, elt k2)
{
  if (k1.unsigned_short_int_u == k2.unsigned_short_int_u)
    return 0;
  else if (k1.unsigned_short_int_u > k2.unsigned_short_int_u)
    return 1;
  else
    return -1;
}

unsigned int
elt_hash_float (elt key)
{
  /* There must be a better hash function for floats than this */
  return ((unsigned int)key.float_u);
}

int
elt_compare_floats (elt k1, elt k2)
{
  float diff = k1.float_u - k2.float_u;
  if (diff == 0)
    return 0;
  else if (diff > 0)
    return 1;
  else
    return -1;
}

#if (ELT_INCLUDES_DOUBLE)
unsigned int
elt_hash_double (elt key)
{
  /* There must be a better hash function for doubles than this.
     Fix this nonsense: */
  return ((unsigned int)key.double_u);
}

int
elt_compare_doubles (elt k1, elt k2)
{
  double diff = k1.double_u - k2.double_u;
  if (diff == 0)
    return 0;
  else if (diff > 0)
    return 1;
  else
    return -1;
}
#endif

int 
elt_compare_strings (elt k1, elt k2)
{
  return strcmp (k1.char_ptr_u, k2.char_ptr_u);
}

unsigned int 
elt_hash_string (elt key)
{
  unsigned int ret = 0;
  unsigned int ctr = 0;
        
  while (*key.char_ptr_u) {
    ret ^= *key.char_ptr_u++ << ctr;
    ctr = (ctr + 1) % sizeof (void *);
  }
  return ret;
}

int 
elt_compare_void_ptrs (elt k1, elt k2)
{
  if (k1.void_ptr_u == k2.void_ptr_u)
    return 0;
  else if (k1.void_ptr_u > k2.void_ptr_u)
    return 1;
  else
    return -1;
}

unsigned int 
elt_hash_void_ptr (elt key)
{
  return ((unsigned)key.void_ptr_u) / sizeof(void *);
}

unsigned int
elt_hash_object (elt key)
{
  return [key.id_u hash];
}

int
elt_compare_objects (elt k1, elt k2)
{
  return [k1.id_u compare:k2.id_u];
}

int
(*(elt_get_comparison_function(const char *encoding)))(elt,elt)
{
  switch (*encoding) 
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      return elt_compare_strings;
      
    case _C_ID: 
    case _C_CLASS:              /* isEqual: on classes works well? */
      return elt_compare_objects;
      
    case _C_PTR: 
      return elt_compare_void_ptrs;
      
    case _C_INT: 
      return elt_compare_ints;

    case _C_SEL:                /* is this where this belongs? */
    case _C_UINT: 
      return elt_compare_unsigned_ints;

    case _C_FLT:
      return elt_compare_floats;

    case _C_LNG: 
      return elt_compare_long_ints;

    case _C_ULNG: 
      return elt_compare_unsigned_long_ints;

    case _C_CHR:
      return elt_compare_chars;
      
    case _C_UCHR:
      return elt_compare_unsigned_chars;
      
    case _C_SHT:
      return elt_compare_shorts;

    case _C_USHT:
      return elt_compare_unsigned_shorts;
      
    default : 
      return 0;
    }
}

unsigned int
(*(elt_get_hash_function(const char *encoding)))(elt)
{
  switch (*encoding) 
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      return elt_hash_string;
      
    case _C_ID: 
    case _C_CLASS:              /* I can send classes isEqual:? */
      return elt_hash_object;
      
    case _C_PTR: 
      return elt_hash_void_ptr;
      
    case _C_INT: 
      return elt_hash_int;

    case _C_SEL:                /* is this where this belongs? */
    case _C_UINT: 
      return elt_hash_unsigned_int;

    case _C_FLT:
      return elt_hash_float;

    case _C_LNG: 
      return elt_hash_long_int;

    case _C_ULNG: 
      return elt_hash_unsigned_long_int;
      
    case _C_CHR:
      return elt_hash_char;
      
    case _C_UCHR:
      return elt_hash_unsigned_char;
      
    case _C_SHT:
      return elt_hash_short;

    case _C_USHT:
      return elt_hash_unsigned_short;
      
    default : 
      return 0;
    }
}


static cache_ptr __comp_func_hashtable = 0;
#define INIT_COMP_FUNC_HASHTABLE_SIZE 32

static inline void
__init_comp_func_hashtable()
{
  __comp_func_hashtable = hash_new(INIT_COMP_FUNC_HASHTABLE_SIZE, 
				   (hash_func_type)hash_ptr, 
				   (compare_func_type)compare_ptrs);
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_ints,
	   @encode(int));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_unsigned_ints,
	   @encode(unsigned int));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_long_ints,
	   @encode(long int));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_unsigned_long_ints,
	   @encode(unsigned long int));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_chars,
	   @encode(char));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_unsigned_chars,
	   @encode(unsigned char));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_shorts,
	   @encode(short));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_unsigned_shorts,
	   @encode(unsigned short));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_floats,
	   @encode(float));
#if (ELT_INCLUDES_DOUBLE)
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_doubles,
	   @encode(double));
#endif
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_strings,
	   @encode(char*));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_void_ptrs,
	   @encode(void*));
  hash_add(&__comp_func_hashtable, 
	   (void*)elt_compare_objects,
	   @encode(id));
}

const char *
elt_get_encoding(int(*comparison_function)(elt,elt))
{
  if (!__comp_func_hashtable)
    __init_comp_func_hashtable();

  return (const char *) 
    hash_value_for_key(__comp_func_hashtable,
		       (void*)comparison_function);
}


/* Is this really necessary?  Can I count on element members always 
   starting at the beginning? */

extern void *elt_get_ptr_to_member(const char *encoding, elt *anElement)
{
  switch (*encoding) 
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      return &(anElement->char_ptr_u);
      
    case _C_ID: 
    case _C_CLASS:
      return &(anElement->id_u);
      
    case _C_PTR: 
      return &(anElement->void_ptr_u);
      
    case _C_SEL:
      return &(anElement->SEL_u);

    case _C_CHR:
      return &(anElement->char_u);

    case _C_UCHR:
      return &(anElement->unsigned_char_u);
      
    case _C_SHT:
      return &(anElement->short_int_u);

    case _C_USHT:
      return &(anElement->unsigned_short_int_u);
      break;
      
    case _C_INT: 
      return &(anElement->int_u);

    case _C_UINT: 
      return &(anElement->unsigned_int_u);

    case _C_LNG: 
      return &(anElement->long_int_u);

    case _C_ULNG: 
      return &(anElement->unsigned_long_int_u);

    case _C_FLT:
      return &(anElement->float_u);
      
#if (ELT_INCLUDES_DOUBLE)
    case _C_DBL:
      return &(anElement->double_u);
#endif
      
    default : 
      return 0;
    }
}

// This printing stuff will change when we get Stream objects;

void
elt_fprintf_elt(FILE *fp, const char *encoding, elt anElement)
{
  switch (*encoding)
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      fprintf(fp, "\"%s\"", anElement.char_ptr_u);
      break;
      
    case _C_ID: 
    case _C_CLASS:
      fprintf(fp, "%s:0x%x", [anElement.id_u name], anElement.unsigned_int_u);
      break;
      
    case _C_PTR: 
      fprintf(fp, "0x%x", anElement.unsigned_int_u);
      break;
      
    case _C_SEL:
      fprintf(fp, "%s", sel_get_name(anElement.SEL_u));
      break;

    case _C_CHR:
      fprintf(fp, "%c", anElement.char_u);
      break;

    case _C_UCHR:
      fprintf(fp, "%c", anElement.unsigned_char_u);
      break;
      
    case _C_SHT:
      fprintf(fp, "%d", anElement.short_int_u);
      break;

    case _C_USHT:
      fprintf(fp, "%d", anElement.unsigned_short_int_u);
      break;
      
    case _C_INT: 
      fprintf(fp, "%d", anElement.int_u);
      break;

    case _C_UINT: 
      fprintf(fp, "%d", anElement.unsigned_int_u);
      break;

    case _C_LNG: 
      fprintf(fp, "%d", anElement.long_int_u);
      break;

    case _C_ULNG: 
      fprintf(fp, "%d", anElement.unsigned_long_int_u);
      break;

    case _C_FLT:
      fprintf(fp, "%f", anElement.float_u);
      break;
      
#if (ELT_INCLUDES_DOUBLE)
    case _C_DBL:
      fprintf(fp, "%lf", anElement.double_u);
      break;
#endif
      
    default : 
      fprintf(fp, "unknown?");
    }
}
