#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strstream.h>
#include "types.h"
//#include "hash.h"
#include "gfunc.h"
#include "genmap.h"
#include "genfiles.h"
#include "gfiles.h"
#include "exceptions.h"
#include "gvars.h"
#include "evalprocs.h"
#include "builtin-syms.h"

#define FormatLisp 1 // Use Lisp style formats.
#define FormatLast 2

static const char DoneMessage[] = "Done."; // seen ~^
static const char ExitMessage[] = "Exit."; // seen ~:^

extern const StringC * Coerce2String(Root *arg);
extern void PrintResult(Root* val, ostream& outs);
const char* PrintFormatSeq(ostream& dest, char* fmt, int fmt_len, Root *arg, int flags);

#define FmtSpaceSeen 1
#define FmtMinusSeen 2
#define FmtPlusSeen 4
#define FmtZeroSeen 8
#define FmtSharpSeen 16
#define FmtColonSeen 32	// Lisp mode: seen : modifier
#define FmtAtSeen 64	// Lisp mode: seen @ modifier
#define FmtIntSeen 128
#define FmtCharSeen 256
#define FmtParamValid 512
#define DEFAULT_OPTION_VALUE (0)

struct FormatOption {
    int flags;
    long value;
};

#define MAX_FORMAT_OPTIONS 10
struct FormatOptions {
    struct FormatOption option[MAX_FORMAT_OPTIONS];
    int count_options;
    int all_flags; // 'Or' of all option[*].flags.
    void init();
    int& flags(int i) { return option[i].flags; }
    int& value(int i) { return option[i].value; }
    int value(int i, int default_val) {
	return flags(i) & FmtParamValid ? value(i) : default_val; }
    // variant1() indicates # flag in C-mode or @ flag in Lisp mode. 
    int variant1() { return all_flags & (FmtAtSeen|FmtSharpSeen); }
    // variant2() indicates : flag in Lisp mode. 
    int variant2() { return all_flags & (FmtColonSeen); }
};

void FormatOptions::init()
{
    FormatOption *cur_option = &option[0];
    int i;
    for (i = 0 ; i < MAX_FORMAT_OPTIONS; i++, cur_option++) {
	cur_option->value = DEFAULT_OPTION_VALUE;
	cur_option->flags = 0;
    }
    count_options = 0;
    all_flags = 0;
}

void PrintToWidth(Root* arg, ostream& dest,
		  int min_width, int max_width, char pad)
{
    if (min_width == 0 && max_width == 0)
	arg->printon(dest);
    else {
	char *str;
	size_t len;
	ostrstream temp_stream;
	arg->printon(temp_stream);
	len = temp_stream.pcount();
	temp_stream << ends;
	str = temp_stream.str();

	if (max_width >= 0 && len > max_width) { // Truncate!
	    if (min_width > 0)
		str += len - max_width;
	    len = max_width;
	}
	if (min_width > 0)
	    for (min_width = min_width - len; --min_width>= 0; )
		dest << pad;
	dest.write(str, len);
	if (min_width < 0)
	    for (min_width = -min_width -len; --min_width >= 0; )
		dest << pad;
	temp_stream.freeze(0);
    }
}

#define END_FORMAT 0
#define GET_FORMAT() (--fmt_len >= 0 ? *fmt++ : END_FORMAT)
static char not_enough_args[] = "Not enough values to format.";
#define GET_ARG() ({ if (start_arg >= arg_len) return not_enough_args; args[start_arg++];})

// Scan a formatting directive.
// If 'options' is non-NULL, put seen options there.
// Returns NULL on success; otherwise an error message.

static char *ScanOptions(FormatOptions *options,
			 char* &fmt, int &fmt_len,
			 Root **args, int arg_len, int& start_arg,
			 int flags)
{
    int done = 0;
    options->init();
    int cur_flags = 0;
    long cur_value = 0;
    for (;;) {
	int fchar = GET_FORMAT();
      test_next:
	switch (fchar) {
	  case END_FORMAT:
	    return "End of format with no conversion operation";
	  case '+':
	    cur_flags |= FmtPlusSeen;
	    continue;
	  case '-':
	    cur_flags |= FmtMinusSeen;
	    continue;
	  case ' ':
	    cur_flags |= FmtSpaceSeen;
	    continue;
	  case '#':
	    if (flags & FormatLisp) {
		cur_value = arg_len - start_arg;
		cur_flags |= FmtParamValid;
	    }
	    else
		cur_flags |= FmtSharpSeen;
	    continue;
	  case 'v': case 'V':
	    if (flags & FormatLisp) {
		const Root *arg = (const Root*)GET_ARG();
		const Numeric *num = arg->numeric();
		if (num == NULL || !num->getlong(&cur_value))
		    return "Formal parameter is not an integer.";
		// FIXME: Should also recognize character.
		cur_flags |= FmtParamValid;
		continue;
	    }
	    else goto other_char;
	  case '@':
	    cur_flags |= FmtAtSeen;
	    continue;
	  case '\'':
	    fchar = GET_FORMAT();
	    if (cur_value != DEFAULT_OPTION_VALUE)
		return "Internal format error.";
	    cur_flags |= FmtCharSeen|FmtParamValid;
	    cur_value = fchar;
	    continue;
	  case ':':
	    cur_flags |= FmtColonSeen;
	    continue;
	  case '0':
	    cur_flags |= FmtZeroSeen;
	    continue;
	  case '1': case '2': case '3': case '4': case '5':
	  case '6': case '7': case '8': case '9':
	    if (cur_value != DEFAULT_OPTION_VALUE)
		return "Internal format error.";
	    cur_value = 0;
	    cur_flags |= FmtIntSeen|FmtParamValid;
	    do {
		cur_value *= 10;
		cur_value += fchar - '0';
		fchar = GET_FORMAT();
	    } while  (fchar >= '0' && fchar <= '9');
	    goto test_next;
	  case '*':
	    if (flags & FormatLisp) goto other_char;
	    else {
		const Root *arg = (const Root*)GET_ARG();
		const Numeric *num = arg->numeric();
		if (num == NULL || !num->getlong(&cur_value))
		    return "Formal parameter is not an integer.";
		// FIXME: Should also recognize character.
		cur_flags |= FmtParamValid;
		continue;
	    }
	  other_char:
	  default:
	    done = 1;
	  case ',': // Lisp-mode.
	  case '.': // C-mode.
	    if (cur_flags & FmtMinusSeen)
		cur_value = - cur_value;
	    options->option[options->count_options].value = cur_value;
	    options->option[options->count_options].flags = cur_flags;
	    options->all_flags |= cur_flags;
	    options->count_options++;
	    if (options->count_options >= MAX_FORMAT_OPTIONS)
		return "Too many format parameters.";
	    cur_value = DEFAULT_OPTION_VALUE;
	    cur_flags = 0;
	    if (done)
		return NULL;
	    continue;
	}
	break;
    }
}

// Return NULL on success; otherwise an error message.
const char * PrintFormat(ostream& dest,
			 char* fmt, int fmt_len,
			 Root **args, int arg_len, int& start_arg,
	     int flags)
#define OUT_CHAR(c) dest.put(c)
{
    FormatOptions options;
    const char *message;
    int magic = flags & FormatLisp ? '~' : '%';
    for (;;) {
	int fchar = GET_FORMAT();
	if (fchar == END_FORMAT)
	    return 0;
	if (fchar != magic) {
	    OUT_CHAR(fchar);
	    continue;
	}
	// Handle prefix stuff
	message = ScanOptions(&options,
			      fmt, fmt_len,
			      args, arg_len, start_arg,
			      flags);
	if (message)
	    return message;
	// Handle the conversion specifier itself
	Root *arg;
	int save_print_base = print_base;
	int save_print_readable = print_readable;
	int save_print_width = print_width;
	int save_print_pad_char = print_pad_char;
	int base_skip;
	fchar = fmt[-1];
	switch (fchar) {
	    int base;
	  case 'r':
	  case 'R':
	    base = options.value(0);
	    base_skip = 1;
	    goto print_int;
	  case 'b':
	  case 'B':
	    base = 2;
	    base_skip = 0;
	    goto print_int;
	  case 'o':
	  case 'O':
	    base = 8;
	    base_skip = 0;
	    goto print_int;
	  case 'x':
	  case 'X':
	    base = 16;
	    base_skip = 0;
	    goto print_int;
	  case 'd':
	  case 'D':
	    base = 10;
	    base_skip = 0;
	    goto print_int;
	  print_int:
	    arg = GET_ARG();
	    print_base = base;
	    print_width = options.value(base_skip);
	    print_pad_char = (options.all_flags & FmtZeroSeen) ? '0' : ' ';
	    print_readable = (options.all_flags & FmtSharpSeen) != 0;
	    arg->printon(dest);
	    print_readable = save_print_readable;
	    print_base = save_print_base;
	    print_width = save_print_width;
	    print_pad_char = save_print_pad_char;
	    break;
	  case 'p':
	    arg = GET_ARG();
	    dest.form("%p", arg);
	    break;
	  case 'a':
	  case 'A':
	    arg = GET_ARG();
	    print_readable = (options.all_flags & FmtSharpSeen) != 0;
	    PrintToWidth(arg, dest,
			 options.value(0),
			 options.value(1, -1),
			 ' ');
	    print_readable = save_print_readable;
	    break;
	  case 'c':
	    arg = GET_ARG();
	    const Integer *intarg = ConvertInteger(arg);
	    if (intarg)
		arg = CCharToChar((unsigned char)intarg->U[0]);
	    print_readable = (options.all_flags & FmtSharpSeen) != 0;
	    PrintToWidth(arg, dest,
			 options.value(0),
			 options.value(1, -1),
			 ' ');
	    print_readable = save_print_readable;
	    break;
	  case 's':
	    if (options.all_flags & FmtPlusSeen) {
		arg = GET_ARG();
		PrintResult(arg, dest);
		break;
	    }
	    if (!(flags & FormatLisp)) {
		arg = GET_ARG();
		print_readable = (options.all_flags & FmtSharpSeen) != 0;
		PrintToWidth(arg, dest,
			     options.value(0),
			     options.value(1, -1),
			     ' ');
		print_readable = save_print_readable;
		break;
	    }
	    // Otherwise fall through to...
	  case 'S':
	    arg = GET_ARG();
	    print_readable = 1;
	    PrintToWidth(arg, dest,
			 options.value(0),
			 options.value(1, -1),
			 ' ');
	    print_readable = save_print_readable;
	    break;
	  case 'e': case 'E':
	  case 'f': case 'F':
	  case 'g': case 'G':
	    arg = GET_ARG();
	    long save_print_float_format = print_float_format;
	    long save_print_precision = print_precision;
	    print_float_format = fchar;
	    print_width = options.value(0);
	    print_precision = options.value(1);
	    arg->printon(dest);
	    print_float_format = save_print_float_format;
	    print_precision = save_print_precision;
	    print_width = save_print_width;
	    break;
	  case '*':
	    if (!(options.flags(0) & FmtIntSeen))
		options.value(0) = 0;
	    if (options.flags(0) & (FmtMinusSeen|FmtColonSeen))
		options.value(0) = -options.value(0);
	    if (options.all_flags & FmtAtSeen)
		start_arg = options.value(0);
	    else
		start_arg += options.value(0)+1;
	    if (start_arg < 0  || start_arg > arg_len)
		return "Bad format 'goto'\n";
	    break;
	  case '{': {
	      char *start_group = fmt;
	      int at_least_once = 0;
	      FormatOptions end_options;
	      int dummy_start_arg = start_arg;
	      int nesting = 0;
	      int sub_fmt_len; // Length of iterated sub-format.
	      // Find matching '}'.
	      for (;;) {
		  sub_fmt_len = fmt-start_group;
		  fchar = GET_FORMAT();
		  if (fchar == END_FORMAT) {
		      if (flags & FormatLisp)
			  return "Missing '~}' following '~{' in format.";
		      else
			  return "Missing '%}' following '%{' in format.";
		  }
		  if (fchar != magic) continue;
		  message = ScanOptions(&end_options, fmt, fmt_len,
					args, arg_len, dummy_start_arg, flags);
		  if (message)
		      return message;
		  if (fmt[-1] == '{')
		      nesting++;
		  else if (fmt[-1] == '}') {
		      if (nesting == 0) {
			  if (magic == '~'
			      ? (end_options.all_flags & FmtColonSeen)
			      : (end_options.all_flags & FmtSharpSeen))
			      at_least_once = 1;
			  break;
		      }
		      nesting--;
		  }
	      }
	      if (sub_fmt_len == 0) {
		  arg = GET_ARG();
		  const StringC *fmt2 = Coerce2String(arg);
		  start_group = fmt2->chars();
		  sub_fmt_len = fmt2->leng();
	      }
	      long max_iterations = options.value(0, -1);
	      Root **sub_args;
	      Root **vec_args = NULL;
	      int sub_arg_len;
	      int sub_start_arg;
	      if (options.variant1()) {
		  sub_args = args;
		  sub_arg_len = arg_len;
		  sub_start_arg = start_arg;
	      } else {
		  arg = GET_ARG();
		  GenSeq *seq = arg->sequence();
		  if (seq == NULL)
		      return "Indirected format argument is not a sequence.";
		  sub_arg_len = seq->length();
		  sub_start_arg = 0;
		  vec_args = new RootPtr[sub_arg_len];
		  sub_args = vec_args;
		  ITERATOR(iter, seq);
		  Root **pArgs = sub_args;
		  for (;;) {
		      Root* val = iter.next();
		      if (val == Missing) break;
		      *pArgs++ = val;
		  }
	      }
	      for (;;) {
		  if (max_iterations >= 0)
		      if (--max_iterations < 0)
			  break;
		  if (sub_start_arg >= sub_arg_len && !at_least_once)
		      break;
		  if (options.variant2()) {
		      if (sub_start_arg >= sub_arg_len)
			  break;
		      Root* sublist = sub_args[sub_start_arg++];
		      int last = sub_start_arg >= sub_arg_len ? FormatLast : 0;
		      message = PrintFormatSeq(dest, start_group, sub_fmt_len,
					       sublist, flags|last);
		      if (message == DoneMessage)
			  message = NULL;
		  }
		  else {
		      message =
			  PrintFormat(dest, start_group, sub_fmt_len,
				      sub_args, sub_arg_len, sub_start_arg,
				      flags);
		      if (message == ExitMessage || message == DoneMessage)
			  break;
		  }
		  if (message)
		      break;
		  at_least_once = 0;
	      }
	      if (options.variant1())
		  start_arg = sub_start_arg;
	      else
		  delete vec_args;
	      if (message)
		  if (message == ExitMessage || message == DoneMessage)
		      message = NULL;
		  else
		      return message;
	      break;
	  }
	  case '?': {
	      arg = GET_ARG();
	      const StringC *fmt2 = Coerce2String(arg);
	      if (options.variant1()) {
		  message = PrintFormat(dest, fmt2->chars(), fmt2->leng(),
					args, arg_len, start_arg, flags);
	      } else {
		  arg = GET_ARG();
		  message = PrintFormatSeq(dest,
					   fmt2->chars(), fmt2->leng(),
					   arg, flags);
	      }
	      if (message)
		  if (message == ExitMessage || message == DoneMessage)
		      return NULL;
		  else
		      return message;
	      break;
	  }
	  case '%':
	    if (magic == '%') {
		dest.put('%');
	    }
	    else {
		int count = options.value(0, 1);
		while (--count >= 0)
		    dest.put('\n');
	    }
	    break;
	  case '^': {
	      int do_exit = 0;
	      if (!(options.flags(0) & FmtParamValid)) // No paramaters.
		  if (options.variant2())
		      do_exit = flags & FormatLast;
		  else
		      do_exit = start_arg >= arg_len;
	      else if (!(options.flags(1) & FmtParamValid)) // One parameter.
		  do_exit = options.value(0) == 0;
	      else if (!(options.flags(2) & FmtParamValid)) // Two parameters.
		  do_exit = options.value(0) == options.value(1);
	      else // At least three parameters.
		  do_exit = options.value(0) <= options.value(1)
		      && options.value(1) <= options.value(2);
	      if (do_exit)
		  return options.variant2() ? ExitMessage : DoneMessage;
	      break;
	  }
	  case '~':
	    if (magic == '~') {
		int count = options.value(0, 1);
		while (--count >= 0)
		    dest.put('~');
	    }
	    else
		goto default_case;
	    break;
	  case END_FORMAT:
	    return "Bad end of format string.";
	  default_case:
	  default:
	    return "Bad format conversion character.";
	}
    }
}

// Format using a sequence.

const char* PrintFormatSeq(ostream& dest, char* fmt, int fmt_len, Root *arg, int flags)
{
    GenSeq *seq = arg->sequence();
    if (seq == NULL)
	return "Indirected format argument is not a sequence.";
    int new_len = seq->length();
    int new_start = 0;
    Root **new_args = (Root**)alloca(new_len * sizeof(Root*));
    ITERATOR(iter, seq);
    Root **pArgs = new_args;
    for (;;) {
	Root* val = iter.next();
	if (val == Missing) break;
	*pArgs++ = val;
    }
    return PrintFormat(dest, fmt, fmt_len,
		       new_args, new_len, new_start, flags);
}

Root *DoSFormat(Root* format_arg, Vector* arglist)
{
    int start_arg = 0;
    const char *message;
    long save_print_lisp = print_lisp;
    const StringC *fmt_string = format_arg->asString();
    ostrstream dest;
    message = PrintFormat(dest,
			  fmt_string->chars(), fmt_string->leng(),
			  arglist->start_addr(), arglist->leng(),
			  start_arg, 0);
    print_lisp = save_print_lisp;
    if (message)
	fprintf(stderr, "Error with format: %s\n", message);
    StringC *str = NewString(dest.pcount(), dest.str());
    dest.freeze(0);
    return str;
}

Root *DoFormat(Root* stream_arg, Root* format_arg, Vector* arglist)
{
    int start_arg = 0;
    const char *message;
    long save_print_lisp = print_lisp;
    const StringC *fmt_string = format_arg->asString();
    if (stream_arg == NULL)
	stream_arg = DEFAULT_OUT_FILE;
    CharFile *file = Coerce2CharFile(stream_arg);
    ostream str(file->rdbuf());
    message = PrintFormat(str,
			  fmt_string->chars(), fmt_string->leng(),
			  arglist->start_addr(), arglist->leng(),
			  start_arg, 0);
    //	str._flags |= ios::dont_close;
    if (message)
	fprintf(stderr, "Error with format: %s\n", message);
    return &NullSequence;
}

Root *LispFormatOp(Root* dest, Root* fmt, Vector* args)
{
    int start_arg = 0;
    const char *message;
    long save_print_lisp = print_lisp;
    print_lisp = 1;
    const StringC *fmt_string = fmt->asString();
    if (dest == &NilSymbol) {
	ostrstream dest;
	message = PrintFormat(dest,
			      fmt_string->chars(), fmt_string->leng(),
			      args->start_addr(), args->leng(),
			      start_arg, FormatLisp);
	print_lisp = save_print_lisp;
	if (message)
	    fprintf(stderr, "Error with format: %s\n", message);
	StringC *str = NewString(dest.pcount(), dest.str());
	dest.freeze(0);
	return str;
    }
    else if (dest == &TSymbol) {
	message = PrintFormat(cout,
			      fmt_string->chars(), fmt_string->leng(),
			      args->start_addr(), args->leng(),
			      start_arg, FormatLisp);
	if (message)
	    fprintf(stderr, "Error with format: %s\n", message);
	print_lisp = save_print_lisp;
	return &NilSymbol;
    }
    else {
	abort();
    }
}
