
using Godot;

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

namespace Rokojori.DocGenerator
{  
  public class ClassDocGenerator
  {   
    ClassDocInfo info = new ClassDocInfo();

    public string GetIcon( string classFile, string name, List<string> baseTypes, List<string> icons )
    {
      var iconMatcher = new Regex( "Icon\\(\"res://addons/rokojori_action_library/Icons/(\\w+\\.svg)" );

      var result = iconMatcher.Matches( classFile );

      if ( result != null && result.Count == 1 )
      {
        return result[ 0 ].Groups[ 1 ].Value;
      }

      if ( icons.IndexOf( name + ".svg" ) != -1 )
      {
        return name + ".svg";
      }

      foreach ( var bt in baseTypes )
      {
        if ( icons.IndexOf( bt + ".svg" ) != -1 )
        {
          return bt + ".svg";
        }
      }

      return "CSharp.svg";
    }

    public static List<string> GetExports( FieldInfo fieldInfo )
    {
      var result = new List<string>();

      var exportAttributes = fieldInfo.GetCustomAttributes( typeof(ExportAttribute), false);

      foreach ( ExportAttribute export in exportAttributes )
      {
        if ( export.Hint == PropertyHint.None)
        {
          result.Add( "Export" );
        }
        else if ( ! string.IsNullOrEmpty( export.HintString ) )
        {
          result.Add( $"Export( PropertyHint.{export.Hint}, \"{export.HintString}\" )" );
        }
        else
        {
          result.Add( $"Export( PropertyHint.{export.Hint} )" );
        }
      }

      return result;
    }

    
    public static string GetOutputFilePath( string outputPath, Type classType )
    {
      var typeName = GetDocsType.Of( classType );
      var outputPathName = typeName.Replace( "`", "-" ).Replace( "<", "-" ).Replace( ">", "-" );

      return FilePath.Join( outputPath, outputPathName + ".json" );
    }


    public ClassDocInfo Create( string filePath, Type classType, string definitionType, List<string> icons )
    {
      var data = filePath == null ? "" :  FilesSync.LoadUTF8( filePath );
      var comments = new DocComments();
      comments.Grab( data );

      var typeName = GetDocsType.Of( classType );
      var baseTypeName = classType.BaseType;
      var typeNameSpace = classType.Namespace;

      // RJLog.Log( typeName, "Comments:", comments.entries.Count );

      info.modificationTime = FilesSync.GetModificationTimeStamp( filePath );
      info.name = typeName;
      info.csNameSpace = typeNameSpace;
      info.isCSOnly = true;
      info.definitionType = definitionType;
      info.sourcePath = filePath.SubstringAfterMatching( "addons/rokojori_action_library/");

      info.doc = comments.FindDoc( "class", typeName );

      var genericArguments = classType.GetGenericArguments().ToList();
      info.generics = genericArguments.Map( t => t.Name );
      info.interfaces = classType.GetInterfaces().ToList().Map( i => GetDocsType.Of( i ) );

      var allBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
      var fields  = Lists.ToList( classType.GetFields( allBindings ) );
      fields = Lists.Filter( fields, f => ! f.IsPrivate );

      var godotTypes = new List<string>()
      {
        "Node", "Resource"
      };     

      


      var it = baseTypeName; 

      while ( it != null )
      {
        if ( godotTypes.IndexOf( it.Name) != -1 )
        {
          if ( "Node" == it.Name )
          {
            info.extendingClasses.AddRange( new string[]{ "Node", "Object" } );
          } 
          else if ( "Resource" == it.Name )
          {
            info.extendingClasses.AddRange( new string[]{ "Resource", "RefCounted", "Object" } );
          }

          it = null;
        }
        else
        {
          info.extendingClasses.Add( GetDocsType.Of( it ) );
          it = it.BaseType; 
        }

        
      }

      info.icon = GetIcon( data, info.name, info.extendingClasses, icons );

     


      fields.ForEach(
        ( f )=>
        { 
          if ( f.Name == "value__" )
          {
            return;
          }
          var memberInfo = MemberInfo.CreateField();
          
          memberInfo.name = f.Name;

          
          memberInfo.csNameSpace = f.FieldType.Namespace;
          memberInfo.dataType = GetDocsType.Of( f.FieldType );
          memberInfo.generics = Lists.Map( f.FieldType.GetGenericArguments(), t => t + "" );
          memberInfo.doc = comments.FindDoc( "field", f.Name );
          memberInfo.attributes = GetExports( f );
          
          if ( f.IsStatic )
          {
            memberInfo.modifiers.Add( "static" );
          }

          memberInfo.modifiers.Add( f.IsPublic ? "public" : "protected" );

          info.memberInfos.Add( memberInfo );
          
        }
      );


       var constructors = Lists.ToList( classType.GetConstructors( allBindings ) );
      constructors = Lists.Filter( constructors, m => ! m.IsPrivate );

      constructors.ForEach(
        ( con ) =>
        {
          var memberInfo = MemberInfo.CreateConstructor();

          memberInfo.name = typeName;
          memberInfo.dataType = typeName;

          memberInfo.csNameSpace = typeNameSpace;
          // memberInfo.generics = info.generics;
          // memberInfo.doc = comments.FindDoc( "constructor", con.Name );        

          memberInfo.modifiers.Add( con.IsPublic ? "public" : "protected" );

          var parameters = Lists.ToList( con.GetParameters() );
          parameters.ForEach(
            ( p )=>
            {
              var parameterType = new ParameterType();
              parameterType.name = p.Name;
              parameterType.type = GetDocsType.Of( p.ParameterType ); 
              parameterType.csNameSpace = p.ParameterType.Namespace; 
              parameterType.generics = Lists.Map( p.ParameterType.GetGenericArguments(), t => t + "" );

              memberInfo.parameters.Add( parameterType );

            }
          );

          info.memberInfos.Add( memberInfo );
        }


      );
      
      var methods = Lists.ToList( classType.GetMethods( allBindings ) );
      methods = Lists.Filter( methods, m => ! m.IsPrivate );

      var godotInternalMethods = new List<string>()
      {
        "GetGodotMethodList",
        "InvokeGodotClassMethod",
        "HasGodotClassMethod",
        "SetGodotClassPropertyValue",
        "GetGodotClassPropertyValue",
        "GetGodotPropertyList",
        "GetGodotPropertyDefaultValues",
        "SaveGodotObjectData",
        "RestoreGodotObjectData",
        "InvokeGodotClassStaticMethod"
      };

      methods.ForEach( 
        m => 
        {
          if ( godotInternalMethods.IndexOf( m.Name ) != -1 )
          {
            return;
          }

          var memberInfo = MemberInfo.CreateMethod();

          memberInfo.isAccessor = GetDocsType.isAccessorMethod( m );
          memberInfo.name = m.Name;
          memberInfo.dataType = GetDocsType.Of( m.ReturnType );

          memberInfo.csNameSpace = m.ReturnType.Namespace;
          memberInfo.generics = Lists.Map( m.GetGenericArguments(), t => t + "" );
          memberInfo.doc = comments.FindDoc( "method", m.Name );
          
          if ( m.IsStatic )
          {
            memberInfo.modifiers.Add( "static" );
          }

          memberInfo.modifiers.Add( m.IsPublic ? "public" : "protected" );

          var parameters = Lists.ToList( m.GetParameters() );
          parameters.ForEach(
            ( p )=>
            {
              var parameterType = new ParameterType();
              parameterType.name = p.Name;
              parameterType.type = GetDocsType.Of( p.ParameterType ); 
              parameterType.csNameSpace = p.ParameterType.Namespace; 
              parameterType.generics = Lists.Map( p.ParameterType.GetGenericArguments(), t => t + "" );

              memberInfo.parameters.Add( parameterType );

            }
          );

          info.memberInfos.Add( memberInfo );
        }
      );



      return info;
    }
  }
}