/*
    File: metaClass.cc
*/

/*
Copyright (c) 2014, Christian E. Schafmeister

CLASP is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

See directory 'clasp/licenses' for full details.

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/* -^- */
// #define DEBUG_LEVEL_FULL

#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunneeded-internal-declaration"
#pragma clang diagnostic pop
#include <clasp/core/foundation.h>
#include <clasp/core/lisp.h>
#include <clasp/core/metaClass.h>
#include <clasp/core/package.h>
#include <clasp/core/pointer.h>
#include <clasp/core/symbolTable.h>
#include <clasp/core/cons.h>
#include <clasp/core/lambdaListHandler.h>
#include <clasp/core/lispList.h>
#include <clasp/core/array.h>
#include <clasp/core/instance.h>
#include <clasp/core/evaluator.h>
#include <clasp/core/hashTable.h>
#include <clasp/core/hashTableEq.h>
#include <clasp/core/wrappers.h>

#define NAMESPACE_gctools_mem
#include <clasp/gctools/gc_interface.h>
#undef NAMESPACE_gctools_mem

SYMBOL_EXPORT_SC_(ClosPkg, forward_referenced_class);
SYMBOL_EXPORT_SC_(ClPkg, built_in_class);
SYMBOL_EXPORT_SC_(ClPkg, standard_class);
SYMBOL_EXPORT_SC_(CorePkg, std_class);
SYMBOL_EXPORT_SC_(ClPkg, structure_class);
SYMBOL_EXPORT_SC_(CorePkg, cxx_class);

namespace core {

gc::Nilable<Instance_sp> identifyCxxDerivableAncestorClass(Instance_sp aClass) {
  if (aClass->cxxClassP()) {
    if (aClass->cxxDerivableClassP()) {
      return aClass;
    }
  }
  for (auto supers : aClass->directSuperclasses()) {
    Instance_sp aSuperClass = gc::As<Instance_sp>(oCar(supers));
    gc::Nilable<Instance_sp> taPossibleCxxDerivableAncestorClass = identifyCxxDerivableAncestorClass(aSuperClass);
    if (taPossibleCxxDerivableAncestorClass.notnilp())
      return taPossibleCxxDerivableAncestorClass;
  }
  return nil<Instance_O>();
}

SYMBOL_EXPORT_SC_(KeywordPkg, creator);

DOCGROUP(clasp);
CL_DEFUN T_sp core__compute_instance_creator(T_sp tinstance, T_sp tmetaclass, List_sp superclasses) {
  // If there is no metaclass - then use _TheStandardClass
  if (tmetaclass.nilp()) {
    tmetaclass = _lisp->_Roots._TheStandardClass;
  }
  Instance_sp instance = gc::As<Instance_sp>(tinstance);
  Instance_sp metaclass = gc::As<Instance_sp>(tmetaclass);
  // If instance class already has an allocator then leave it alone
  if (instance->CLASS_has_creator())
    return instance->CLASS_get_creator();
  if (metaclass->_className() == clos::_sym_funcallable_standard_class) {
    Creator_sp funcallableInstanceCreator = gc::GC<FuncallableInstanceCreator_O>::allocate(instance);
    return funcallableInstanceCreator;
  };
  Instance_sp aCxxDerivableAncestorClass_unsafe; // The constructor will initialize this pointer to NULL.
#ifdef DEBUG_CLASS_INSTANCE
  printf("%s:%d:%s   for class -> %s   superclasses -> %s\n", __FILE__, __LINE__, __FUNCTION__, _rep_(instance->name()).c_str(),
         _rep_(superclasses).c_str());
#endif
  for (auto cur : superclasses) {
    T_sp tsuper = oCar(cur);
    Instance_sp aSuperClass = gc::As<Instance_sp>(tsuper);
    if (aSuperClass->cxxClassP() && !aSuperClass->cxxDerivableClassP()) {
      SIMPLE_ERROR("You cannot derive from the non-derivable C++ class {}\n"
                   "any C++ class you want to derive from must inherit from the clbind derivable class",
                   _rep_(aSuperClass->_className()));
    }
    gc::Nilable<Instance_sp> aPossibleCxxDerivableAncestorClass = identifyCxxDerivableAncestorClass(aSuperClass);
    if (aPossibleCxxDerivableAncestorClass.notnilp()) {
      if (!aCxxDerivableAncestorClass_unsafe) {
        aCxxDerivableAncestorClass_unsafe = aPossibleCxxDerivableAncestorClass;
      } else {
        SIMPLE_ERROR("Only one derivable C++ class is allowed to be"
                     " derived from at a time instead we have two {} and {}",
                     _rep_(aCxxDerivableAncestorClass_unsafe->_className()),
                     _rep_(aPossibleCxxDerivableAncestorClass->_className()));
      }
    }
  }
  if (aCxxDerivableAncestorClass_unsafe) {
    // Here aCxxDerivableAncestorClass_unsafe has a value - so it's ok to dereference it
    Creator_sp aCxxAllocator(gctools::As<Creator_sp>(aCxxDerivableAncestorClass_unsafe->CLASS_get_creator()));
#ifdef DEBUG_CLASS_INSTANCE
    printf("%s:%d   duplicating aCxxDerivableAncestorClass_unsafe %s creator\n", __FILE__, __LINE__,
           _rep_(aCxxDerivableAncestorClass_unsafe).c_str());
#endif
    Creator_sp dup = aCxxAllocator->duplicateForClassName(instance->_className());
    return dup;
  } else {
    // I think this is the most common outcome -
#ifdef DEBUG_CLASS_INSTANCE
    printf("%s:%d   Creating an InstanceCreator_O for the class: %s\n", __FILE__, __LINE__, _rep_(instance->name()).c_str());
#endif
    InstanceCreator_sp instanceAllocator = gc::GC<InstanceCreator_O>::allocate(instance);
    return instanceAllocator;
  }
}

/*! Return true if every member of subset is in superset */
bool subsetp(List_sp subset, List_sp superset) {
  ASSERT(subset);
  ASSERT(superset);
  if (subset.nilp() && superset.nilp())
    return true;
  if (superset.nilp())
    return false;
  if (subset.nilp())
    return true;
  for (; subset.notnilp(); subset = oCdr(subset)) {
    T_sp o = oCar(subset);
    if (!superset.asCons()->memberEq(o))
      return false;
  }
  return true;
}

CL_LAMBDA(low high);
CL_DECLARE();
CL_DOCSTRING(R"dx(subclassp)dx");
DOCGROUP(clasp);
CL_DEFUN bool core__subclassp(T_sp low, T_sp high) {
  ASSERT(clos__classp(low));
  ASSERT(clos__classp(high));
  if (low == high)
    return true;
  if (Instance_sp lowmc = low.asOrNull<Instance_O>()) {
    List_sp lowClassPrecedenceList = lowmc->instanceRef(Instance_O::REF_CLASS_CLASS_PRECEDENCE_LIST); // classPrecedenceList();
    return lowClassPrecedenceList.asCons()->memberEq(high).notnilp();
  }
  SIMPLE_ERROR("Illegal argument for subclassp: {}", _rep_(low));
};

SYMBOL_SC_(CorePkg, subclassp);
SYMBOL_SC_(CorePkg, allocateRawClass);

}; // namespace core
