#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include <gc.h>

#ifdef __CYGWIN__
# include <errno.h>
#else
  extern int errno;
#endif

#include "idst-config.h"

#define INLINE_CACHE	0
#define VTBL_CACHE	0
#define STATISTICS	1

typedef struct object *oop;
typedef struct assoc  *assoc_t;
typedef struct vtbl   *vtbl_t;
typedef struct sel    *sel_t;
typedef        oop   (*imp_t)(oop, ...);

struct object
{
  vtbl_t	_vtbl[0];
};

struct sel
{
  vtbl_t	_vtbl[0];
  oop		 size;
  char		*name;
  struct sel	*next;
#if (VTBL_CACHE)
  size_t	 index;
#endif
};

struct assoc
{
  vtbl_t	_vtbl[0];
  sel_t		 key;
  imp_t		 value;
};

struct vtbl
{
  vtbl_t	_vtbl[0];
  oop		 size;
  oop		 capacity;
  assoc_t	*bindings;
  vtbl_t	 delegate;
#if (VTBL_CACHE)
  struct array	*cache;
  vtbl_t	 next;
#endif
};

extern vtbl_t	vtbl_Nil;
extern vtbl_t	vtbl_Tagged;

extern sel_t	_sel_List;

extern void  _idst_initialise(int argc, char **argv);

extern imp_t _rebind(oop receiver, sel_t sel);

extern oop   _proto(oop parent);
extern sel_t _selector(char *name);
extern void  _method(oop proto, sel_t sel, imp_t imp);

extern void flushCache(void);

#if (INLINE_CACHE)
# if (STATISTICS)
    extern unsigned long messageSends;
#   define COUNT_SEND	++messageSends
# else
#   define COUNT_SEND
# endif
struct posic { vtbl_t vtbl; imp_t imp; } ;
# define _bind(RCV, SEL) ({										\
      static struct posic cache;									\
      register oop rcv= (RCV);										\
      register vtbl_t vtbl= rcv ? (((unsigned)rcv & 1) ? vtbl_Tagged : rcv->_vtbl[-1]) : vtbl_Nil;	\
      if (vtbl != cache.vtbl) { cache.vtbl= vtbl;  cache.imp= _rebind(rcv, SEL); }			\
      else COUNT_SEND;											\
      cache.imp;											\
    })
#else
# define _bind _rebind
#endif

static inline void *_newPointers(int size)
{
#if (STATISTICS)
  extern unsigned long allocationCount, allocationBytes;
  allocationCount += 1;
  allocationBytes += size;
#endif
  return GC_MALLOC(size);
}

static inline void *_newBytes(int size)
{
#if (STATISTICS)
  extern unsigned long allocationCount, allocationBytes;
  allocationCount += 1;
  allocationBytes += size;
#endif
  return GC_MALLOC_ATOMIC(size);
}

static inline void *_realloc(void *ptr, int size)
{
#if (STATISTICS)
  extern unsigned long allocationBytes;
  allocationBytes += size;
#endif
  return GC_REALLOC(ptr, size);
}

static inline oop _clone(oop proto, int size)
{
  oop chunk= (oop)_newPointers(sizeof(vtbl_t) + size);
  chunk->_vtbl[0]= proto->_vtbl[-1];
  return (oop)&chunk->_vtbl[1];
}

#define _malloc _newPointers

#if (TAGGED_INTEGERS)
# define     _integerObject(value)	((oop)(((value) << 1) | 1))
# define     _isIntegerObject(object)	((int)(object) & 1)
# define     _areIntegerObjects(a, b)	((int)(a) & (int)(b) & 1)
# define     _integerValue(object)	((int)(object) >> 1)
#endif

extern oop   _nlReturn(oop blockClosure, void *envp, oop anObject);
extern oop   _nlAnswer;

extern int    _argc;
extern char **_argv;
