using Godot;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.VisualBasic;
using System.Linq;

namespace Rokojori
{ 
  [Tool]
  [GlobalClass]
  public abstract partial class ShaderGenerationModule:Resource
  { 
    public bool sortableCode = true;
    public string uniformGroup;

    public virtual bool CodeIsSortable( ShaderPhase phase )
    {
      return sortableCode;
    }

    public virtual List<string> GetVariantNames()
    {
      return new List<string>(){ "" };
    }

    public virtual List<ShaderVariant> GetVariants( ShaderGenerationContext context )
    {
      return new List<ShaderVariant>(){};
    } 

    public static ShaderVariant ToVariant( params ShaderCode[] code )
    {
      var variant = new ShaderVariant();
      variant.shaderCode = code.ToList();

      return variant;
    }

    public static List<ShaderVariant> ToVariants( params ShaderCode[] code )
    {
      return [ ToVariant( code ) ];
    }

    public string GetShaderCacheData()
    {
      var shaderHash = "";
      var name = "[ " + GetType().FullName + " ]";
      var exportedTuples = ReflectionHelper.GetExportedMembersAsJSON( this );
      exportedTuples.Sort();

      shaderHash = name + " { " + ( exportedTuples.Map( t => $"\"{t.Item1}\":{t.Item2}" ).Join( ", " ) ) + " } ";      

      
      return shaderHash.Replace( "\n", "" );      
    }

    public string GetShaderCacheHash()
    {
      var name = GetType().Name.Substring( 0, 3 ).ToLower(); 
      var exportedTuples = ReflectionHelper.GetExportedMembersAsJSON( this );
      exportedTuples.Sort();

      var allAtts = " { " + ( exportedTuples.Map( t => $"\"{t.Item1}\":{t.Item2}" ).Join( ", " ) ) + " } ";      

      
      return name + "." + allAtts.Length;      
    }


    public static List<ShaderCode> IncludeFromLibrary( string path )
    {
      return ToCode( "#include \"res://addons/rokojori_action_library/Runtime/Shading/Library/" + path + ".gdshaderinc\"\n" );
    }

    public static List<ShaderCode> IncludeTransformLibrary(){ return IncludeFromLibrary( "Transform" ); }

    public static List<ShaderCode> IncludeDepthLibrary(){ return IncludeFromLibrary( "Depth" ); }

    public static List<ShaderCode> IncludeCamerasLibrary(){ return IncludeFromLibrary( "Cameras" ); }

    public static List<ShaderCode> IncludeNoiseLibrary(){ return IncludeFromLibrary( "Noise" ); }

    public static List<ShaderCode> IncludeDitheringLibrary(){ return IncludeFromLibrary( "Dithering" ); }

    public static List<ShaderCode> IncludeSDFLibrary(){ return IncludeFromLibrary( "SDF" ); }

    public static List<ShaderCode> IncludeMathLibrary(){ return IncludeFromLibrary( "Math" ); }

    public static List<ShaderCode> IncludeLightLibrary(){ return IncludeFromLibrary( "Light" ); }

    public static List<ShaderCode> IncludeUVLibrary(){ return IncludeFromLibrary( "UV" ); }

    public static List<ShaderCode> IncludeColorsLibrary(){ return IncludeFromLibrary( "Colors" ); }

    public static List<ShaderCode> IncludeBillboardLibrary(){ return IncludeFromLibrary( "Billboard" ); }

    public static ShaderVariant ToVariant( List<ShaderCode> code )
    {
      var variant = new ShaderVariant();
      variant.shaderCode = code;

      return variant;
    }

     public static List<ShaderVariant> ToVariants( List<ShaderCode> code )
    {
      return [ ToVariant( code ) ];
    }

    public static List<ShaderVariant> ToVariants( params string[] values )
    {
      return ToVariants( ToCode( values ) );
    }

    public static List<ShaderCode> ToCode( params string[] values )
    {
      return Lists.From( (ShaderCode)new StringShaderCode( Lists.From( values ).Join( "" ) ) );
    }

    public static List<ShaderCode> ToCode( string value )
    {
      return Lists.From( (ShaderCode)new StringShaderCode( value ) );
    } 

    public static List<ShaderCode> ToUnsortableCode( string value )
    {
      var code = ToCode( value );

      code.ForEach( c => c.sortableCode = false );
        
      return code;
    } 

    public List<ShaderCode> AsUniformGroup( string name, string value )
    {
      name = name.Replace( ".", "" );

      if ( uniformGroup != null && uniformGroup != "" )
      {
        name = uniformGroup + "." + name;
      }

      var wideName = RegexUtility.UpperCaseAndWide( name );
      var blockCode = value.Indent( "      ", false );
            
      var code =
      @$"      
      // [ {wideName} ] 
      group_uniforms {name}; 

      {blockCode}
      
      group_uniforms;      
      ";

      return ToCode( code.Indent( "" ) );
    
    }

   
    public List<ShaderCode> AsUniformGroup( string name, List<ShaderCode> code )
    {
      name = name.Replace( ".", "" );
      
      if ( uniformGroup != null && uniformGroup != "" )
      {
        name = uniformGroup + "." + name;
      }

      var wideName = RegexUtility.UpperCaseAndWide( name );
            
      var codeBefore =
      @$"      
      // [ {wideName} ] 
      group_uniforms {name}; 
     
      ";

      var codeAfter =
      @$"      
     
      
      group_uniforms;      
      ";

      return Lists.FromLists( ToCode( codeBefore.Indent("") ), code, ToCode( codeAfter.Indent("") ) );
    }

    public List<ShaderCode> StartUniformGroup( string name )
    {
      var wideName = RegexUtility.UpperCaseAndWide( name );
            
      var codeBefore =
      @$"      
      // [ {wideName} ] 
      group_uniforms {name}; 
     
      ";

      return ToCode( codeBefore.Indent("") );
    }

    public List<ShaderCode> EndUniformGroup()
    {
      var codeAfter =
      @$"      
      group_uniforms;      
      ";

      return ToCode( codeAfter.Indent("") );
    }

    public static List<ShaderCode> ToInnerBlock( string name, string block )
    {
      var wideName = RegexUtility.UpperCaseAndWide( name );
      var blockCode = block.Indent( "      ", false );
      
      var code =
      
      @$"

      // [ {wideName} ]

      {blockCode}

      ";

      return ToCode( code.Indent( "  " ) );
    }

    public static List<ShaderCode> ToCode( List<string> values )
    {
      return Lists.From( (ShaderCode)new StringShaderCode( Lists.From( values ).Join( "" ) ) );
    }

    public static string UniformFor( ShaderProperty property )
    {
      if ( property is FloatProperty fp )
      {
        return Uniform( fp );
      }

      if ( property is Vector2Property v2 )
      {
        return Uniform( v2 );
      }

      return null;
    }

    public static string Uniform( FloatProperty property )
    {
      var name = property.propertyName.propertyName;
      var value = property.value._G();

      return @$"uniform float {name} = {value};";
    }

    public static string Uniform( Vector2Property property )
    {
      var name = property.propertyName.propertyName;
      var x = property.value.X._G();
      var y = property.value.Y._G();

      return @$"uniform vec2 {name} = vec2( {x}, {y});";
    }

    public static string Uniform( string name, float value )
    {
      return @$"uniform float {name} = {value._G()};";
    }

    public static string Uniform01( string name, float value )
    {
      return Uniform( name, value, 0, 1 );
    }

    public static string Uniform( string name, float value, float min, float max )
    {
      var mi = min._G();
      var ma = max._G();
      var v  = value._G();

      return @$"uniform float {name}:hint_range( {mi}, {ma}) = {v};";
    }

    public static string UniformSampler( string name, string hints )
    {
      return @$"uniform sampler2D {name}:{hints};";
    }

    public static string Uniform( string name, Color color )
    {
      var r = color.R._G();
      var g = color.G._G();
      var b = color.B._G();
      var a = color.A._G();

      return @$"uniform vec4 {name}:source_color = vec4( {r}, {g}, {b}, {a});";
    
    }

    public static string Uniform( string name, Vector3 vec )
    {
      var x = vec.X._G();
      var y = vec.Y._G();
      var z = vec.Z._G();

      return @$"uniform vec3 {name} = vec3( {x}, {y}, {z});";    
    }

    
    public static string Varying(  string type, string name )
    {
      return @$"varying {type} {name};";    
    }

    public static string VaryingFloat( string name ){ return Varying( "float", name ); }    
    public static string VaryingVec2( string name ){ return Varying( "vec2", name ); }
    public static string VaryingVec3( string name ){ return Varying( "vec3", name ); }
    public static string VaryingVec4( string name ){ return Varying( "vec4", name ); }

    public static string Uniform( string name, Vector4 vec )
    {
      var x = vec.X._G();
      var y = vec.Y._G();
      var z = vec.Z._G();
      var w = vec.W._G();

      return @$"uniform vec4 {name} = vec4( {x}, {y}, {z}, {w});";    
    }

    public static string DepthTextureUniform()
    {
      return UniformSampler( "depthTexture", "hint_depth_texture, repeat_disable, filter_nearest" );
    }

    public static string ScreenTextureUniform()
    {
      return UniformSampler( "screenTexture", "hint_screen_texture, repeat_disable, filter_linear" );
    }

    public static string NormalRoughnessTextureUniform()
    {
      return UniformSampler( "normalRoughness", "hint_normal_roughness_texture, repeat_disable, filter_linear" );
    }

  }

}