/* ----------------------------------------------------------------------------- 
 * 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.
 *
 * interface.cxx
 *
 * This module contains support for the interface feature.
 * This feature is used in language modules where the target language does not
 * naturally support C++ style multiple inheritance, but does support inheritance 
 * from multiple interfaces.
 * ----------------------------------------------------------------------------- */

#include "swigmod.h"

static bool interface_feature_enabled = false;

/* -----------------------------------------------------------------------------
 * collect_interface_methods()
 *
 * Create a list of all the methods from the base classes of class n that are
 * marked as an interface. The resulting list is thus the list of methods that
 * need to be implemented in order for n to be non-abstract.
 * ----------------------------------------------------------------------------- */

static List *collect_interface_methods(Node *n) {
  List *methods = NewList();
  if (Hash *bases = Getattr(n, "interface:bases")) {
    List *keys = Keys(bases);
    for (Iterator base = First(keys); base.item; base = Next(base)) {
      Node *cls = Getattr(bases, base.item);
      if (cls == n)
	continue;
      for (Node *child = firstChild(cls); child; child = nextSibling(child)) {
	if (Cmp(nodeType(child), "cdecl") == 0) {
	  if (GetFlag(child, "feature:ignore") || Getattr(child, "interface:owner"))
	    continue; // skip methods propagated to bases
	  Node *m = Copy(child);
	  set_nextSibling(m, NIL);
	  set_previousSibling(m, NIL);
	  Setattr(m, "interface:owner", cls);
	  Append(methods, m);
	}
      }
    }
    Delete(keys);
  }
  return methods;
}

/* -----------------------------------------------------------------------------
 * collect_interface_bases
 * ----------------------------------------------------------------------------- */

static void collect_interface_bases(Hash *bases, Node *n) {
  if (Getattr(n, "feature:interface")) {
    String *name = Getattr(n, "interface:name");
    if (!Getattr(bases, name))
      Setattr(bases, name, n);
  }

  if (List *baselist = Getattr(n, "bases")) {
    for (Iterator base = First(baselist); base.item; base = Next(base)) {
      if (!GetFlag(base.item, "feature:ignore")) {
	if (Getattr(base.item, "feature:interface"))
	  collect_interface_bases(bases, base.item);
      }
    }
  }
}

/* -----------------------------------------------------------------------------
 * collect_interface_base_classes()
 *
 * Create a hash containing all the classes up the inheritance hierarchy
 * marked with feature:interface (including this class n).
 * Stops going up the inheritance chain as soon as a class is found without
 * feature:interface.
 * The idea is to find all the base interfaces that a class must implement.
 * ----------------------------------------------------------------------------- */

static void collect_interface_base_classes(Node *n) {
  if (Getattr(n, "feature:interface")) {
    // check all bases are also interfaces
    if (List *baselist = Getattr(n, "bases")) {
      for (Iterator base = First(baselist); base.item; base = Next(base)) {
	if (!GetFlag(base.item, "feature:ignore")) {
	  if (!Getattr(base.item, "feature:interface")) {
	    Swig_error(Getfile(n), Getline(n), "Base class '%s' of '%s' is not similarly marked as an interface.\n", SwigType_namestr(Getattr(base.item, "name")), SwigType_namestr(Getattr(n, "name")));
	    SWIG_exit(EXIT_FAILURE);
	  }
	}
      }
    }
  }

  Hash *interface_bases = NewHash();
  collect_interface_bases(interface_bases, n);
  if (Len(interface_bases) == 0)
    Delete(interface_bases);
  else
    Setattr(n, "interface:bases", interface_bases);
}

/* -----------------------------------------------------------------------------
 * process_interface_name()
 * ----------------------------------------------------------------------------- */

static void process_interface_name(Node *n) {
  if (Getattr(n, "feature:interface")) {
    String *interface_name = Getattr(n, "feature:interface:name");
    if (!Len(interface_name)) {
      Swig_error(Getfile(n), Getline(n), "The interface feature for '%s' is missing the name attribute.\n", SwigType_namestr(Getattr(n, "name")));
      SWIG_exit(EXIT_FAILURE);
    }
    if (Strchr(interface_name, '%')) {
      String *name = NewStringf(interface_name, Getattr(n, "sym:name"));
      Setattr(n, "interface:name", name);
    } else {
      Setattr(n, "interface:name", interface_name);
    }
  }
}

/* -----------------------------------------------------------------------------
 * Swig_interface_propagate_methods()
 *
 * Find all the base classes marked as an interface (with feature:interface) for
 * class node n. For each of these, add all of its methods as methods of n so that
 * n is not abstract. If class n is also marked as an interface, it will remain
 * abstract and not have any methods added.
 * ----------------------------------------------------------------------------- */

void Swig_interface_propagate_methods(Node *n) {
  if (interface_feature_enabled) {
    process_interface_name(n);
    collect_interface_base_classes(n);
    List *methods = collect_interface_methods(n);
    bool is_interface = Getattr(n, "feature:interface") != 0;
    for (Iterator mi = First(methods); mi.item; mi = Next(mi)) {
      if (!is_interface && GetFlag(mi.item, "abstract"))
	continue;
      String *this_decl = Getattr(mi.item, "decl");
      String *this_decl_resolved = SwigType_typedef_resolve_all(this_decl);
      bool identically_overloaded_method = false; // true when a base class' method is implemented in n
      if (SwigType_isfunction(this_decl_resolved)) {
	String *name = Getattr(mi.item, "name");
	for (Node *child = firstChild(n); child; child = nextSibling(child)) {
	  if (Getattr(child, "interface:owner"))
	    break; // at the end of the list are newly appended methods
	  if (Cmp(nodeType(child), "cdecl") == 0) {
	    if (checkAttribute(child, "name", name)) {
	      String *decl = SwigType_typedef_resolve_all(Getattr(child, "decl"));
	      identically_overloaded_method = Strcmp(decl, this_decl_resolved) == 0;
	      Delete(decl);
	      if (identically_overloaded_method)
		break;
	    }
	  }
	}
      }
      Delete(this_decl_resolved);
      if (!identically_overloaded_method) {
	// TODO: Fix if the method is overloaded with different arguments / has default args
	appendChild(n, mi.item);
      } else {
	Delete(mi.item);
      }
    }
    Delete(methods);
  }
}

/* -----------------------------------------------------------------------------
 * Swig_interface_feature_enable()
 *
 * Turn on interface feature support
 * ----------------------------------------------------------------------------- */

void Swig_interface_feature_enable() {
  interface_feature_enabled = true;
}
