/* -----------------------------------------------------------------------------
 * 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 http://www.swig.org/legal.html.
 *
 * nested.cxx
 *
 * Nested structs support
 * ----------------------------------------------------------------------------- */

#include "swigmod.h"
#include "cparse.h"

// Nested classes processing section
static Hash *classhash = 0;

static String *make_name(Node *n, String *name, SwigType *decl) {
  int destructor = name && (*(Char(name)) == '~');
  if (String *yyrename = Getattr(n, "class_rename")) {
    String *s = NewString(yyrename);
    Delattr(n, "class_rename");
    if (destructor && (*(Char(s)) != '~')) {
      Insert(s, 0, "~");
    }
    return s;
  }

  if (!name)
    return 0;
  return Swig_name_make(n, 0, name, decl, 0);
}

// C version of add_symbols()
static void add_symbols_c(Node *n) {
  String *decl;
  String *wrn = 0;
  String *symname = 0;
  int iscdecl = Cmp(nodeType(n), "cdecl") == 0;
  Setattr(n, "ismember", "1");
  Setattr(n, "access", "public");
  if (Getattr(n, "sym:name"))
    return;
  decl = Getattr(n, "decl");
  if (!SwigType_isfunction(decl)) {
    String *name = Getattr(n, "name");
    String *makename = Getattr(n, "parser:makename");
    if (iscdecl) {
      String *storage = Getattr(n, "storage");
      if (Cmp(storage, "typedef") == 0) {
	Setattr(n, "kind", "typedef");
      } else {
	SwigType *type = Getattr(n, "type");
	String *value = Getattr(n, "value");
	Setattr(n, "kind", "variable");
	if (value && Len(value)) {
	  Setattr(n, "hasvalue", "1");
	}
	if (type) {
	  SwigType *ty;
	  SwigType *tmp = 0;
	  if (decl) {
	    ty = tmp = Copy(type);
	    SwigType_push(ty, decl);
	  } else {
	    ty = type;
	  }
	  if (!SwigType_ismutable(ty)) {
	    SetFlag(n, "hasconsttype");
	    SetFlag(n, "feature:immutable");
	  }
	  if (tmp)
	    Delete(tmp);
	}
	if (!type) {
	  Printf(stderr, "notype name %s\n", name);
	}
      }
    }
    Swig_features_get(Swig_cparse_features(), 0, name, 0, n);
    if (makename) {
      symname = make_name(n, makename, 0);
      Delattr(n, "parser:makename");	/* temporary information, don't leave it hanging around */
    } else {
      makename = name;
      symname = make_name(n, makename, 0);
    }

    if (!symname) {
      symname = Copy(Getattr(n, "unnamed"));
    }
    if (symname) {
      wrn = Swig_name_warning(n, 0, symname, 0);
    }
  } else {
    String *name = Getattr(n, "name");
    SwigType *fdecl = Copy(decl);
    SwigType *fun = SwigType_pop_function(fdecl);
    if (iscdecl) {
      Setattr(n, "kind", "function");
    }

    Swig_features_get(Swig_cparse_features(), 0, name, fun, n);

    symname = make_name(n, name, fun);
    wrn = Swig_name_warning(n, 0, symname, fun);

    Delete(fdecl);
    Delete(fun);

  }
  if (!symname)
    return;
  if (GetFlag(n, "feature:ignore")) {
    /* Only add to C symbol table and continue */
    Swig_symbol_add(0, n);
  } else if (strncmp(Char(symname), "$ignore", 7) == 0) {
    char *c = Char(symname) + 7;
    SetFlag(n, "feature:ignore");
    if (strlen(c)) {
      SWIG_WARN_NODE_BEGIN(n);
      Swig_warning(0, Getfile(n), Getline(n), "%s\n", c + 1);
      SWIG_WARN_NODE_END(n);
    }
    Swig_symbol_add(0, n);
  } else {
    Node *c;
    if ((wrn) && (Len(wrn))) {
      String *metaname = symname;
      if (!Getmeta(metaname, "already_warned")) {
	SWIG_WARN_NODE_BEGIN(n);
	Swig_warning(0, Getfile(n), Getline(n), "%s\n", wrn);
	SWIG_WARN_NODE_END(n);
	Setmeta(metaname, "already_warned", "1");
      }
    }
    c = Swig_symbol_add(symname, n);

    if (c != n) {
      /* symbol conflict attempting to add in the new symbol */
      if (Getattr(n, "sym:weak")) {
	Setattr(n, "sym:name", symname);
      } else {
	String *e = NewStringEmpty();
	String *en = NewStringEmpty();
	String *ec = NewStringEmpty();
	int redefined = Swig_need_redefined_warn(n, c, true);
	if (redefined) {
	  Printf(en, "Identifier '%s' redefined (ignored)", symname);
	  Printf(ec, "previous definition of '%s'", symname);
	} else {
	  Printf(en, "Redundant redeclaration of '%s'", symname);
	  Printf(ec, "previous declaration of '%s'", symname);
	}
	if (Cmp(symname, Getattr(n, "name"))) {
	  Printf(en, " (Renamed from '%s')", SwigType_namestr(Getattr(n, "name")));
	}
	Printf(en, ",");
	if (Cmp(symname, Getattr(c, "name"))) {
	  Printf(ec, " (Renamed from '%s')", SwigType_namestr(Getattr(c, "name")));
	}
	Printf(ec, ".");
	SWIG_WARN_NODE_BEGIN(n);
	if (redefined) {
	  Swig_warning(WARN_PARSE_REDEFINED, Getfile(n), Getline(n), "%s\n", en);
	  Swig_warning(WARN_PARSE_REDEFINED, Getfile(c), Getline(c), "%s\n", ec);
	} else {
	  Swig_warning(WARN_PARSE_REDUNDANT, Getfile(n), Getline(n), "%s\n", en);
	  Swig_warning(WARN_PARSE_REDUNDANT, Getfile(c), Getline(c), "%s\n", ec);
	}
	SWIG_WARN_NODE_END(n);
	Printf(e, "%s:%d:%s\n%s:%d:%s\n", Getfile(n), Getline(n), en, Getfile(c), Getline(c), ec);
	Setattr(n, "error", e);
	Delete(e);
	Delete(en);
	Delete(ec);
      }
    }
  }
  Delete(symname);
}

/* Strips C-style and C++-style comments from string in-place. */
static void strip_comments(char *string) {
  int state = 0;
  /*
   * 0 - not in comment
   * 1 - in c-style comment
   * 2 - in c++-style comment
   * 3 - in string
   * 4 - after reading / not in comments
   * 5 - after reading * in c-style comments
   * 6 - after reading \ in strings
   */
  char *c = string;
  while (*c) {
    switch (state) {
    case 0:
      if (*c == '\"')
	state = 3;
      else if (*c == '/')
	state = 4;
      break;
    case 1:
      if (*c == '*')
	state = 5;
      *c = ' ';
      break;
    case 2:
      if (*c == '\n')
	state = 0;
      else
	*c = ' ';
      break;
    case 3:
      if (*c == '\"')
	state = 0;
      else if (*c == '\\')
	state = 6;
      break;
    case 4:
      if (*c == '/') {
	*(c - 1) = ' ';
	*c = ' ';
	state = 2;
      } else if (*c == '*') {
	*(c - 1) = ' ';
	*c = ' ';
	state = 1;
      } else
	state = 0;
      break;
    case 5:
      if (*c == '/')
	state = 0;
      else
	state = 1;
      *c = ' ';
      break;
    case 6:
      state = 3;
      break;
    }
    ++c;
  }
}

// Create a %insert with a typedef to make a new name visible to C
static Node *create_insert(Node *n, bool noTypedef = false) {
  // format a typedef
  String *ccode = Getattr(n, "code");
  Push(ccode, " ");
  if (noTypedef) {
    Push(ccode, Getattr(n, "name"));
    Push(ccode, " ");
    Push(ccode, Getattr(n, "kind"));
  } else {
    Push(ccode, Getattr(n, "kind"));
    Push(ccode, "typedef ");
    Append(ccode, " ");
    Append(ccode, Getattr(n, "tdname"));
  }
  Append(ccode, ";");

  /* Strip comments - further code may break in presence of comments. */
  strip_comments(Char(ccode));

  /* Make all SWIG created typedef structs/unions/classes unnamed else
     redefinition errors occur - nasty hack alert. */
  if (!noTypedef) {
    const char *types_array[3] = { "struct", "union", "class" };
    for (int i = 0; i < 3; i++) {
      char *code_ptr = Char(ccode);
      while (code_ptr) {
	/* Replace struct name (as in 'struct name {...}' ) with whitespace
	   name will be between struct and opening brace */

	code_ptr = strstr(code_ptr, types_array[i]);
	if (code_ptr) {
	  char *open_bracket_pos;
	  code_ptr += strlen(types_array[i]);
	  open_bracket_pos = strchr(code_ptr, '{');
	  if (open_bracket_pos) {
	    /* Make sure we don't have something like struct A a; */
	    char *semi_colon_pos = strchr(code_ptr, ';');
	    if (!(semi_colon_pos && (semi_colon_pos < open_bracket_pos)))
	      while (code_ptr < open_bracket_pos)
		*code_ptr++ = ' ';
	  }
	}
      }
    }
  }
  {
    /* Remove SWIG directive %constant which may be left in the SWIG created typedefs */
    char *code_ptr = Char(ccode);
    while (code_ptr) {
      code_ptr = strstr(code_ptr, "%constant");
      if (code_ptr) {
	char *directive_end_pos = strchr(code_ptr, ';');
	if (directive_end_pos) {
	  while (code_ptr <= directive_end_pos)
	    *code_ptr++ = ' ';
	}
      }
    }
  }
  Node *newnode = NewHash();
  set_nodeType(newnode, "insert");
  Setfile(newnode, Getfile(n));
  Setline(newnode, Getline(n));
  String *code = NewStringEmpty();
  Wrapper_pretty_print(ccode, code);
  Setattr(newnode, "code", code);
  Delete(code);
  Delattr(n, "code");
  return newnode;
}

static void insertNodeAfter(Node *n, Node *c) {
  Node *g = parentNode(n);
  set_parentNode(c, g);
  Node *ns = nextSibling(n);
  if (Node *outer = Getattr(c, "nested:outer")) {
    while (ns && outer == Getattr(ns, "nested:outer")) {
      n = ns;
      ns = nextSibling(n);
    }
  }
  if (!ns) {
    set_lastChild(g, c);
  } else {
    set_nextSibling(c, ns);
    set_previousSibling(ns, c);
  }
  set_nextSibling(n, c);
  set_previousSibling(c, n);
}

void Swig_nested_name_unnamed_c_structs(Node *n) {
  if (!classhash)
    classhash = Getattr(n, "classes");
  Node *c = firstChild(n);
  while (c) {
    Node *next = nextSibling(c);
    if (String *declName = Getattr(c, "nested:unnamed")) {
      if (Node *outer = Getattr(c, "nested:outer")) {
	// generate a name
	String *name = NewStringf("%s_%s", Getattr(outer, "name"), declName);
	Delattr(c, "nested:unnamed");
	// set the name to the class and symbol table
	Setattr(c, "tdname", name);
	Setattr(c, "name", name);
	Swig_symbol_setscope(Getattr(c, "symtab"));
	Swig_symbol_setscopename(name);
	// now that we have a name - gather base symbols
	if (List *publicBases = Getattr(c, "baselist")) {
	  List *bases = Swig_make_inherit_list(name, publicBases, 0);
	  Swig_inherit_base_symbols(bases);
	  Delete(bases);
	}
	Setattr(classhash, name, c);

	// Merge the extension into the symbol table
	if (Node *am = Getattr(Swig_extend_hash(), name)) {
	  Swig_extend_merge(c, am);
	  Swig_extend_append_previous(c, am);
	  Delattr(Swig_extend_hash(), name);
	}
	Swig_symbol_popscope();

	// process declarations following this type (assign correct new type)
	SwigType *ty = Copy(name);
	Node *decl = nextSibling(c);
	List *declList = NewList();
	while (decl && Getattr(decl, "nested:unnamedtype") == c) {
	  Setattr(decl, "type", ty);
	  Append(declList, decl);
	  Delattr(decl, "nested:unnamedtype");
	  SetFlag(decl, "feature:immutable");
	  add_symbols_c(decl);
	  decl = nextSibling(decl);
	}
	Delete(ty);
	Swig_symbol_setscope(Swig_symbol_global_scope());
	add_symbols_c(c);

	Node *ins = create_insert(c);
	insertNodeAfter(c, ins);
	removeNode(c);
	insertNodeAfter(n, c);
	Delete(ins);
	Delattr(c, "nested:outer");
      } else {
	// global unnamed struct - ignore it and it's instances
	SetFlag(c, "feature:ignore");
	while (next && Getattr(next, "nested:unnamedtype") == c) {
	  SetFlag(next, "feature:ignore");
	  next = nextSibling(next);
	}
	c = next;
	continue;
      }
    } else if (cparse_cplusplusout) {
      if (Getattr(c, "nested:outer")) {
	Node *ins = create_insert(c, true);
	insertNodeAfter(c, ins);
	Delete(ins);
	Delattr(c, "nested:outer");
      }
    }
    // process children
    Swig_nested_name_unnamed_c_structs(c);
    c = next;
  }
}

static void remove_outer_class_reference(Node *n) {
  for (Node *c = firstChild(n); c; c = nextSibling(c)) {
    if (GetFlag(c, "feature:flatnested") || Language::instance()->nestedClassesSupport() == Language::NCS_None) {
      Delattr(c, "nested:outer");
      remove_outer_class_reference(c);
    }
  }
}

void Swig_nested_process_classes(Node *n) {
  Node *c = firstChild(n);
  while (c) {
    Node *next = nextSibling(c);
    if (!Getattr(c, "templatetype")) {
      if (GetFlag(c, "nested") && (GetFlag(c, "feature:flatnested") || Language::instance()->nestedClassesSupport() == Language::NCS_None)) {
	removeNode(c);
	if (!checkAttribute(c, "access", "public"))
	  SetFlag(c, "feature:ignore");
	else if (Strcmp(nodeType(n),"extend") == 0 && Strcmp(nodeType(parentNode(n)),"class") == 0)
	  insertNodeAfter(parentNode(n), c);
	else
	  insertNodeAfter(n, c);
      }
      Swig_nested_process_classes(c);
    }
    c = next;
  }
  remove_outer_class_reference(n);
}

