using Godot;
using System.Reflection;
using System.Collections.Generic;

namespace Rokojori
{ 
  [Tool]
  [GlobalClass]
  public partial class CSShaderClassGenerator:Node
  { 
    [Export]
    public Shader[] shaders;

    [Export]
    public bool generate;

    public override void _Process( double delta )
    {
      if ( generate )
      {
        generate = false;
        Generate();
      }
    }

    public static readonly string ShaderName = "${ShaderName}";
    public static readonly string ShaderResourcePath = "${ShaderResourcePath}";
    public static readonly string ShaderStaticPropertyNames = "${ShaderStaticPropertyNames}";
    public static readonly string ShaderInstancePropertiesDeclarations = "${ShaderInstancePropertiesDeclarations}";
    public static readonly string ShaderInstancePropertiesInitializers = "${ShaderInstancePropertiesInitializers}";
    public static readonly string MaterialPresets = "${MaterialPresets}";

    public void Generate()
    {
      Arrays.ForEach( shaders, s => Generate( s ) );
    }

    public void Generate( Shader shaderFile )
    {
      var resourcePath = shaderFile.ResourcePath;
      var shader = ResourceLoader.Load<Shader>( resourcePath );
      var resourceParentPath = RegexUtility.ParentPath( resourcePath );
      var absoluteFilePath = FilePath.Absolute( ProjectSettings.GlobalizePath( resourcePath ) );
      var absoluteParentPath = absoluteFilePath.CreateAbsoluteParent();

      RJLog.Log( resourceParentPath );

      var shaderName = absoluteFilePath.fileName;

      var members = Shaders.GetUniformMembers( shader );

      var templatePath = ProjectSettings.GlobalizePath( "res://addons/rokojori_action_library/Runtime/Shading/Tools/CSShaderClassGenerator/CSShaderClassTemplate.txt" );

      var template = FilesSync.LoadUTF8( templatePath );



      var staticProps = new List<string>();
      var instanceProps = new List<string>();
      var instanceInitializers = new List<string>();

      members.ForEach(
        ( m )=>
        {
          var type = m.GetPropertyNameType();
          var name = m.name;
          var csName = name.ToCamelCase();

          var declaration = "public static readonly ${type}PropertyName ${csName} = ${type}PropertyName.Create( \"${name}\" );";
          var instance = "public readonly CustomMaterialProperty<${csType}> ${csName};";
          var init = "${csName} = new CustomMaterialProperty<${csType}>( this, ${ShaderName}Shader.${csName} );";

          var csType = type;

          if ( type == "Float" )
          {
            csType = "float";
          }
          else if ( type == "Int" )
          {
            csType = "int";
          }
          else if ( type == "Bool" )
          {
            csType = "bool";
          }

          var map = new StringMap();
          map[ "${type}" ] = type;
          map[ "${csType}"] = csType;
          map[ "${name}" ] = name;
          map[ "${csName}" ] = csName;
          map[ "${ShaderName}"] = shaderName;

          staticProps.Add( map.ReplaceAll( declaration ) );
          instanceProps.Add( map.ReplaceAll( instance ) );
          instanceInitializers.Add( map.ReplaceAll( init ) );
        }
      );

      var materials = FilesSync.GetFiles( absoluteParentPath.fullPath, 
        ( f ) =>
        {
          return f.HasFileExtension( "material" );
        }
      ); 

      var presets = new List<string>();

      materials.ForEach( m => 
        {
          var materialName = m.fileName;
          var shortName = materialName;

          if ( materialName.ToLower().StartsWith( shaderName.ToLower() ) )
          {
            shortName = shortName.Substring( shaderName.Length );
          }

          var declaration = 
          "public static readonly CachedResource<${ShaderName}Material> ${csName} = CustomMaterial.Cached<${ShaderName}Material>(\n" +
          "      \"${materialPath}\"\n" +
          "    );";


          var mMap = new StringMap();
          mMap[ "${materialPath}" ] = resourceParentPath + "/" + materialName + ".material";
          mMap[ "${ShaderName}" ]   = shaderName;

          var csName = RegexUtility.ToValidCSName( shortName ).ToPascalCase();

          if ( System.Char.IsDigit( csName[ 0 ] ) )
          {
            csName = "_" + csName;
          }

          mMap[ "${csName}" ]   = csName;

          

          presets.Add( mMap.ReplaceAll( declaration ) );
        }
      );


      var variables = new StringMap();
      variables[ CSShaderClassGenerator.ShaderName ] = shaderName;
      variables[ CSShaderClassGenerator.ShaderResourcePath ] = resourcePath;

      variables[ CSShaderClassGenerator.ShaderStaticPropertyNames ] = Lists.Join( staticProps, "\n    " );
      variables[ CSShaderClassGenerator.ShaderInstancePropertiesDeclarations ] = Lists.Join( instanceProps, "\n    " );
      variables[ CSShaderClassGenerator.ShaderInstancePropertiesInitializers ] = Lists.Join( instanceInitializers, "\n      " );

      variables[ CSShaderClassGenerator.MaterialPresets ] = Lists.Join( presets, "\n      " );

      var shaderClass = variables.ReplaceAll( template );

      var outputPath = absoluteFilePath.AddToFileName( "Material" ).WithExtension( "cs" );
      
  

      FilesSync.SaveUTF8( outputPath.fullPath, shaderClass );

      this.LogInfo( "\nGenerated >> ", outputPath.fullPath, "\n" + shaderClass );


    }
  }
}