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 List<MemberInfo> GetDataMemberInfos<T>( object instance )
    {
      var list = GetDataMemberInfos( instance, BindingFlags.Instance | BindingFlags.Public );
      list = Lists.Filter( list, m => IsType<T>( m ) );
      return list;
    }

    public static bool IsTypeOneOf<T>( T instance, params Type[] types )
    {
      if ( instance == null )
      {
        return false;
      } 

      var type = instance.GetType();

      for ( int i = 0; i < types.Length; i++ )
      {
        if ( type.IsAssignableFrom( types[ i ] ) )
        {
          return true;
        }
      }

      return false;
    }

    public static bool IsType<T>( MemberInfo mi )
    {
      Type type = null;

      if ( mi is FieldInfo fi )
      {
        type = fi.FieldType;
      }

      if ( mi is PropertyInfo pi )
      {
        type = pi.PropertyType;
      }

      return typeof( T ).IsAssignableFrom( type );
    }

    public static List<MemberInfo> GetDataMemberInfos( object instance, BindingFlags flags )
    {
      var list = new List<MemberInfo>();
      var type = instance.GetType();

      var fields = type.GetFields( flags );      

      for ( int i = 0; i < fields.Length; i++ )
      {
        list.Add( fields[ i ] );        
      }

      var properties = type.GetProperties( flags );

      for ( int i = 0; i < properties.Length; i++ )
      {
        list.Add( properties[ i ] );        
      }

      return list;
    }

    public static List<MemberInfo> GetDataMemberInfos<T>( BindingFlags flags = defaultBindings )
    {
      var list = new List<MemberInfo>();
      var type = typeof( T );

      var fields = type.GetFields( flags );      

      for ( int i = 0; i < fields.Length; i++ )
      {
        list.Add( fields[ i ] );        
      }

      var properties = type.GetProperties( flags | BindingFlags.GetProperty | BindingFlags.SetProperty );

      for ( int i = 0; i < properties.Length; i++ )
      {
        list.Add( properties[ i ] );        

        RJLog.Log( properties[ i ].Name );
      }

      return list;
    }

    public static List<T> GetDataMemberValues<T>( object instance )
    {
      var infos = GetDataMemberInfos<T>( instance );

      return Lists.Map( infos,  i => GetDataMemberValue<T>( instance, i ) );
    }

    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, MemberInfo info ) 
    {
      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 T GetDataMemberValue<T>( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) 
    {
      return GetDataMemberValue<T>( instance, GetDataMemberInfo( instance, memberName, flags ) );      
    }

    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 );
        }
      );
    }

  }
}