using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Csla.Properties;
namespace Csla
{
  internal static class MethodCaller
  {
    const BindingFlags allLevelFlags =
      BindingFlags.FlattenHierarchy |
      BindingFlags.Instance |
      BindingFlags.Public |
      BindingFlags.NonPublic;
    const BindingFlags oneLevelFlags =
      BindingFlags.DeclaredOnly |
      BindingFlags.Instance |
      BindingFlags.Public |
      BindingFlags.NonPublic;
    /// 
    /// Gets a reference to the DataPortal_Create method for
    /// the specified business object type.
    /// 
    /// Type of the business object.
    /// Criteria parameter value.
    /// 
    /// If the criteria parameter value is an integer, that is a special
    /// flag indicating that the parameter should be considered missing
    /// (not Nothing/null - just not there).
    /// 
    public static MethodInfo GetCreateMethod(Type objectType, object criteria)
    {
      MethodInfo method = null;
      if (criteria is int)
      {
        // an "Integer" criteria is a special flag indicating
        // that criteria is empty and should not be used
        method = MethodCaller.GetMethod(objectType, "DataPortal_Create");
      }
      else
        method = MethodCaller.GetMethod(objectType, "DataPortal_Create", criteria);
      return method;
    }
    /// 
    /// Gets a reference to the DataPortal_Fetch method for
    /// the specified business object type.
    /// 
    /// Type of the business object.
    /// Criteria parameter value.
    /// 
    /// If the criteria parameter value is an integer, that is a special
    /// flag indicating that the parameter should be considered missing
    /// (not Nothing/null - just not there).
    /// 
    public static MethodInfo GetFetchMethod(Type objectType, object criteria)
    {
      MethodInfo method = null;
      if (criteria is int)
      {
        // an "Integer" criteria is a special flag indicating
        // that criteria is empty and should not be used
        method = MethodCaller.GetMethod(objectType, "DataPortal_Fetch");
      }
      else
        method = MethodCaller.GetMethod(objectType, "DataPortal_Fetch", criteria);
      return method;
    }
    /// 
    /// Uses reflection to dynamically invoke a method
    /// if that method is implemented on the target object.
    /// 
    public static object CallMethodIfImplemented(
      object obj, string method, params object[] parameters)
    {
      MethodInfo info = GetMethod(obj.GetType(), method, parameters);
      if (info != null)
        return CallMethod(obj, info, parameters);
      else
        return null;
    }
    /// 
    /// Uses reflection to dynamically invoke a method,
    /// throwing an exception if it is not
    /// implemented on the target object.
    /// 
    public static object CallMethod(
      object obj, string method, params object[] parameters)
    {
      MethodInfo info = GetMethod(obj.GetType(), method, parameters);
      if (info == null)
        throw new NotImplementedException(
          method + " " + Resources.MethodNotImplemented);
      return CallMethod(obj, info, parameters);
    }
    /// 
    /// Uses reflection to dynamically invoke a method,
    /// throwing an exception if it is not
    /// implemented on the target object.
    /// 
    public static object CallMethod(
      object obj, MethodInfo info, params object[] parameters)
    {
      // call a private method on the object
      object result;
      try
      {
        result = info.Invoke(obj, parameters);
      }
      catch (Exception e)
      {
        throw new Csla.Server.CallMethodException(
          info.Name + " " + Resources.MethodCallFailed, e.InnerException);
      }
      return result;
    }
    /// 
    /// Uses reflection to locate a matching method
    /// on the target object.
    /// 
    public static MethodInfo GetMethod(
      Type objectType, string method, params object[] parameters)
    {
      MethodInfo result = null;
      // try to find a strongly typed match
      // put all param types into a list of Type
      List types = new List();
      foreach (object item in parameters)
      {
        if (item == null)
          types.Add(typeof(object));
        else
          types.Add(item.GetType());
      }
      // first see if there's a matching method
      // where all params match types
      result = FindMethod(objectType, method, types.ToArray());
      if (result == null)
      {
        // no match found - so look for any method
        // with the right number of parameters
        result = FindMethod(objectType, method, parameters.Length);
      }
      // no strongly typed match found, get default
      if (result == null)
      {
        try
        { 
          result = objectType.GetMethod(method, allLevelFlags); 
        }
        catch (AmbiguousMatchException)
        {
          MethodInfo[] methods = objectType.GetMethods();
          foreach (MethodInfo m in methods)
            if (m.Name == method && m.GetParameters().Length == parameters.Length)
            {
              result = m;
              break;
            }
          if (result == null)
            throw;
        }
      }
      return result;
    }
    /// 
    /// Returns a business object type based on
    /// the supplied criteria object.
    /// 
    public static Type GetObjectType(object criteria)
    {
      if (criteria.GetType().IsSubclassOf(typeof(CriteriaBase)))
      {
        // get the type of the actual business object
        // from CriteriaBase 
        return ((CriteriaBase)criteria).ObjectType;
      }
      else
      {
        // get the type of the actual business object
        // based on the nested class scheme in the book
        return criteria.GetType().DeclaringType;
      }
    }
    /// 
    /// Returns information about the specified
    /// method, even if the parameter types are
    /// generic and are located in an abstract
    /// generic base class.
    /// 
    public static MethodInfo FindMethod(Type objType, string method, Type[] types)
    {
      MethodInfo info = null;
      do
      {
        //find for a strongly typed match
        info = objType.GetMethod(method, oneLevelFlags, null, types, null);
        if (info != null)
          break;  //match found
        objType = objType.BaseType;
      } while (objType != null);
      return info;
    }
    /// 
    /// Returns information about the specified
    /// method, finding the method based purely
    /// on the method name and number of parameters.
    /// 
    public static MethodInfo FindMethod(Type objType, string method, int parameterCount)
    {
      // walk up the inheritance hierarchy looking
      // for a method with the right number of
      // parameters
      MethodInfo result = null;
      Type currentType = objType;
      do
      {
        MethodInfo info = currentType.GetMethod(method, oneLevelFlags);
        if (info != null)
        {
          if (info.GetParameters().Length == parameterCount)
          {
            // got a match so use it
            result = info;
            break;
          }
        }
        currentType = currentType.BaseType;
      } while (currentType != null);
      return result;
    }
  }
}