/* ----------------------------------------------------------------------------- 
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * wrapfunc.c
 *
 * This file defines a object for creating wrapper functions.  Primarily
 * this is used for convenience since it allows pieces of a wrapper function
 * to be created in a piecemeal manner.
 * ----------------------------------------------------------------------------- */

#include "swig.h"
#include <ctype.h>

static int Compact_mode = 0;	/* set to 0 on default */
static int Max_line_size = 128;

/* -----------------------------------------------------------------------------
 * NewWrapper()
 *
 * Create a new wrapper function object.
 * ----------------------------------------------------------------------------- */

Wrapper *NewWrapper(void) {
  Wrapper *w;
  w = (Wrapper *) Malloc(sizeof(Wrapper));
  w->localh = NewHash();
  w->locals = NewStringEmpty();
  w->code = NewStringEmpty();
  w->def = NewStringEmpty();
  return w;
}

/* -----------------------------------------------------------------------------
 * DelWrapper()
 *
 * Delete a wrapper function object.
 * ----------------------------------------------------------------------------- */

void DelWrapper(Wrapper *w) {
  Delete(w->localh);
  Delete(w->locals);
  Delete(w->code);
  Delete(w->def);
  Free(w);
}

/* -----------------------------------------------------------------------------
 * Wrapper_compact_print_mode_set()
 *
 * Set compact_mode.
 * ----------------------------------------------------------------------------- */

void Wrapper_compact_print_mode_set(int flag) {
  Compact_mode = flag;
}

/* -----------------------------------------------------------------------------
 * Wrapper_pretty_print()
 *
 * Formats a wrapper function and fixes up the indentation.
 * ----------------------------------------------------------------------------- */

void Wrapper_pretty_print(String *str, File *f) {
  String *ts;
  int level = 0;
  int c, i;
  int empty = 1;
  int indent = 2;
  int plevel = 0;
  int label = 0;

  ts = NewStringEmpty();
  Seek(str, 0, SEEK_SET);
  while ((c = Getc(str)) != EOF) {
    if (c == '\"') {
      Putc(c, ts);
      while ((c = Getc(str)) != EOF) {
	if (c == '\\') {
	  Putc(c, ts);
	  c = Getc(str);
	}
	Putc(c, ts);
	if (c == '\"')
	  break;
      }
      empty = 0;
    } else if (c == '\'') {
      Putc(c, ts);
      while ((c = Getc(str)) != EOF) {
	if (c == '\\') {
	  Putc(c, ts);
	  c = Getc(str);
	}
	Putc(c, ts);
	if (c == '\'')
	  break;
      }
      empty = 0;
    } else if (c == ':') {
      Putc(c, ts);
      if ((c = Getc(str)) == '\n') {
	if (!empty && !strchr(Char(ts), '?'))
	  label = 1;
      }
      Ungetc(c, str);
    } else if (c == '(') {
      Putc(c, ts);
      plevel += indent;
      empty = 0;
    } else if (c == ')') {
      Putc(c, ts);
      plevel -= indent;
      empty = 0;
    } else if (c == '{') {
      Putc(c, ts);
      Putc('\n', ts);
      for (i = 0; i < level; i++)
	Putc(' ', f);
      Printf(f, "%s", ts);
      Clear(ts);
      level += indent;
      while ((c = Getc(str)) != EOF) {
	if (!isspace(c)) {
	  Ungetc(c, str);
	  break;
	}
      }
      empty = 0;
    } else if (c == '}') {
      if (!empty) {
	Putc('\n', ts);
	for (i = 0; i < level; i++)
	  Putc(' ', f);
	Printf(f, "%s", ts);
	Clear(ts);
      }
      level -= indent;
      Putc(c, ts);
      empty = 0;
    } else if (c == '\n') {
      Putc(c, ts);
      empty = 0;
      if (!empty) {
	int slevel = level;
	if (label && (slevel >= indent))
	  slevel -= indent;
	if ((Char(ts))[0] != '#') {
	  for (i = 0; i < slevel; i++)
	    Putc(' ', f);
	}
	Printf(f, "%s", ts);
	for (i = 0; i < plevel; i++)
	  Putc(' ', f);
      }
      Clear(ts);
      label = 0;
      empty = 1;
    } else if (c == '/') {
      empty = 0;
      Putc(c, ts);
      c = Getc(str);
      if (c != EOF) {
	Putc(c, ts);
	if (c == '/') {		/* C++ comment */
	  while ((c = Getc(str)) != EOF) {
	    if (c == '\n') {
	      Ungetc(c, str);
	      break;
	    }
	    Putc(c, ts);
	  }
	} else if (c == '*') {	/* C comment */
	  int endstar = 0;
	  while ((c = Getc(str)) != EOF) {
	    if (endstar && c == '/') {	/* end of C comment */
	      Putc(c, ts);
	      break;
	    }
	    endstar = (c == '*');
	    Putc(c, ts);
	    if (c == '\n') {	/* multi-line C comment. Could be improved slightly. */
	      for (i = 0; i < level; i++)
		Putc(' ', ts);
	    }
	  }
	}
      }
    } else {
      if (!empty || !isspace(c)) {
	Putc(c, ts);
	empty = 0;
      }
    }
  }
  if (!empty)
    Printf(f, "%s", ts);
  Delete(ts);
  Printf(f, "\n");
}

/* -----------------------------------------------------------------------------
 * Wrapper_compact_print()
 *
 * Formats a wrapper function and fixes up the indentation.
 * Print out in compact format, with Compact enabled.
 * ----------------------------------------------------------------------------- */

void Wrapper_compact_print(String *str, File *f) {
  String *ts, *tf;		/*temp string & temp file */
  int level = 0;
  int c, i;
  int empty = 1;
  int indent = 2;

  ts = NewStringEmpty();
  tf = NewStringEmpty();
  Seek(str, 0, SEEK_SET);

  while ((c = Getc(str)) != EOF) {
    if (c == '\"') {		/* string 1 */
      empty = 0;
      Putc(c, ts);
      while ((c = Getc(str)) != EOF) {
	if (c == '\\') {
	  Putc(c, ts);
	  c = Getc(str);
	}
	Putc(c, ts);
	if (c == '\"')
	  break;
      }
    } else if (c == '\'') {	/* string 2 */
      empty = 0;
      Putc(c, ts);
      while ((c = Getc(str)) != EOF) {
	if (c == '\\') {
	  Putc(c, ts);
	  c = Getc(str);
	}
	Putc(c, ts);
	if (c == '\'')
	  break;
      }
    } else if (c == '{') {	/* start of {...} */
      empty = 0;
      Putc(c, ts);
      if (Len(tf) == 0) {
	for (i = 0; i < level; i++)
	  Putc(' ', tf);
      } else if ((Len(tf) + Len(ts)) < Max_line_size) {
	Putc(' ', tf);
      } else {
	Putc('\n', tf);
	Printf(f, "%s", tf);
	Clear(tf);
	for (i = 0; i < level; i++)
	  Putc(' ', tf);
      }
      Append(tf, ts);
      Clear(ts);
      level += indent;
      while ((c = Getc(str)) != EOF) {
	if (!isspace(c)) {
	  Ungetc(c, str);
	  break;
	}
      }
    } else if (c == '}') {	/* end of {...} */
      empty = 0;
      if (Len(tf) == 0) {
	for (i = 0; i < level; i++)
	  Putc(' ', tf);
      } else if ((Len(tf) + Len(ts)) < Max_line_size) {
	Putc(' ', tf);
      } else {
	Putc('\n', tf);
	Printf(f, "%s", tf);
	Clear(tf);
	for (i = 0; i < level; i++)
	  Putc(' ', tf);
      }
      Append(tf, ts);
      Putc(c, tf);
      Clear(ts);
      level -= indent;
    } else if (c == '\n') {	/* line end */
      while ((c = Getc(str)) != EOF) {
	if (!isspace(c))
	  break;
      }
      if (c == '#') {
	Putc('\n', ts);
      } else if (c == '}') {
	Putc(' ', ts);
      } else if ((c != EOF) || (Len(ts) != 0)) {
	if (Len(tf) == 0) {
	  for (i = 0; i < level; i++)
	    Putc(' ', tf);
	} else if ((Len(tf) + Len(ts)) < Max_line_size) {
	  Putc(' ', tf);
	} else {
	  Putc('\n', tf);
	  Printf(f, "%s", tf);
	  Clear(tf);
	  for (i = 0; i < level; i++)
	    Putc(' ', tf);
	}
	Append(tf, ts);
	Clear(ts);
      }
      Ungetc(c, str);

      empty = 1;
    } else if (c == '/') {	/* comment */
      empty = 0;
      c = Getc(str);
      if (c != EOF) {
	if (c == '/') {		/* C++ comment */
	  while ((c = Getc(str)) != EOF) {
	    if (c == '\n') {
	      Ungetc(c, str);
	      break;
	    }
	  }
	} else if (c == '*') {	/* C comment */
	  int endstar = 0;
	  while ((c = Getc(str)) != EOF) {
	    if (endstar && c == '/') {	/* end of C comment */
	      break;
	    }
	    endstar = (c == '*');
	  }
	} else {
	  Putc('/', ts);
	  Putc(c, ts);
	}
      }
    } else if (c == '#') {	/* Preprocessor line */
      Putc('#', ts);
      while ((c = Getc(str)) != EOF) {
	Putc(c, ts);
	if (c == '\\') {	/* Continued line of the same PP */
	  c = Getc(str);
	  if (c == '\n')
	    Putc(c, ts);
	  else
	    Ungetc(c, str);
	} else if (c == '\n')
	  break;
      }
      if (!empty) {
	Append(tf, "\n");
      }
      Append(tf, ts);
      Printf(f, "%s", tf);
      Clear(tf);
      Clear(ts);
      for (i = 0; i < level; i++)
	Putc(' ', tf);
      empty = 1;
    } else {
      if (!empty || !isspace(c)) {
	Putc(c, ts);
	empty = 0;
      }
    }
  }
  if (!empty) {
    Append(tf, ts);
  }
  if (Len(tf) != 0)
    Printf(f, "%s", tf);
  Delete(ts);
  Delete(tf);
  Printf(f, "\n");
}

/* -----------------------------------------------------------------------------
 * Wrapper_print()
 *
 * Print out a wrapper function.  Does pretty or compact printing as well.
 * ----------------------------------------------------------------------------- */

void Wrapper_print(Wrapper *w, File *f) {
  String *str;

  str = NewStringEmpty();
  Printf(str, "%s\n", w->def);
  Printf(str, "%s\n", w->locals);
  Printf(str, "%s\n", w->code);
  if (Compact_mode == 1)
    Wrapper_compact_print(str, f);
  else
    Wrapper_pretty_print(str, f);

  Delete(str);
}

/* -----------------------------------------------------------------------------
 * Wrapper_add_local()
 *
 * Adds a new local variable declaration to a function. Returns -1 if already
 * present (which may or may not be okay to the caller).
 * ----------------------------------------------------------------------------- */

int Wrapper_add_local(Wrapper *w, const_String_or_char_ptr name, const_String_or_char_ptr decl) {
  /* See if the local has already been declared */
  if (Getattr(w->localh, name)) {
    return -1;
  }
  Setattr(w->localh, name, decl);
  Printf(w->locals, "%s;\n", decl);
  return 0;
}

/* -----------------------------------------------------------------------------
 * Wrapper_add_localv()
 *
 * Same as add_local(), but allows a NULL terminated list of strings to be
 * used as a replacement for decl.   This saves the caller the trouble of having
 * to manually construct the 'decl' string before calling.
 * ----------------------------------------------------------------------------- */

int Wrapper_add_localv(Wrapper *w, const_String_or_char_ptr name, ...) {
  va_list ap;
  int ret;
  String *decl;
  DOH *obj;
  decl = NewStringEmpty();
  va_start(ap, name);

  obj = va_arg(ap, void *);
  while (obj) {
    Append(decl, obj);
    Putc(' ', decl);
    obj = va_arg(ap, void *);
  }
  va_end(ap);

  ret = Wrapper_add_local(w, name, decl);
  Delete(decl);
  return ret;
}

/* -----------------------------------------------------------------------------
 * Wrapper_check_local()
 *
 * Check to see if a local name has already been declared
 * ----------------------------------------------------------------------------- */

int Wrapper_check_local(Wrapper *w, const_String_or_char_ptr name) {
  if (Getattr(w->localh, name)) {
    return 1;
  }
  return 0;
}

/* ----------------------------------------------------------------------------- 
 * Wrapper_new_local()
 *
 * Adds a new local variable with a guarantee that a unique local name will be
 * used.  Returns the name that was actually selected.
 * ----------------------------------------------------------------------------- */

char *Wrapper_new_local(Wrapper *w, const_String_or_char_ptr name, const_String_or_char_ptr decl) {
  int i;
  String *nname = NewString(name);
  String *ndecl = NewString(decl);
  char *ret;

  i = 0;

  while (Wrapper_check_local(w, nname)) {
    Clear(nname);
    Printf(nname, "%s%d", name, i);
    i++;
  }
  Replace(ndecl, name, nname, DOH_REPLACE_ID);
  Setattr(w->localh, nname, ndecl);
  Printf(w->locals, "%s;\n", ndecl);
  ret = Char(nname);
  Delete(nname);
  Delete(ndecl);
  return ret;			/* Note: nname should still exists in the w->localh hash */
}


/* -----------------------------------------------------------------------------
 * Wrapper_new_localv()
 *
 * Same as add_local(), but allows a NULL terminated list of strings to be
 * used as a replacement for decl.   This saves the caller the trouble of having
 * to manually construct the 'decl' string before calling.
 * ----------------------------------------------------------------------------- */

char *Wrapper_new_localv(Wrapper *w, const_String_or_char_ptr name, ...) {
  va_list ap;
  char *ret;
  String *decl;
  DOH *obj;
  decl = NewStringEmpty();
  va_start(ap, name);

  obj = va_arg(ap, void *);
  while (obj) {
    Append(decl, obj);
    Putc(' ', decl);
    obj = va_arg(ap, void *);
  }
  va_end(ap);

  ret = Wrapper_new_local(w, name, decl);
  Delete(decl);
  return ret;
}
