using System.Collections;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Rokojori
{
  public class ReflectionHelper
  {
    public static Type GetTypeByNameFromAssembly( string name, Assembly assembly )
    { 
      var type = assembly.GetType( name );

      if ( type != null )
      {
        return type;
      }

      var types = assembly.GetTypes();

      var genericEndingRegex = @"`\d+$";

      for ( int i = 0; i < types.Length; i++ )
      {
        var typeName = types[ i ].Name;
        var isGeneric = Regex.IsMatch( typeName, genericEndingRegex );

        if ( ! isGeneric )
        {
          continue;
        }

        var typeNameWithoutGeneric = Regex.Replace( typeName, genericEndingRegex, "" );
        
        if ( typeNameWithoutGeneric == name )
        {
          return types[ i ];
        }
      } 

      return null;

    }

    public static Type GetTypeByName( string name )
    { 
      var assemblies = new List<Assembly>();
      assemblies.AddRange( AppDomain.CurrentDomain.GetAssemblies() );
      assemblies.Reverse();

      var assembly = assemblies.Find( a =>  a.GetType( name ) != null );

      return assembly == null ? null : assembly.GetType( name );
    }

    public static bool IsList( Type type )
    {
      if ( ! ( type.IsGenericType ) )
      {
        return false;
      }

      return type.GetGenericTypeDefinition().IsAssignableFrom( typeof( List<> ) );
    }

    public static bool IsList( object objectValue )
    {
      if ( objectValue == null ) 
      { 
        return false; 
      }

      if ( ! ( objectValue is IList ) )
      {
        return false;
      }

      return IsList( objectValue.GetType() );
    }

    public static bool IsDictionary( Type type )
    {
      if ( ! ( type.IsGenericType ) )
      {
        return false;
      }

      return type.GetGenericTypeDefinition().IsAssignableFrom( typeof( Dictionary<,> ) );
    }

    public static bool IsDictionary( object objectValue )
    {
      if ( objectValue == null ) 
      { 
        return false; 
      }

      if ( ! ( objectValue is IDictionary ) )
      {
        return false;
      }

      return IsDictionary( objectValue.GetType() );
    }

    public static string GetMemberName( object obj, object member )
    {
      if ( obj == null || member == null )
      {
        return null;
      }

      var type = obj.GetType();
      var fields = type.GetFields();

      for ( int i = 0; i < fields.Length; i++ )
      {
        var value = fields[ i ].GetValue( obj );       

        if ( value == member )
        {
          return fields[ i ].Name;
        } 
      }

      return null;

    }

    public static List<FieldInfo> GetFieldInfosOfType<T>( object instance ) where T:class 
    {
      var type = instance.GetType();
      var fields = type.GetFields();
      var list = new List<FieldInfo>();

      for ( int i = 0; i < fields.Length; i++ )
      {
        var value = fields[ i ].GetValue( instance );
        var tValue = value as T;

        if ( tValue != null )
        {
          list.Add( fields[ i ] );
        } 
      }

      return list;
    }


    public static List<T> GetFieldValuesOfType<T>( object instance ) where T:class 
    {
      var type = instance.GetType();
      var fields = type.GetFields();
      var list = new List<T>();

      for ( int i = 0; i < fields.Length; i++ )
      {
        var value = fields[ i ].GetValue( instance );
        var tValue = value as T;

        if ( tValue != null )
        {
          list.Add( tValue );
        } 
      }

      return list;
    }

    public static void GetFields<C,T>( System.Action<System.Reflection.FieldInfo> action )
    {
      GetFields<T>( typeof( C ), action );
    }

    public static void GetFields<T>( System.Type c, System.Action<System.Reflection.FieldInfo> action )
    {
      var fields = c.GetFields();

      foreach( var f in fields )
      {
        if ( f.FieldType == typeof( T ) )
        {
          action( f );
        }  
      }
    }

    
    public static FieldInfo GetFieldInfo( object instance, string memberName )
    {
      var type = instance.GetType();
      var fields = type.GetFields();
      var index = -1;
      
      for ( int i = 0; i < fields.Length; i++ )
      {
        if ( fields[ i ].Name == memberName )
        {
          index = i;
        }
        
      }
      
      return index == -1 ? null : fields[ index ];
    } 

    /*public static bool HasMember( object instance, string memberName )
    {
      return GetFieldInfo( instance, memberName ) != null;
    }*/

    public static MemberInfo GetDataMemberInfo( object instance, string memberName, BindingFlags flags = BindingFlags.Public | BindingFlags.Instance )
    {
      var type = instance.GetType();
      var fieldInfo = type.GetField( memberName, flags );
      
      if ( fieldInfo != null )
      {
        return fieldInfo;
      }
      
      return type.GetProperty( memberName, flags );
    }

    public const BindingFlags defaultBindings =  BindingFlags.Public | 
                                                 BindingFlags.NonPublic | 
                                                 BindingFlags.Instance | 
                                                 BindingFlags.Static;

    public static bool HasDataMember( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings )
    {
      return GetDataMemberInfo( instance, memberName, flags ) != null;
    }    

    public static T GetDataMemberValue<T>( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) 
    {
      var info = GetDataMemberInfo( instance, memberName, flags );

      if ( info == null )
      {
        return default(T);
      }

      if ( info is FieldInfo fieldInfo )
      {
        return (T) fieldInfo.GetValue( instance );
      }

      if ( info is PropertyInfo propertyInfo )
      {
        return (T) propertyInfo.GetValue( instance );
      }

      return default(T);
      
    }

    public static void SetDataMemberValue( object instance, string memberName, object value, BindingFlags flags = ReflectionHelper.defaultBindings )
    {
      var info = GetDataMemberInfo( instance, memberName, flags );

      if ( info == null )
      {
        return;
      }

      if ( info is FieldInfo fieldInfo )
      {
        fieldInfo.SetValue( instance, value );
      }

      if ( info is PropertyInfo propertyInfo )
      {
        propertyInfo.SetValue( instance, value );
      }
    }

    public static void CopyDataMembersFromTo( object source, object target, List<string> dataMembers )
    {
      dataMembers.ForEach(
        dm =>
        {
          var value = GetDataMemberValue<object>( source, dm );
          SetDataMemberValue( target, dm, value );
        }
      );
    }

  }
}