//
// (c) 2002-2003, sof.
//
// Dynamic invocation helper classes. The details of how
// to access the .NET object model via the Reflection API
// is taken care of by Invoker.{h,cpp}
//
#include "Invoker.h"

namespace DynInvoke {

static TypeName* ParseType(String* str) {
    int curPos = 0;
    int endPos;

    //    Console::WriteLine("x{0}y", str);
    TypeName* typeName = new TypeName();

    if ( str->get_Chars(0) == '[' ) {
      endPos = str->IndexOf(']');
      curPos = endPos + 1;
      typeName->m_assembly = str->Substring(1,endPos-1);
      typeName->m_length = endPos+1;
    }
    String* delimStr = " ,()";
    Char delims __gc [] = delimStr->ToCharArray();

    endPos  = str->IndexOfAny(delims,curPos);
    //    Console::WriteLine("{0} {1} x{2}x", __box(endPos), __box(curPos), str);
    if ( endPos == -1 ) {
      typeName->m_class = str->Substring(curPos);
    } else {
      typeName->m_class = str->Substring(curPos,endPos-curPos);
    }

    //    typeName->m_class = str->Substring(curPos,endPos-curPos);
    typeName->m_length += endPos-curPos;

    return typeName;
}

// Method: GetType(String* typeName);
// 
// Purpose: Assembly-savvy version of Type::GetType()
//
Type* InvokeBridge::GetType(String* typeName) {

  try {
    Type* t = Type::GetType(typeName);
    if (t) return t;
  } catch (Exception*) {
    ;
  }

  for (int i=0;i < InvokeBridge::m_assemblies->Count; i++) {
     try {
       String* stuff = String::Format("{0},{1}",typeName,InvokeBridge::m_assemblies->get_Item(i)->ToString());
       //       Console::WriteLine(stuff);
       Type* t = Type::GetType(stuff);
       if (t) {
	 return t;
       }
     } catch (Exception*) {
       continue;
     }
  }
  return 0;
}

//
// Method:  CreateInstance(String* typeName, Object* [])
//
// Purpose: Assembly-savvy invocation of Activator::CreateInstance
Object* InvokeBridge::CreateInstance(TypeName* typeName,
				     Object* args[]) {

  Object* instance = 0;
  Type*   t = InvokeBridge::GetType(typeName->toStdString());

  //  Console::WriteLine("x{0} y{1}", typeName->toStdString(), t);
  if (!t) {
    try {
      Assembly* localA = Assembly::LoadFrom(typeName->m_assembly);
      t = localA->GetType(typeName->m_class);
    } catch (Exception* e) {
      ;
    }
  }
  
  if (!t) {
    try {
      AppDomain* currentDomain = AppDomain::CurrentDomain;
      
      //      Assembly* stuff[] = currentDomain->GetAssemblies();
      //      for (int i=0;i < stuff.Length; i++) {
      //	Console::WriteLine("x{0} y{1}", stuff[i]->ToString(), stuff[i]->FullName);
      //      }
      //      Console::WriteLine("x{0} y{1}", typeName->toStdString(), t);
      Assembly* localA = Assembly::LoadWithPartialName("HugsAssembly");
      t = localA->GetType(typeName->m_class);
      //      Console::WriteLine("x{0} y{1}", typeName->toStdString(), t);
    } catch (Exception*) {
      ;
    }
  }

  if (t) {
    try {
      Object* o =Activator::CreateInstance(t,(Object* [])args);
      return o;
    } catch (Exception* e) {
      Console::WriteLine("Failure: {0}", e);
      return 0;
    }
  }
}

//
// Method: CreateObject(String* objSpec, Array* args)
//
// Purpose: Given a fully qualified name of a class/type, try
//          to create an instance of it.
//
Object* InvokeBridge::CreateObject(String* assemName,
				   String* objSpec,
				   Array* args) {
 
  Object* instance = 0;

  // Unravel the name of the class/type.
  TypeName* typeName = ParseType(objSpec);
  
  if (assemName != 0 && assemName->Length > 0) {
    typeName->m_assembly = assemName;
  }

  // Try creating the instance..
  try {
    instance = InvokeBridge::CreateInstance(typeName,(Object* [])args);
  } catch (Exception* e) {
    Console::WriteLine("Unable to create instance \"{0}\" {1}", objSpec, e);
    throw(e);
  }
  if (!instance) {
    Console::WriteLine("Unable to create instance \"{0}\"", objSpec);
  }
  return instance;
}

//
// Method:  InvokeMethod
// 
// Purpose: Given a pointer to an already created object, look up
//          one of its method. If found, invoke the method passing it
//          'args' as arguments.
//
Object*
InvokeBridge::InvokeMethod(Object* obj, 
			   String* methName,
			   Array* args) {
  // Get the methods from the type
  MethodInfo* methods __gc[] = obj->GetType()->GetMethods();
  MethodInfo* mInfo;

  if (!methods) {
    Console::WriteLine("InvokeMethod: No matching types found");
    return 0;
  }
			
  System::Reflection::BindingFlags flgs 
    = (System::Reflection::BindingFlags) // why do I need to cast?
      (System::Reflection::BindingFlags::Public       |
       System::Reflection::BindingFlags::NonPublic    |
       System::Reflection::BindingFlags::Instance     |
       System::Reflection::BindingFlags::Static       |
       System::Reflection::BindingFlags::InvokeMethod);
    
  /* Caller is assumed to catch any exceptions raised. */
  return obj->GetType()->InvokeMember(methName,
				      flgs,
				      0,
				      obj,
				      (Object __gc* [])args);
}

//
// Method:  InvokeStaticMethod
// 
// Purpose: Invoke a static method, given the fully qualified name
//          of the method (and its arguments). If found, invoke the
//          method passing it 'args' as arguments.
//
Object* InvokeBridge::InvokeStaticMethod(String* assemName,
					 String* typeAndMethName,
					 Array* args) {

  // Get the methods from the type
  MethodInfo* methods __gc[];
  MethodInfo* mInfo;

  int lastDot = typeAndMethName->LastIndexOf('.');
  String* className = typeAndMethName->Substring(0,lastDot);
  String* methName  = typeAndMethName->Substring(lastDot+1);

  // Unravel the name of the class/type.
  TypeName* typeName = ParseType(className);
  Type* t;
  
  if (assemName != 0 && assemName->Length > 0) {
    typeName->m_assembly = assemName;
  }
  
  try {
    t = InvokeBridge::GetType(typeName->toStdString());
    
    if (!t) {
      try {
	Assembly* localA = Assembly::LoadFrom(typeName->m_assembly);
	t = localA->GetType(typeName->m_class);
	//	Console::WriteLine("InvokeStaticMethod: Type {0} found", t);
      } catch (Exception* e) {
	;
      }
    }

    if (t) {
      methods = t->GetMethods();
    } else {
      Console::WriteLine("InvokeStaticMethod: Type {0} not found", className);
      return 0;
    }
  } catch (Exception *e) {
      Console::WriteLine("InvokeStaticMethod: Type {0} not found", className);
      throw(e);
  }

  System::Reflection::BindingFlags flgs 
    = (System::Reflection::BindingFlags) // why do I need to cast?
      (System::Reflection::BindingFlags::DeclaredOnly |
       System::Reflection::BindingFlags::Public       |
       System::Reflection::BindingFlags::NonPublic    |
       System::Reflection::BindingFlags::Static       |
       System::Reflection::BindingFlags::InvokeMethod);
    
  return t->InvokeMember(methName,
			 flgs,
			 0,
			 0,
			 (Object __gc* [])args);
}

//
// Method:  GetField
//
// Purpose: Fetch the (boxed) value of named field of a given object.
//
Object* InvokeBridge::GetField(Object* obj, System::String* fieldName) {

  FieldInfo* fInfo = obj->GetType()->GetField(fieldName);
  return fInfo->GetValue(obj);
}

//
// Method:  GetStaticField
//
// Purpose: Fetch the (boxed) value of named static field.
//
Object* InvokeBridge::GetStaticField(System::String* clsName,
				     System::String* fieldName) {

  Type* ty = InvokeBridge::GetType(clsName);
  System::Reflection::BindingFlags static_field_flgs 
    = (System::Reflection::BindingFlags)
    (System::Reflection::BindingFlags::Public       |
     System::Reflection::BindingFlags::NonPublic    |
     System::Reflection::BindingFlags::FlattenHierarchy |
     System::Reflection::BindingFlags::Static);

  FieldInfo* fInfo = ty->GetField(fieldName, static_field_flgs);
  return fInfo->GetValue(0); // according to doc, ok to pass any val here.
}

//
// Method:  SetField
//
// Purpose: Replace the (boxed) value of named field of a given object.
//
void InvokeBridge::SetField(Object* obj, System::String* fieldName, Object* val) {

  FieldInfo* fInfo = obj->GetType()->GetField(fieldName);
  fInfo->SetValue(obj,val);
  return;
}

//
// Method:  SetStaticField
//
// Purpose: Replace the (boxed) value of named static field.
//
void InvokeBridge::SetStaticField(System::String* clsName,
				  System::String* fieldName,
				  Object* val) {

  Type* ty = InvokeBridge::GetType(clsName);
  System::Reflection::BindingFlags static_field_flgs 
    = (System::Reflection::BindingFlags)
    (System::Reflection::BindingFlags::Public       |
     System::Reflection::BindingFlags::NonPublic    |
     System::Reflection::BindingFlags::FlattenHierarchy |
     System::Reflection::BindingFlags::Static);
  
  FieldInfo* fInfo = ty->GetField(fieldName,static_field_flgs);
  fInfo->SetValue(0,val);
  return;
}

Array* InvokeBridge::NewArgArray(int sz)
{
 return Array::CreateInstance(__typeof(Object), sz); 
}

void InvokeBridge::SetArg(Array* arr, Object* val, int idx)
{
 arr->SetValue(val,idx);
}

Object* InvokeBridge::GetArg(Array* arr, int idx)
{
 return arr->GetValue(idx);
}

} /* namespace */
