
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System;
using Godot;


namespace Rokojori.Reallusion
{ 
  public class CCMaterialInfo:CCImportFileBase
  {  

    public string name;


    public CCJSONProperty<string> materialType = new CCJSONProperty<string>( "Material Type" );
    public CCJSONProperty<int> multiUVIndex = new CCJSONProperty<int>( "MultiUV Index" );
    public CCJSONProperty<string> nodeType = new CCJSONProperty<string>( "Node Type" );
    public CCJSONProperty<bool> twoSide = new CCJSONProperty<bool>( "Two Side" );
    public CCJSONProperty<List<float>> diffuseColor = new CCJSONProperty<List<float>>( "Diffuse Color" );
    public CCJSONProperty<List<float>> ambientColor = new CCJSONProperty<List<float>>( "Ambient Color" );
    public CCJSONProperty<List<float>> specularColor = new CCJSONProperty<List<float>>( "Specular Color" );
    public CCJSONProperty<float> opacity = new CCJSONProperty<float>( "Opacity" );
    public CCJSONProperty<float> selfIllumination = new CCJSONProperty<float>( "Self Illumination" );
    public CCJSONProperty<float> specular = new CCJSONProperty<float>( "Specular" );
    public CCJSONProperty<float> glossiness = new CCJSONProperty<float>( "Glossiness" );

    public CCJSONProperty<List<CCTextureInfo>> textures = new CCJSONProperty<List<CCTextureInfo>>( "Textures" ); 
    
    public CCJSONProperty<CCCustomShader> customShader = new CCJSONProperty<CCCustomShader>( "Custom Shader" );

    public CCJSONProperty<CCSubsurfaceScatter> subsurfaceScatter = new CCJSONProperty<CCSubsurfaceScatter>( "Subsurface Scatter" );

    public CCMaterialInfo( CCImportFile ccImportFile, string name ):base( ccImportFile )
    {
      this.name = name;

      textures.SetReader(
        ( data ) =>
        {
          var list = new List<CCTextureInfo>();
          var obj = data.AsObject();

          Info( "Reading textures:", obj.keys );

          obj.keys.ForEach( 
            k => 
            {
              var ti = CCTextureInfo.Create( importFile, k, obj.Get( k ) );
              Info( "Adding texture for material", name, ti.name, ti.texturePath.value );
              list.Add( ti );
            }
          );


          return list;
        }
      );

      customShader.SetReader(
        ( data ) =>
        {
          var shaderName = data.AsObject().Get( "Shader Name" ).stringValue;
          var cs = new CCCustomShader( importFile, shaderName );

          Info( "Found Shader", shaderName );

          cs.ReadFrom( data.AsObject() );

          return cs;

        }
      );

      subsurfaceScatter.SetReader(
        ( data ) =>
        {
          var ss = new CCSubsurfaceScatter( importFile );

          ss.ReadFrom( data.AsObject() );

          return ss;

        }
      );
    }

    public void ReadFrom( JSONObject root )
    {      
      materialType.Read( root );
      multiUVIndex.Read( root );
      nodeType.Read( root );
      twoSide.Read( root );
      diffuseColor.Read( root );
      ambientColor.Read( root );
      specularColor.Read( root );
      opacity.Read( root );
      selfIllumination.Read( root );
      textures.Read( root );
      customShader.Read( root );
      subsurfaceScatter.Read( root );
    }

    public bool IsCustomShader( string shaderName )
    {
      return customShader.exists && customShader.value.name == shaderName;
    }

    public Material CreateMaterial( CCMaterialSettings settings )
    {
      RJLog.Log( "Custom Shader", customShader.exists ? customShader.value.name : "none" );

      if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLHair )
      {
        return CreateHairMaterial( settings );
      }
     
      if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLEyeOcclusion )
      {       
        return CreateEyeOcclusionMaterial( settings );
      }

      if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLEye )
      {       
        return CreateEyeMaterial( settings );
      }


      if ( 
        settings.materialType.shaderType == CCMaterialType.CCShaderType.RLSkin ||
        settings.materialType.shaderType == CCMaterialType.CCShaderType.RLHead 
      )
      {
        
        return CreateSkinMaterial( settings );
      }

      if ( HasOpacityTexture() )
      {
        return CreateOpacityMaterial( settings );
      }


      return CreatePBRMaterial( settings );
    }


    public Color GetAlbedoColor( float gamma )
    {
      var a = opacity.GetOr( 1.0f );
      var color = ColorX.From( diffuseColor.GetOr( new List<float>{ 255, 255, 255, 255 } ), 255 );
      color.A *= a;

      color = color.Gamma( gamma );

      return color;
    }

    public bool HasOpacityTexture()
    {
      if ( textures.exists )
      {
        var opacityTexture = textures.value.Find( t => t.name == CCTextureInfo.Opacity );
        return opacityTexture != null;
      }

      return false;
    }

    public bool IsTransparent()
    {
      return HasOpacityTexture() || GetAlbedoColor( 1.0f ).A < 1.0f;
    }
  

    public CCMaterialType GetMaterialType()
    {
      var materialType = new CCMaterialType();

      materialType.shaderType = CCMaterialType.CCShaderType.PBR;

      if ( customShader.exists )
      {
        materialType.shaderType = ReflectionHelper.StringToEnum( customShader.value.name, CCMaterialType.CCShaderType.Unknown );
      }

      return materialType;
    }


    public StandardMaterial3D CreatePBRMaterial( CCMaterialSettings settings, bool skin = false )
    {
      var material = new StandardMaterial3D();

      material.CullMode = twoSide.GetOr( false ) ? BaseMaterial3D.CullModeEnum.Disabled : BaseMaterial3D.CullModeEnum.Back;

      material.Metallic = 1.0f;
      material.AlbedoColor = GetAlbedoColor( settings.configuration.gammaForAlbedoColor );
      material.Transparency = IsTransparent() ? BaseMaterial3D.TransparencyEnum.Alpha : BaseMaterial3D.TransparencyEnum.Disabled;
      material.MetallicSpecular = skin ? 0.0f : 0.5f;


      if ( skin && subsurfaceScatter.exists)
      {
        material.SubsurfScatterEnabled = true;
        material.SubsurfScatterStrength = subsurfaceScatter.value.radius.value / 3.0f;   
        material.SubsurfScatterSkinMode = true;   
      }

      if ( ! textures.exists )
      {
        return material;
      }

      textures.value.ForEach(
        ( t ) =>
        {
          if ( t.isBaseColor )
          {
            material.AlbedoTexture = t.GetTexture();
            var uv1Scale = t.tiling.GetOr( new List<float>(){ 1, 1 } );
            var uv1Offset = t.offset.GetOr( new List<float>(){ 0, 0 } );
            material.Uv1Scale = new Vector3( uv1Scale[ 0 ], uv1Scale[ 1 ], 1 );
            material.Uv1Offset = new Vector3( uv1Offset[ 0 ], uv1Offset[ 1 ], 1 ); 
          }
          else if ( t.isNormal )
          {
            material.NormalTexture = t.GetTexture();
            material.NormalEnabled = true;
          }
          else if ( t.isRoughness )
          {
            material.RoughnessTexture = t.GetTexture();
            material.RoughnessTextureChannel = BaseMaterial3D.TextureChannel.Red;            
          }
          else if ( t.isMetallic )
          {
            material.MetallicTexture = t.GetTexture();
            material.MetallicTextureChannel = BaseMaterial3D.TextureChannel.Red;
          }
          else if ( t.isGlow )
          {
            material.EmissionEnabled = true;
            material.EmissionTexture = t.GetTexture();
          }
          else if ( t.isAO )
          {
            material.AOEnabled = true;
            material.AOTexture = t.GetTexture();
            material.AOTextureChannel = BaseMaterial3D.TextureChannel.Red;
          }
        }
      );

      return material;
    } 

    public ShaderMaterial CreateEyeOcclusionMaterial( CCMaterialSettings settings )
    {
      var eyeOcclusionMaterial = new CCEyeOcclusionMaterial();
      
      eyeOcclusionMaterial.shadowTopColor.Set( 
        ColorX.From( customShader.value.shadowTopColor.value, 255.0f )
      );

      eyeOcclusionMaterial.shadowBottomColor.Set( 
        ColorX.From( customShader.value.shadowBottomColor.value, 255.0f )
      );

      return eyeOcclusionMaterial;
    }

    public ShaderMaterial CreateEyeMaterial( CCMaterialSettings settings )
    {
      var eyeMaterial = new CCEyeMaterial();

      textures.value.ForEach(
        ( t ) =>
        {
          if ( t.isBaseColor )
          {
            eyeMaterial.textureAlbedo.Set( t.GetTexture() );

          }
        }
      );

      eyeMaterial.textureNormal.Set( customShader.value.GetIrisNormal() );
      
      return eyeMaterial;
    }

    public ShaderMaterial CreateSkinMaterial( CCMaterialSettings settings )
    { 
      var skinMaterial = new CCSkinMaterial();      
      CustomMaterial outputMaterial = skinMaterial;

      if ( settings.configuration.transmissiveSkin )
      {
        outputMaterial = new CCSkinTransmissiveMaterial();
      }

      if ( settings.configuration.applyAlbedoNoise )
      {
        var skinScale = 2.0f;

        if ( name.EndsWith( "Head" ) )
        {
          skinScale = 1.0f;
        }
        else if ( name.EndsWith( "Body" ) )
        {
          skinScale = 5.0f;
        }


        skinMaterial.albedoNoiseUvScale.AssignFor( outputMaterial, skinScale ); 
        skinMaterial.albedoNoiseOffset.AssignFor( outputMaterial, settings.configuration.albedoNoiseBrightness );
        skinMaterial.albedoNoise.AssignFor( outputMaterial, settings.configuration.albedoNoiseAmount );
        skinMaterial.textureAlbedoNoise.AssignFor( outputMaterial, settings.configuration.skinAlbedoNoise );
      }

      skinMaterial.albedo.AssignFor( outputMaterial, GetAlbedoColor(  settings.configuration.gammaForAlbedoColor ) );
      skinMaterial.specular.AssignFor( outputMaterial, 0.5f );      
      
      
      var tiling = customShader.value.GetFloatVariable( "MicroNormal Tiling", 20f );
      skinMaterial.uv1Scale.AssignFor( outputMaterial, Vector3.One );
      skinMaterial.uv2Scale.AssignFor( outputMaterial, Vector3.One * tiling );

      skinMaterial.microNormalTexture.AssignFor( outputMaterial, customShader.value.GetMicroNormal() );
      skinMaterial.microNormalMaskTexture.AssignFor( outputMaterial, customShader.value.GetMicroNormalMask() );
      skinMaterial.microNormalScale.AssignFor( outputMaterial, customShader.value.GetFloatVariable( "MicroNormal Strength", 0.5f ) );

      if ( outputMaterial is CCSkinTransmissiveMaterial st )
      {
        st.textureSubsurfaceTransmittance.Set( customShader.value.GetTransmisionMap() );
      }
      // skinMaterial.textureSubsurfaceTransmittance.Set( customShader.value.GetTransmisionMap() );
      skinMaterial.textureSpecular.AssignFor( outputMaterial, customShader.value.GetSpecularMask() );
      skinMaterial.textureSubsurfaceScattering.AssignFor( outputMaterial, customShader.value.GetSSSMap() );

      textures.value.ForEach(
        ( t ) =>
        {
          if ( t.isBaseColor )
          {
            skinMaterial.textureAlbedo.AssignFor( outputMaterial, t.GetTexture() );

            var uv1Scale = t.tiling.GetOr( new List<float>(){ 1, 1 } );
            var uv1Offset = t.offset.GetOr( new List<float>(){ 0, 0 } );

            skinMaterial.uv1Scale.AssignFor( outputMaterial, new Vector3( uv1Scale[ 0 ], uv1Scale[ 1 ], 1 ) );
            skinMaterial.uv1Offset.AssignFor( outputMaterial, new Vector3( uv1Offset[ 0 ], uv1Offset[ 1 ], 1 ) );
          }
          else if ( t.isNormal )
          {
            skinMaterial.textureNormal.AssignFor( outputMaterial, t.GetTexture() );
          }
          else if ( t.isRoughness )
          {
            skinMaterial.textureRoughness.AssignFor( outputMaterial, t.GetTexture() );
          }
          else if ( t.isAO )
          {
            skinMaterial.textureAmbientOcclusion.AssignFor( outputMaterial, t.GetTexture() );
          }
        }
      );

      return outputMaterial;

    }

    public ShaderMaterial CreateOpacityMaterial( CCMaterialSettings settings )
    {
      var opacityMaterial   = new CCPBROpacityMaterial();

      opacityMaterial.albedo.Set( GetAlbedoColor(  settings.configuration.gammaForAlbedoColor ) );
      opacityMaterial.specular.Set( 0.0f );
      opacityMaterial.opacity.Set( opacity.GetOr( 1 ) );
      opacityMaterial.uv1Scale.Set( Vector3.One );
      opacityMaterial.uv2Scale.Set( Vector3.One );

      textures.value.ForEach(
        ( t ) =>
        {
          if ( t.isBaseColor )
          {
            opacityMaterial.textureAlbedo.Set( t.GetTexture() );

            var uv1Scale = t.tiling.GetOr( new List<float>(){ 1, 1 } );
            var uv1Offset = t.offset.GetOr( new List<float>(){ 0, 0 } );

            opacityMaterial.uv1Scale.Set( new Vector3( uv1Scale[ 0 ], uv1Scale[ 1 ], 1 ) );
            opacityMaterial.uv1Offset.Set( new Vector3( uv1Offset[ 0 ], uv1Offset[ 1 ], 1 ) );
          }
          if ( t.isOpacity )
          {
            opacityMaterial.textureOpacity.Set( t.GetTexture() );
          }
          else if ( t.isNormal )
          {
            opacityMaterial.textureNormal.Set( t.GetTexture() );
          }
          else if ( t.isRoughness )
          {
            opacityMaterial.textureRoughness.Set( t.GetTexture() );
          }
          else if ( t.isMetallic )
          {
            opacityMaterial.textureMetallic.Set( t.GetTexture() );
            opacityMaterial.metallicTextureChannel.Set( new Vector4( 1, 0, 0, 0 )  );
          }
          else if ( t.isAO )
          {
            opacityMaterial.textureAmbientOcclusion.Set( t.GetTexture() );
            opacityMaterial.aoTextureChannel.Set( new Vector4( 1, 0, 0, 0 )  );
          }
        }
      );

      return opacityMaterial;
    }   

    public ShaderMaterial CreateHairMaterial( CCMaterialSettings settings )
    {
      
      
      var scissorMaterial   = new CCHairScissorMaterial();
      var alphaMaterial     = new CCHairAlphaMaterial();
      var alphaBackMaterial = new CCHairAlphaBackMaterial();
      
            
      scissorMaterial.NextPass = alphaBackMaterial;
      alphaBackMaterial.NextPass = alphaMaterial;

      alphaBackMaterial.RenderPriority = settings.materialRenderPriorityIndexBack;
      alphaMaterial.RenderPriority = settings.materialRenderPriorityIndexFront;

      var m = new List<CustomMaterial>(){ scissorMaterial, alphaMaterial, alphaBackMaterial };

      var mat = scissorMaterial;

      CustomMaterial outputMaterial = scissorMaterial;
      
      var opacityScale = 1.0f;

      if ( CCImportConfiguration.HairShaderMode.Alpha_Only == settings.configuration.hairShaderMode )
      {
        var hairMaterial = new CCHairMaterial();
        hairMaterial.RenderPriority = settings.materialRenderPriorityIndexBack; 
        outputMaterial = hairMaterial;
        m = new List<CustomMaterial>(){ hairMaterial };
        opacityScale = 1.5f;
      }
      else if ( CCImportConfiguration.HairShaderMode.Shadow_Scissor_AlphaBack_AlphaFront == settings.configuration.hairShaderMode )
      {
        scissorMaterial.NextPass = null;

        var shadowMaterial = new CCHairShadowMaterial();
        shadowMaterial.RenderPriority = -2;
        var hairMaterial = new CCHairScissorMaterial();

        outputMaterial = shadowMaterial;       
        shadowMaterial.NextPass = hairMaterial;
        hairMaterial.NextPass = alphaBackMaterial;                
        
        m = new List<CustomMaterial>(){ shadowMaterial, hairMaterial, alphaMaterial, alphaBackMaterial };
        opacityScale = 1f;
      } 

      mat.naturalColors.AssignFor( m, settings.configuration.naturalColors );
      mat.maximumHighlightAmount.AssignFor( m, settings.configuration.maximumHighlightAmount );
      
      mat.opacityGamma.AssignFor( m, settings.configuration.GetHairOpacityGamma( settings.meshName, settings.materialIndex ) );

      mat.flowMap.AssignFor( m, customShader.value.GetHairFlowMap() );
      mat.rootMap.AssignFor( m, customShader.value.GetHairRootMap() );
      mat.idMap.AssignFor( m, customShader.value.GetHairIDMap() );

      mat.anisotropyRatio.AssignFor( m, settings.configuration.anisotropicRatio );

      mat.vertexGreyToColor.AssignFor( m, customShader.value.GetColorVariable( "VertexGrayToColor", 255f, false ) );
      mat.vertexColorStrength.AssignFor( m, customShader.value.GetFloatVariable( "VertexColorStrength" ) );
      mat.diffuseStrength.AssignFor( m, customShader.value.GetFloatVariable( "Diffuse Strength" ) );
      mat.baseColorMapStrength.AssignFor( m, customShader.value.GetFloatVariable( "BaseColorMapStrength" ) );

      mat.strandRootColor.AssignFor( m, customShader.value.GetColorVariable( "RootColor", 255f, false ) );
      mat.strandRootStrength.AssignFor( m, customShader.value.GetFloatVariable( "RootColorStrength" ) );

      mat.strandEndColor.AssignFor( m, customShader.value.GetColorVariable( "TipColor", 255f, false ) );
      mat.strandEndStrength.AssignFor( m, customShader.value.GetFloatVariable( "TipColorStrength" ) );
        
      mat.highlightAColor.AssignFor( m, customShader.value.GetColorVariable( "_1st Dye Color", 255, false ) );
      mat.highlightAStrength.AssignFor( m, customShader.value.GetFloatVariable( "_1st Dye Strength" ) );
      mat.highlightARange.AssignFor( m, 
        customShader.value.GetColorVariable( "_1st Dye Distribution from Grayscale", 255, false ).ToVector3() 
      );
      mat.highlightAOverlapEnd.AssignFor( m, customShader.value.GetFloatVariable( "Mask 1st Dye by RootMap" ) );      
      mat.highlightAInvert.AssignFor( m, customShader.value.GetFloatVariable( "Invert 1st Dye RootMap Mask" ) );

      mat.highlightBColor.AssignFor( m, customShader.value.GetColorVariable( "_2nd Dye Color", 255, false ) );
      mat.highlightBStrength.AssignFor( m, customShader.value.GetFloatVariable( "_2nd Dye Strength" ) );
      mat.highlightBRange.AssignFor( m, 
        customShader.value.GetColorVariable( "_2nd Dye Distribution from Grayscale", 255, false ).ToVector3() 
      );
      mat.highlightBOverlapEnd.AssignFor( m, customShader.value.GetFloatVariable( "Mask 2nd Dye by RootMap" ) );      
      mat.highlightBInvert.AssignFor( m, customShader.value.GetFloatVariable( "Invert 2nd Dye RootMap Mask" ) );


      mat.albedo.AssignFor( m, GetAlbedoColor( settings.configuration.gammaForAlbedoColor ) );
      // mat.specular.AssignFor( m, 0.5f );
      mat.opacity.AssignFor( m, opacity.GetOr( 1 ) * opacityScale );
      mat.blendAmount.AssignFor( m, 0f );

      mat.roughnessOffset.AssignFor( m, settings.configuration.roughnessOffset );
      mat.metallicOffset.AssignFor( m, settings.configuration.metallicOffset );

      textures.value.ForEach(
        ( t ) =>
        {
          if ( t.isBaseColor )
          {            
            mat.textureAlbedo.AssignFor( m, t.GetTexture() );
          }
          if ( t.isOpacity )
          {
            mat.textureOpacity.AssignFor( m, t.GetTexture() );
          }
          else if ( t.isRoughness )
          {
            mat.textureRoughness.AssignFor( m, t.GetTexture() );
          }
          else if ( t.isMetallic )
          {
            mat.textureMetallic.AssignFor( m, t.GetTexture() );
          }
          else if ( t.isAO )
          {
            mat.textureAmbientOcclusion.AssignFor( m, t.GetTexture() );
          }
          else if ( t.isBlend )
          {
            mat.textureBlend.AssignFor( m, t.GetTexture() );
            mat.blendAmount.AssignFor( m, t.strength.GetOr( 0f ) / 100f ) ;
          }
        }
      );

      return outputMaterial;
    }   
    
  }
}