/*
  @copyright Russell Standish 2000-2013
  @author Russell Standish
  This file is part of Classdesc

  Open source licensed under the MIT license. See LICENSE for details.
*/

#include "javaClass_base.h"
#include <sstream>
extern std::map<std::string, classdesc::Functional_ptr> method_registry;

namespace classdesc
{
  struct JNIexporterBase
  {
    std::string name;
    virtual void exportClass(javaClass_t& cf) const=0;
  };

  template <class T>
  struct JNIexporter: public JNIexporterBase
  {
    T archetype;
    void exportClass(javaClass_t& cf) const {javaClass(cf, name, archetype);}
  };

  // this is a singleton to get around library load order issues
  typedef std::vector<shared_ptr<JNIexporterBase> > JNIRegistry;
  JNIRegistry& JNIexporterRegistry();

  template <class T>
  struct JNIRegister
  {
    JNIRegister(const std::string& name) {
      shared_ptr<JNIexporterBase> object(new JNIexporter<T>);
      object->name=name;
      JNIexporterRegistry().push_back(object);
      std::cout << "registering "<<name << std::endl;
    }
  };

  //void write_JNI(const JNIexporterBase& object);

  /// a base class to add a virtual destructor to supplied types t
  /// allow for cleanup
  struct BaseForCleanup
  {
    virtual ~BaseForCleanup() {}
  };
  template <class T>
  struct Cleanup: public T, public BaseForCleanup {};

  /// a container storing all allocated C++ objects, for cleanup at DLL unload
  typedef std::vector<shared_ptr<BaseForCleanup> > CleanupContainer;
  extern CleanupContainer cleanupContainer;

  struct register_java_class: public javaClass_t
  {
    void add_functor(const std::string& name, Functional_ptr f, const std::string& sig)
    {
      method_registry.insert(make_pair(name,f));
    }
};



  /// initialise a Java CppObject with a newly created C++ object of type T
  template <class T>
  void registerCppObject(JNIEnv *env, jobject obj)
  {
    try
      {
        jclass clazz=env->GetObjectClass(obj);
        assert(clazz);
        // register is chosen because it is a C++ keyword, so won't
        // conflict with any method names
        jfieldID fld=env->GetFieldID(clazz,"register","Ljava/lang/String;");
        cleanupContainer.push_back(shared_ptr<BaseForCleanup>(new Cleanup<T>));
        std::ostringstream objName;
        objName << std::hex << cleanupContainer.back().get();
        std::string objNameStr=objName.str();
        std::vector<jchar> jname(objNameStr.begin(), objNameStr.end());
        env->SetObjectField(obj,fld,env->NewString(&jname[0], jname.size()));
        // register the objects instance variables and methods with the
        // method registry
        register_java_class jc;
        javaClass(jc,objName.str(),dynamic_cast<T&>(*cleanupContainer.back()));
      }
    catch (std::exception& ex)
      {
        env->ThrowNew(env->FindClass("java/lang/Throwable"),ex.what());
      }
  }

  /// Obtain the name of the C++ object pointed to by a Java CppObject
  inline std::string cppObject(JNIEnv *env, jobject obj)
  {
    jfieldID fld=env->GetFieldID(env->GetObjectClass(obj),"register",
                          "Ljava/lang/String;");
    jstring jname=reinterpret_cast<jstring>(env->GetObjectField(obj,fld));
    const jchar* jnameptr=env->GetStringChars(jname,NULL);
    std::string name(jnameptr, jnameptr+env->GetStringLength(jname));
    env->ReleaseStringChars(jname,jnameptr);
    return name;
  }


  template <class M> struct typeof_member;
  template <class type, class C> struct typeof_member<type (C::*)>
  { typedef type T;};

  /// specialise for instance variables to use getter_setters
  template <class O, class M> typename
  enable_if<is_member_object_pointer<M>, 
            getter_setter<typename typeof_member<M>::T>
            >::T
  make_functional(O& obj, const M& member) 
  {
    return getter_setter<typename typeof_member<M>::T>(obj.*member);
  }

  /// specialise for member function variables 
  template <class O, class M> typename
  enable_if<is_member_function_pointer<M>, 
            Functional<functional::bound_method<O,M> > >::T
  make_functional(O& obj, const M& member) 
  {
    return Functional<functional::bound_method<O,M> >(bind1st(member, obj));
  }


}
