#ifndef __INTERNAL_H__
#define __INTERNAL_H__ 1

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

#include "imptypes.h"

// support code with the procedure name in lower case is not callable by IMP directly
// These will be called in turn by procedures with the name in upper case.

extern char *_imp_current_file;
extern int _imp_current_line;

extern int _imp_typeof(_imp_Name *N);
extern int _imp_sizeof(_imp_Name *N);

extern unsigned char *_imp_length(_imp_string *str);
extern unsigned char *_imp_charno(_imp_string *str, int pos);

extern _imp_string _imp_formatf(char *s, ...);
  
extern void _imp_readsymbol(int *P);
extern void _imp_readch(int *P);
extern int _imp_nextsymbol(void);
extern int _imp_nextch(void);
extern void _imp_skipsymbol(void);
extern void _imp_printsymbol(char SYM);
extern void _imp_printch(char SYM);
extern void _imp_printstring(_imp_string S);

// experimenting with different ways to handle long integers:
extern void _imp_writeint(int V, int P);
extern void _imp_writelong(long long int V, int P);

extern void _imp_readitem(_imp_string *S);
extern void _imp_readstring(_imp_string *S);
extern void _imp_readtext(_imp_string *S, int DELIM);
extern _imp_string _imp_nextitem(void);
extern void _imp_readline(_imp_string *S);
extern int _imp_instream(void);
extern int _imp_outstream(void);
extern int _imp_inputstream(void);
extern int _imp_outputstream(void);
extern _imp_string _imp_inputname(void);
extern _imp_string _imp_outputname(void);
extern _imp_string _imp_infilename(void);
extern _imp_string _imp_outfilename(void);
extern void _imp_selectinput(int N);
extern void _imp_selectoutput(int N);
extern void _imp_openinput(int N, _imp_string FD);
extern void _imp_openoutput(int N, _imp_string FD);
extern void _imp_openbinaryinput(int N, _imp_string FD);
extern void _imp_openbinaryoutput(int N, _imp_string FD);
extern void _imp_defineinput(int I, _imp_string SPEC);
extern void _imp_defineoutput(int I, _imp_string SPEC);
extern void _imp_closestream(int str);
extern void _imp_closeinput(void);
extern void _imp_closeoutput(void);
extern void _imp_abandoninput(void);
extern void _imp_abandonoutput(void);
extern void _imp_resetinput(void);
extern void _imp_space(void);
extern void _imp_spaces(int N);
extern void _imp_newpage(void);
extern void _imp_newline(void);
extern void _imp_newlines(int N);
extern void _imp_print(double R, int BEFORE, int AFTER);
extern void _imp_printfloating(double R, int A, int B);
extern void _imp_printfl(double R, int PLACES);
extern void _imp_printfhex(double R);
extern _imp_string _imp_substring(_imp_string *S, int FROM, int TO);
extern _imp_string _imp_fromstring(_imp_string *S, int FROM, int TO);
extern _imp_string _imp_trim(_imp_string S, int MAX);
extern void _imp_toupper(_imp_string *S);
extern void _imp_tolower(_imp_string *S);
extern _imp_string _imp_time(void);
extern _imp_string _imp_date(void);
extern int _imp_cputime(void);
extern _imp_string _imp_cliparam(void);
extern _imp_string _imp_eventmessage(void);
extern _imp_string _imp_itos(int I, int P);
extern int _imp_stoi(_imp_string S);

// internal routines that filter all I/O.  Well, mostly all.
extern int _imp_getchar(FILE *f);
extern void _imp_putchar(int ch /*, FILE *f*/);

static inline void _imp_zeropad /*__attribute__((no_instrument_function))*/ (_imp_string *str) {
  int len = str->length;
  if (str->cstr.s[len] != '\0') fprintf(stderr, "*** WARNING: IMP string at %p is not zero-terminated for access by C\n", str);
  do str->cstr.s[len++] = '\0'; while (len < 256);
}

static inline void _imp_strcpy /*__attribute__((no_instrument_function))*/ (_imp_string *to, _imp_string from) {
  memmove(to, &from, *_imp_length(&from)+1);
}

static inline _imp_string * /*__attribute__((no_instrument_function))*/ _imp_strdup(_imp_string s) {
  _imp_string *heap = malloc(sizeof(_imp_string));
  // TO DO: instead of copying max size, copy actual length
  memmove(heap, &s, sizeof(_imp_string));
  return heap;
}

static inline int /*__attribute__((no_instrument_function))*/ unassigned_string(_imp_string *s) {
  return (s->charno[0] == s->charno[1]) && (memcmp(&s->charno[0], &s->charno[1], 255) == 0);
}

static inline _imp_string /*__attribute__((no_instrument_function))*/ _imp_join(_imp_string left, _imp_string right) {
  _imp_string joined;
  if (unassigned_string(&left)) {
    // TO DO: make into a signal
    fprintf(stderr, "UNASSIGNED STRING in left operand of string concatenation in %s line %d\n", _imp_current_file, _imp_current_line);
    exit(1); // short-term error message. Needs _imp_signal. *TO DO*
  }
  if (unassigned_string(&right)) {
    // TO DO: make into a signal
    fprintf(stderr, "UNASSIGNED STRING in right operand of string concatenation in %s line %d\n", _imp_current_file, _imp_current_line);
    exit(1); // short-term error message. Needs _imp_signal. *TO DO*
  }
  int ll = *_imp_length(&left), rl = *_imp_length(&right);
  int combined_length = ll+rl;
  if (combined_length > 255) {
    // TO DO: make into a signal
    fprintf(stderr, "STRING CAPACITY EXCEEDED: \"%s\".\"%s\"\n", _imp_i2cstr(&left), _imp_i2cstr(&right));
    exit(1); // short-term error message. Needs _imp_signal. *TO DO*
  }
  _imp_strcpy(&joined, left);
  memmove(&joined.cstr.s[ll], &right.cstr.s, rl);
  *_imp_length(&joined) = combined_length;
  return joined; // Return whole string on the stack, not just a pointer.
}

// TO DO!!! move to signal code...
static inline void _imp_monitor /*__attribute__((no_instrument_function))*/ (int n, int line, char *file, const char *funcname) {
}

extern _imp_string *_imp_strcat(_imp_string *to, _imp_string from);
extern int _imp_strcmp(_imp_string left, _imp_string right);
extern int _imp_resolve(_imp_string s, _imp_string *left, _imp_string match, _imp_string *right);

extern int __attribute__ ((noinline)) _imp_rcheck1d(int ix, int low, int high, char *arrayname);
extern int __attribute__ ((noinline)) _imp_ucheck_int(int v, char *vs);
extern double __attribute__ ((noinline)) _imp_urcheck_double(double v, char *vs);
extern int __attribute__ ((noinline)) _imp_zcheck_int(int v, char *vs);
extern int __attribute__ ((noinline)) _imp_zcheck_double(double v, char *vs);

#define _R(ix, low, high, vs) _imp_rcheck1d(ix, low, high, vs)
// because this is inserted into machine generated code,
// we know that 'v' will always be an atom, not an expression:
//#define _U(v) _imp_ucheck_int(v, #v)
#define _U(v)  ((typeof(v))_imp_ucheck_int(v, #v))
#define _UR(v) ((typeof(v))_imp_urcheck_double(v, #v))
#define _Z(v)  ((typeof(v))_imp_zcheck_int(v, #v))
#define _ZR(v) ((typeof(v))_imp_zcheck_double(v, #v))

#endif // defined __INTERNAL_H__
