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



namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class SetBakingMaterials:Action
  {  
    [Export]
    public Node targetContainer;

    [Export]
    public BakingMaterialMode mode;

    [Export]
    public float distance;

    [Export]
    public float radius;

    MapList<VisualInstance3D,Material> _originalMaterials;

    protected override void _OnTrigger()
    {
      SetTarget( targetContainer );
      ApplyBakingMaterials( distance, radius );      
    }
    
    public void SetTarget( Node targetContainer )
    {
      _originalMaterials = new MapList<VisualInstance3D, Material>();

      this.targetContainer = targetContainer;

      Nodes.ForEach<VisualInstance3D>( targetContainer,
        ( v )=>
        {
          _originalMaterials[ v ] = Materials.GetAll<Material>( v );
        }
      );
    }

    public void ApplyBakingMaterials( float distance, float radius )
    {
      Nodes.ForEach<VisualInstance3D>( targetContainer,
        ( v )=>
        {
          SetMaterial( v, distance, radius );
        }
      );
    }


    FloatPropertyName centerDistance = FloatPropertyName.Create( "centerDistance");
    FloatPropertyName rangeDistance  = FloatPropertyName.Create( "rangeDistance");

    void SetMaterial( VisualInstance3D v, float distance, float radius )
    {
      var materials = _originalMaterials[ v ];

      if ( BakingMaterialMode.Preview != mode )
      {
        materials = CreateBakingMaterials( materials );
      }

      if ( BakingMaterialMode.Depth == mode )
      {

        materials.ForEach( 
          m => 
          {
            centerDistance.Set( m, distance );
            rangeDistance.Set( m, radius );
          }
        );
      }

      Materials.Set( v, materials );
    }

    List<Material> CreateBakingMaterials( List<Material> materials )
    {
      return Lists.Map( materials, m => CreateBakingMaterial( m ) );
    }

    Material CreateBakingMaterial( Material originalMaterial )
    {
      var bakingMaterial = LoadBakingMaterialForMode().Duplicate( true ) as Material;

      var materialTransfer = GetBakingMaterialTransfer();

      this.LogInfo( mode, materialTransfer, originalMaterial, bakingMaterial );
      
      materialTransfer.Transfer( originalMaterial, bakingMaterial );

      return bakingMaterial;
    }

    static readonly string transferPaths = "res://addons/rokojori_action_library/Runtime/Procedural/Baking/BakingMaterials/";
    
    MaterialTransfer GetBakingMaterialTransfer()
    {
      switch ( mode )
      {
        case BakingMaterialMode.Albedo:  return Load<MaterialTransfer>( transferPaths + "Albedo/Albedo Transfer.tres" );
        case BakingMaterialMode.Normals: return Load<MaterialTransfer>( transferPaths + "Normal/Normal Transfer.tres" );
        case BakingMaterialMode.ORM:     return Load<MaterialTransfer>( transferPaths + "ORM/ORM Transfer.tres" );
        case BakingMaterialMode.Depth:     return Load<MaterialTransfer>( transferPaths + "Depth/Depth Transfer.tres" );
      }

      return null;
    }

    public static readonly string materialsPath = "res://addons/rokojori_action_library/External/Imposter/materials/";
    public static readonly string depthMapPath  = "res://addons/rokojori_action_library/Runtime/Shading/Shaders/Baking/DepthMap.material";
   

    public Material LoadBakingMaterialForMode()
    {
      switch ( mode )
      {
        case BakingMaterialMode.Albedo:  return Load<Material>( materialsPath + "albedo_material.material" );
        case BakingMaterialMode.Normals: return Load<Material>( materialsPath + "normal_baker.material" );
        case BakingMaterialMode.ORM:     return Load<Material>( materialsPath + "orm_baker.material" );
        case BakingMaterialMode.Depth:   return Load<Material>( depthMapPath );
      }

      return null;
    }

    
    static Dictionary<string,Resource> _cachedResources = new Dictionary<string, Resource>();

    static R Load<R>( string resourcePath ) where R:Resource
    {
      RJLog.Log( "Loading", resourcePath );

      if ( ! _cachedResources.ContainsKey( resourcePath ) )
      {
        
        _cachedResources[ resourcePath ] = ResourceLoader.Load( resourcePath );

        RJLog.Log( "Loaded", resourcePath, _cachedResources[ resourcePath ] );

        if ( _cachedResources[ resourcePath ] == null )
        {
          RJLog.Error( "Resource not found:", resourcePath );
        }
      }      
      
      return _cachedResources[ resourcePath ] as R;
    }

    

  }
}