/*
 * binary.c - Binary data types are implemented here.
 *
 * Copyright (C) 1997, George Madrid
 * All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "config.h"
#include "nsobj.h"
#include "nsref.h"
#include "nsrefarr.h"
#include "utils.h"
#include "btree.h"

/* ---------- Methods ---------- */
nsObj binary_getClass(nsRef);
nsRef binary_setClass(nsRef, nsObj);
void binary_Print(nsRef);

nsRef symbol_setClass(nsRef, nsObj);
void symbol_Print(nsRef);

void string_Print(nsRef);
nsObj string_setARef(nsRef, nsObj, nsObj);
nsObj string_ARef(nsRef, nsObj);

struct nsr_ops binary_ops = {
     binary_getClass,
     binary_setClass,
     binary_Print,
     NULL,
};

struct nsr_ops symbol_ops = {
     binary_getClass,
     symbol_setClass,
     symbol_Print,
     NULL,
};

struct nsr_ops string_ops = {
     binary_getClass,
     binary_setClass,
     string_Print,
     string_setARef,
     string_ARef,
};

/* ---------------------------------------------------------------------- */
struct nsBinObjS {
     nsObj classSymbol;
     size_t binSize;
     char data[1];		/* This should always be the last element */
};

struct nsSymTabS {
     nsObj nso;			/* The object */
     int16 *ustr;		/* The Ustr that is the symbol name */
};

/* ---------- Static stuff ---------- */
const char *SYMBOL_STRING = "symbol";
const char *STRING_STRING = "string";
BTREE symbol_table;

static int cmp_symtab_entry(const void *, const void *);
static nsObj make_raw_binary(size_t, nsRef *);
static nsObj make_binary_internal(size_t, nsObj, nsRef *);

#define get_bop(nsr)  ((struct nsBinObjS *)(nsr + 1))

/*---------------------------------------------------------------------- */  
/*
 * make_binary(size, class) -> nsObj
 *
 * Make a new binary object that contains a space for <size> bytes.
 * Make it of type <class>.
 */
nsObj
make_binary(size_t size, nsObj class)
{
     return(make_binary_internal(size, class, NULL));
}

static nsObj
make_binary_internal(size_t size, nsObj class, nsRef *nsrp)
{
     nsObj ret;
     nsRef nsr;

     /* This should really raise an exception of some kind -gam */
     assert(nso_issymbol(class));

     ret = make_raw_binary(size, &nsr);

     /* Now set up object specific stuff */
     binary_setClass(nsr, class); /* Call the function in case the ops
				   * change */
     get_bop(nsr)->binSize = size;

     if (nsrp) *nsrp = nsr;
     return ret;
}

/* make a binary object with no class (poor white trash) */
static nsObj
make_raw_binary(size_t size, nsRef *nsRefp)
{
     size_t index = 0;
     nsRef nsr;
     
     /* This has two extra bytes.  Do you care?  -gam */
     index = alloc_slot(sizeof(struct nsRefS)
			+ sizeof(struct nsBinObjS) + size, &nsr);
     if (!index) return 0; /* Out of memory */

     nsr->primclass = PRIMC_BINARY;
     nsr->ops = &binary_ops;

     /* Add type information */
     if (nsRefp) *nsRefp = nsr;
     return (index << 2) | REFERENCE_BITS;
}

nsObj
make_symbol_symbol()
{
     nsObj ret;
     nsRef nsr;
     struct nsBinObjS *nsbop;
     struct nsSymTabS nsts;
     
     /* Remember that NS uses two byte chars. */
     ret = make_raw_binary((strlen(SYMBOL_STRING) + 1) * 2, &nsr);
     
     nsbop = (struct nsBinObjS *)(nsr + 1);

     /* This thing points to its own bootstraps.  Heave ho! */
     nsbop->classSymbol = ret;
     copy_cstr_to_ustr((int16 *)nsbop->data, SYMBOL_STRING);
     /* Do this here since we can't call binary_setClass */
     nsr->ops = &symbol_ops;

     /* Put it in the symbol table. */
     nsts.nso = ret;
     nsts.ustr = (int16 *)nsbop->data;
     btree_Insert(symbol_table, &nsts);

     return ret;
}

/*
 * NOTE: This MUST be called AFTER make_symbol_symbol().
 */
nsObj
make_string_symbol()
{
     nsObj ret;
     nsRef nsr;
     struct nsBinObjS *nsbop;
     struct nsSymTabS nsts;
     
     /* Remember that NS uses two byte chars. */
     ret = make_raw_binary((strlen(STRING_STRING) + 1) * 2, &nsr);
     
     nsbop = (struct nsBinObjS *)(nsr + 1);

     /* This thing points to its own bootstraps.  Heave ho! */
     nsbop->classSymbol = SYMBOL_CLASS;
     copy_cstr_to_ustr((int16 *)nsbop->data, STRING_STRING);
     /* Do this here since we can't call binary_setClass */
     nsr->ops = &symbol_ops;

     /* Put it in the symbol table. */
     nsts.nso = ret;
     nsts.ustr = (int16 *)nsbop->data;
     btree_Insert(symbol_table, &nsts);

     return ret;
}

nsObj
make_symbol(const char *sym_string)
{
     nsObj ret;
     nsRef nsr;
     struct nsSymTabS nsts;
     struct nsSymTabS nsts2;
     
/*     if (!btree_Search(symbol_table, */
     

     ret = make_binary_internal((strlen(sym_string) + 1) * 2,
				STRING_CLASS, &nsr);
     copy_cstr_to_ustr((int16 *)get_bop(nsr)->data, sym_string);

     /* Check to see if we've got this one already */
     nsts.nso = ret;
     nsts.ustr = (int16 *)get_bop(nsr)->data;
     if (!btree_Search(symbol_table, &nsts, &nsts2)) {
	  /* Found it.  Clean up our mess and go home */
	  free_slot(ref_index(ret));
	  return(nsts2.nso);
     }

     /* Have to set this here, since you can't set it with setClass */
     get_bop(nsr)->classSymbol = SYMBOL_CLASS;
     nsr->ops = &symbol_ops;

     /* ...and put it in the symbol table */
     /* Error checking, Geoge --gam */
     btree_Insert(symbol_table, &nsts);

     return(ret);
}

nsObj
make_string(const char *string)
{
     nsObj ret;
     nsRef nsr;

     ret = make_binary_internal((strlen(string) + 1) * 2,
				STRING_CLASS, &nsr);
     copy_cstr_to_ustr((int16 *)get_bop(nsr)->data, string);
     return ret;
}

/* ---------------------------------------------------------------------- */
nsObj
binary_getClass(nsRef nsr)
{
     /* Need to add stuff here to change the behavior if the class is */
     /* set to something cool (like string). */

     return get_bop(nsr)->classSymbol;
}

/* ---------------------------------------------------------------------- */
nsRef
binary_setClass(nsRef nsr, nsObj class)
{
     struct nsr_ops *ops;

     assert(nso_issymbol(class));

     get_bop(nsr)->classSymbol = class;

     /* Each class behaves a little differently, so see if the ops
      * change. */
     if (class == SYMBOL_CLASS) {
	  /* This should be removed and throw an exception of some
	   * kind -gam */
	  assert(class != SYMBOL_CLASS);
     }
     else if (class == STRING_CLASS) {
	  ops = &string_ops;
     }
     else {
	  ops = &binary_ops;
     }
     nsr->ops = ops;

     return nsr;
}

nsRef
symbol_setClass(nsRef nsr, nsObj class)
{
     /* The symbol type is sacred.  A symbol can't have its class */
     /* changed */

     /* Some sort of exception should be thrown here -gam */

     return(nsr);
}

/* ---------------------------------------------------------------------- */
void
binary_Print(nsRef nsr)
{
     int i, size;
     char *data;
     
     data = (char *)get_bop(nsr)->data;
     size = get_bop(nsr)->binSize;
     /* Need to add printing the class here -gam */
     fputc('<', stdout);
     nso_print(get_bop(nsr)->classSymbol);
     fputc(':', stdout);
     for (i = 0; i < size; i++) {
	  if (i % 4 == 0) {
	       printf(" 0x");
	  }
	  output_two_char_hex(data[i]);
     }
     fputc('>', stdout);
}

void
symbol_Print(nsRef nsr)
{
     print_ustr((int16 *)get_bop(nsr)->data);
}

void
string_Print(nsRef nsr)
{
     fputc('"', stdout);
     print_ustr((int16 *)get_bop(nsr)->data);
     fputc('"', stdout);
}

int
symbol_equal(nsObj sym1, nsObj sym2)
{
     nsRef nsr1, nsr2;

     if (sym1 == sym2) return 1;

     nsr1 = find_index(ref_index(sym1));
     nsr2 = find_index(ref_index(sym2));
     
     return !ustrcasecmp((int16 *)get_bop(nsr1)->data,
			 (int16 *)get_bop(nsr2)->data);
}

int
init_symbol_tree()
{
     symbol_table = btree_Create(sizeof(struct nsSymTabS), cmp_symtab_entry);
     return(0);
}

static int
cmp_symtab_entry(const void *s1, const void *s2)
{
     return(ustrcasecmp(((struct nsSymTabS *)s1)->ustr,
			((struct nsSymTabS *)s2)->ustr));
}
     
nsObj
string_setARef(nsRef nsr, nsObj index, nsObj value)
{
     fprintf(stderr, "Unimplemented\n");
     abort();
}

nsObj
string_ARef(nsRef nsr, nsObj index)
{
     fprintf(stderr, "Unimplemented\n");
     abort();
}
