
using Godot;

using Rokojori;
using System.Collections.Generic;
using System;
using Microsoft.VisualBasic;
using Rokojori.Tools;
using System.Threading.Tasks;

namespace Rokojori.DocGenerator
{  
  public class ClassTypeEntry
  {
    public Type type;
    public string definitionType;
    public string path;
  }

  public class ShaderTypeEntry
  {
    public string path;
  }


  public class DocGenerator
  {    
    string path = "";
    string outputPath = "";
    List<string> icons = new List<string>();

    public bool checkChangeTime = false;
  
    public List<FilePath> classFiles = new List<FilePath>();
    public List<ClassTypeEntry> classTypes = new List<ClassTypeEntry>();
    public List<ShaderTypeEntry> shaderTypes = new List<ShaderTypeEntry>();

    public bool shaders = true;
    public bool cSharp = true;
    

    public async void Generate( string path, string outputPath, List<string> icons )
    {
      this.path = path;
      this.outputPath = outputPath;
      this.icons = icons;

      await GetFiles();
      CreateTypesFromFiles();
      // IncludeDefaultTypes();
      GenerateDocsFromTypes();
    }

    async Task GetFiles()
    {
      classFiles = FilesSync.GetFiles( path, 
      f => 
      { 
        var isCS = f.fileExtension == ".cs";
        var isShader = f.fileExtension == ".gdshader" || f.fileExtension == ".gdshaderinc";

        if ( isShader )
        {
          // RJLog.Log( "SHADER: FILE", f.absolutePath );
        }

        return ( cSharp && isCS ) || ( shaders && isShader );
      }, 
      true 
      );

      // if ( checkChangeTime )
      // {
      //   classFiles = await classFiles.FilterAsync(
      //     async ( f ) =>
      //     {
      //       let generated = 
      //       return await Git.IsFileNewerThan( f.absolutePath, changeTime );
      //     }
      //   );
      // }
    }

    void IncludeDefaultTypes()
    {
      var defaultTypes = new List<Type>()
      {
        typeof( NetworkNode ),
        typeof( Action ),
        typeof( Sensor ),
        typeof( Selector )
      };

      defaultTypes.ForEach(
        ( dt )=>
        {
          var entry = new ClassTypeEntry();
          entry.type = dt;
          entry.path = null;

          classTypes.Add( entry);
        }
      );


    }

    void CreateTypesFromFiles()
    {
      var genericType = new EventSlot<int>().GetType();
      var namespaceLabel = "Rokojori";
      var assembly = genericType.Assembly;

      classFiles.ForEach(
        ( cf )=>
        {
          var name = cf.fileName;

          if ( cf.fileExtension == ".cs" )
          {
            var fileContent = FilesSync.LoadUTF8( cf.fullPath );
            var definitions = CSharpLexer.GetAllObjectDefinitions( fileContent );

            // RJLog.Log( cf.fileName, ">>", definitions.Join( "," ) );

            for ( int i = 0; i < definitions.Count; i++ )
            {
              var sequence = definitions[ i ];
              var definitionType = sequence[ 0 ];
              var definitionName = sequence[ 1 ];            
            
              // RJLog.Log( "Adding definition:", definitionName.match );
              AddCSharpsClassType( cf, definitionName.match, definitionType.match, namespaceLabel, assembly );
              
            }
          }
          else if ( cf.fileExtension == ".gdshader" || cf.fileExtension == ".gdshaderinc" )
          {
            var shaderType = new ShaderTypeEntry();
            shaderType.path = cf.absolutePath;
            shaderTypes.Add( shaderType );

            // RJLog.Log( "SHADER: Adding definition:", cf.absolutePath );
          }
          

        }
      );
    }    

    void AddCSharpsClassType( FilePath cf, string name, string definitionType, string namespaceLabel, System.Reflection.Assembly assembly )
    {
      // var name = cf.fileName;
      var type = ReflectionHelper.GetTypeByName( namespaceLabel + "." + name );

      if ( type == null )
      {
        type = ReflectionHelper.GetTypeByNameFromAssembly( name, assembly );
      } 

      if ( type == null )
      {
        return;
      }

      if ( checkChangeTime )
      {

        var outputPath = ClassDocGenerator.GetOutputFilePath( this.outputPath, type );

        // RJLog.Log( "Checking change times:", outputPath, cf.fullPath );

        if ( FilesSync.FileExists( outputPath ) )
        {
          var outputModificationTime = FilesSync.GetModificationTime( outputPath );
          var classModificationTime = FilesSync.GetModificationTime( cf.fullPath );

          //  RJLog.Log( "Change times are:", outputModificationTime, classModificationTime );

          if ( outputModificationTime.IsNewerThan( classModificationTime ) )
          {
            // RJLog.Log( "File not changed:", type, cf.fullPath );
            return;
          } 
        }
        else
        {

        }
      }

      var entry = new ClassTypeEntry();
      entry.type = type;
      entry.path = cf.fullPath;
      entry.definitionType = definitionType;


      classTypes.Add( entry);
      
    }

    void GenerateDocsFromTypes()
    {
      FilesSync.EnsureDirectoryExists( outputPath );
      
      classTypes.ForEach( 
        ( c )=>
        {
          var cdg = new ClassDocGenerator();
          var cinfo = cdg.Create( c.path, c.type, c.definitionType, icons );
          var outputPath = ClassDocGenerator.GetOutputFilePath( this.outputPath, c.type );
          // var outputPathName = cinfo.name.Replace( "`", "-" ).Replace( "<", "-" ).Replace( ">", "-" );

          // var outputPath = FilePath.Join( this.outputPath, outputPathName + ".json" );
          FilesSync.SaveJSON( outputPath, cinfo );
          RJLog.Log( "Updated", c.type.Name, c.path );
        }
      );

      //  RJLog.Log( "SHADER: shaderTypes", shaderTypes.Count );

      shaderTypes.ForEach(
        ( s ) =>
        { 
          try
          {
            // RJLog.Log( "SHADER: DOC", s.path, path );
            var sdg = new ShaderDocGenerator();
            var cinfo = sdg.Create( s.path, path );
            var prefix = cinfo.definitionType + "." ;
            var outputPathName = prefix + cinfo.name;

            var outputPath = FilePath.Join( this.outputPath, outputPathName + ".json" );
            // RJLog.Log( "SHADER: SAVING", s.path, ">>", outputPathName );
            FilesSync.SaveJSON( outputPath, cinfo );
          }
          catch ( System.Exception e )
          {
            RJLog.Error( s.path );
            RJLog.Error( e );
          }
         
        } 
      );
    }
  }
}