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

namespace Rokojori
{ 
  [Tool]
  [GlobalClass]
  public abstract partial class TextureModule:ShaderGenerationModule
  { 
    public enum TextureChannelType
    {
      RGBA,
      RGB,
      ONE,
      SCALAR
    }

    public enum TextureChannelSource
    {
      R,
      G,
      B,
      A
    }

    public TextureChannelType _type;
    public TextureChannelSource _channelSource;

    public bool is1D => _type == TextureChannelType.ONE;

    public string _domainName;
    public string _domainValueName;
    public string _domainScaleName;
    public string _domainOffsetName;
    public string _domainIntensityName;
    public string _target;
    public string _scaleTarget;

    public enum IncludeMode
    {
      Inline,
      Optional_via_Bool,
      Condtional_Define,
      Variant
    }

    public IncludeMode _includeMode = IncludeMode.Inline;

    public string _uvChannel;

    public bool _srgb = false;
    public bool _normal = false;
    public bool _repeat = true;

    public enum DomainMode
    {
      Value,
      Texture,
      Texture_Scale,
      Texture_Scale_Offset,
      Texture_Scale_Intensity
    }

    public DomainMode _domainMode = DomainMode.Texture_Scale_Offset;
    public float domainOffsetDefault = 0;
    public float domainOffsetMin = -1;
    public float domainOffsetMax = 1;

    public float domainIntensityDefault = 1;
    public float domainIntensityMin = 0;
    public float domainIntensityMax = 20;

    public float domainValueDefault = 0.5f;
    public float domainValueMin = 0;
    public float domainValueMax = 1;

    public float domainScaleDefault = 1.0f;
    public float domainScaleMin = 0;
    public float domainScaleMax = 1;

    public bool _clamp = false;

    public enum AssignmentType
    {
      Set,
      Add,
      Subtract,
      Multiply,
      Divide
    }

    public AssignmentType _assignmentType = AssignmentType.Set;

    public void SetDomainNames( string name,  bool scale = true, bool value=true, bool offset = true, bool intensity = true )
    {
      _domainName = name;

      if ( scale )
      {
        _domainScaleName = name;
      }

      if ( value )
      {
        _domainValueName = name;
      }

      if ( offset )
      {
        _domainOffsetName = name;
      }

      if ( intensity )
      {
        _domainIntensityName = name + "Intensity";
      }
    }

    public string assignmentOperator 
    {
      get 
      {
        
        if ( AssignmentType.Add == _assignmentType )
        {
          return " += ";
        }

        if ( AssignmentType.Subtract == _assignmentType )
        {
          return " -= ";
        }

        if ( AssignmentType.Multiply == _assignmentType )
        {
          return " *= ";
        }

        if ( AssignmentType.Divide == _assignmentType )
        {
          return " /= ";
        }
       
        return " = ";
        
      }
    }

    public TextureChannelType scaleType = TextureChannelType.SCALAR;

    public Color scaleColor = Colors.White;

    public enum TextureDefault
    {
      White, 
      Black,
      None
    }

    public TextureDefault _textureDefault = TextureDefault.White; 

    public TextureFilter _textureFilter = TextureFilter.Linear_MipMap_Anisotropic;

    public enum TextureFilter
    {
      Nearest,
      Nearest_MipMap,
      Nearest_MipMap_Anisotropic,
      Linear,
      Linear_MipMap,
      Linear_MipMap_Anisotropic
    }

    public virtual void GrabValues()
    {

    }

    public virtual string AddVariables()
    {
      return "";
    }

    public string GetSampledName()
    {
      return "sampled".ExtendVariableName( _domainName );

    }

    public static List<string> GetTextureHints( bool srgb, bool normal, bool repeat, TextureFilter filter, TextureDefault textureDefault )
    {
      var hints = new List<string>();

      if ( srgb )
      {
        hints.Add( "source_color" );
      }
      
      if ( normal )
      {
        hints.Add( "hint_normal" );
      }
      else  if ( TextureDefault.None != textureDefault )
      {
        hints.Add( TextureDefault.Black == textureDefault ? "hint_default_black" : "hint_default_white" );
      }

      
      hints.Add( repeat ? "repeat_enable" : "repeat_disable" );
      hints.Add( "filter_" + ( filter + "" ).ToLower() );

      return hints;
    }

    public override List<ShaderVariant> GetVariants( ShaderGenerationContext context )
    {
      GrabValues();

      if ( _clamp && ShaderPhase.Includes == context.phase )
      {
        return ToVariants( IncludeMathLibrary() );
      }

      if ( ShaderPhase.Variables == context.phase )
      {
        var textureName = _domainName;

        var valuesDefinition = "";
        var samplerDefinition = "";
        

        if ( DomainMode.Value != _domainMode )
        {
          var hints = GetTextureHints( _srgb, _normal, _repeat, _textureFilter, _textureDefault );

          // if ( _srgb )
          // {
          //   hints.Add( "source_color" );
          // }
          
          // if ( _normal )
          // {
          //   hints.Add( "hint_normal" );
          // }
          // else
          // {
          //   hints.Add( TextureDefault.Black == _textureDefault ? "hint_default_black" : "hint_default_white" );
          // }

          
          // hints.Add( _repeat ? "repeat_enable" : "repeat_disable" );
          // hints.Add( "filter_" + ( _textureFilter + "" ).ToLower() );
          
          var textureSamplerName = _domainName + "Texture";

          samplerDefinition = UniformSampler( textureSamplerName, hints.Join() );

          if ( _domainMode != DomainMode.Texture )
          {
            if ( is1D )
            {
              valuesDefinition += Uniform( _domainScaleName, domainScaleDefault, domainScaleMin, domainScaleMax );
            }
            else
            {
              if ( TextureChannelType.SCALAR == scaleType )
              {
                valuesDefinition += Uniform( _domainScaleName, domainScaleDefault, domainScaleMin, domainScaleMax );
              }
              else
              {
                valuesDefinition += Uniform( _domainScaleName, scaleColor );
              }
              
            }

            valuesDefinition +="\n";
          }
        }        
        
        if ( DomainMode.Value == _domainMode )
        {
          valuesDefinition += Uniform( _domainValueName, domainValueDefault, domainValueMin, domainValueMax );
        } 
        else if ( DomainMode.Texture_Scale_Offset == _domainMode )
        {
          valuesDefinition += "\n";
          valuesDefinition += Uniform( _domainOffsetName, domainOffsetDefault, domainOffsetMin, domainOffsetMax );
          valuesDefinition += "\n";
        }
        else if ( DomainMode.Texture_Scale_Intensity == _domainMode )
        {
          valuesDefinition += "\n";
          valuesDefinition += Uniform( _domainIntensityName, domainIntensityDefault, domainIntensityMin, domainIntensityMax );
          valuesDefinition += "\n";
        }

        var code = valuesDefinition + samplerDefinition;

        code += AddVariables();

        return ToVariants( AsUniformGroup( textureName, code ) );

      }

      if ( ShaderPhase.Fragment == context.phase )
      {
        if ( DomainMode.Texture == _domainMode || 
             DomainMode.Texture_Scale == _domainMode || 
             DomainMode.Texture_Scale_Offset == _domainMode ||
             DomainMode.Texture_Scale_Intensity == _domainMode
        )
        {
          var textureSamplerName = _domainName + "Texture";
          var code = new List<string>();
          var sampledName = GetSampledName();
          var sampled = "vec4 " + sampledName + " = texture( " + textureSamplerName + ", " + _uvChannel + " );";
          code.Add( sampled );

          var member = "";

          if ( TextureChannelType.RGB == _type )
          {
            member = ".rgb";
          }
          else if ( TextureChannelType.ONE== _type )
          {
            member = TextureChannelSource.R == _channelSource ? ".r" : 
                     TextureChannelSource.G == _channelSource ? ".g" : 
                     TextureChannelSource.B == _channelSource ? ".b" :  
                     ".a";
          }

          var scaleOffset = "";

          var scaleMember = "";

          if ( TextureChannelType.RGB == scaleType )
          {
            scaleMember = ".rgb";
          }
          else if ( TextureChannelType.ONE == scaleType )
          {
            scaleMember = TextureChannelSource.R == _channelSource ? ".r" : 
                     TextureChannelSource.G == _channelSource ? ".g" : 
                     TextureChannelSource.B == _channelSource ? ".b" :  
                     ".a";
          }

          // RJLog.Log( GetType().Name, "SCALE MEMBER", scaleMember, scaleType );

          if ( DomainMode.Texture_Scale == _domainMode || 
              DomainMode.Texture_Scale_Offset == _domainMode ||
              DomainMode.Texture_Scale_Intensity == _domainMode 
          )
          {
            scaleOffset += " * " + _domainScaleName + scaleMember;

            if ( DomainMode.Texture_Scale_Offset == _domainMode )
            {
              scaleOffset += " + " + _domainOffsetName + scaleMember;
            }
            else if ( DomainMode.Texture_Scale_Intensity == _domainMode )
            {
              scaleOffset += " * " + _domainIntensityName;
            }
          }          
          

          if ( DomainMode.Texture_Scale == _domainMode && _scaleTarget != "" && _scaleTarget != null )
          {
            code.Add( _target + assignmentOperator + sampledName + member + ";" );
            code.Add( _scaleTarget + assignmentOperator + _domainScaleName + ";" );
          }
          else
          {
            code.Add( _target + assignmentOperator + sampledName + member + scaleOffset + ";" );
          }

          if ( _clamp )
          {
            code.Add( $"{_target} = clamp01( {_target} ); ");
          }
          

          return ToVariants( ToCode( code.Join( "\n" ).Indent( "  " ).LineBreaks() ) );
        }
        else
        {
          
        }
      }

      return null;
    }
  }

}