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

namespace Rokojori
{ 
  public class Materials
  { 

    public static bool HaveSameShaderType( Material a, Material b )
    {
      return a.GetType() == b.GetType();
    }

    public static int CountOf( Node node )
    { 
      var s = new StandardMaterial3D();

      if ( node is MeshInstance3D mi )
      {
        return mi.GetSurfaceOverrideMaterialCount();
      }

      return 1;
    }

    public static List<Material> ResolveNextPasses( Material material )
    {
      var list = new List<Material>();

      var it = material;

      while ( it != null )
      {
        list.Add( it );
        it = it.NextPass;
      }

      return list;
    }

    public static List<M> GetAll<M>( Node node ) where M:Material
    {
      var list = new List<M>();

      var count = CountOf( node );

      for ( int i = 0; i < count; i++ )
      {
        list.Add( Get<M>( node, i ) );
      }

      return list;
    } 
    
    public static M Get<M>( Node node, int index = 0 ) where M:Material
    {
      if ( node is MeshInstance3D mi )
      {
        var material = mi.GetSurfaceOverrideMaterial( index ) as M;

        if ( material != null )
        {
          return material;
        }

        var mesh = mi.Mesh;
        
        if ( mesh != null && mesh.SurfaceGetMaterial( 0 ) != null )
        {
          return (M) mesh.SurfaceGetMaterial( 0 );
        }
      }
      else if ( node is CsgPrimitive3D )
      {
        return ReflectionHelper.GetDataMemberValue<M>( node, "material" );
      }

      else if ( node is GpuParticles3D gp)
      {
        return gp.ProcessMaterial as M;
      }

      if ( node is GeometryInstance3D gi )
      {         
        return gi.MaterialOverride as M;
      }

      return null;

    }

    public static void SetAll( Node node, Material material )
    { 
      var count = CountOf( node );

      for ( int i = 0; i < count; i++ )
      {
        Set( node, material, i );
      }
    }

    public static void Set( Node node, List<Material> materials )
    { 
      var count = CountOf( node );

      for ( int i = 0; i < count && i < materials.Count; i++ )
      {
        Set( node, materials[ i ], i );
      }
    }

    public static void Set( Node node, Material material, int index = 0 )
    {
      if ( node is MeshInstance3D mi)
      {
        var mesh = mi.Mesh;

        mi.SetSurfaceOverrideMaterial( index, material );

        if ( index == 0 && mi.MaterialOverride != null )
        {
          mi.MaterialOverride = null;
        }

        return;
      }

      if ( node is CsgPrimitive3D csg )
      {
        ReflectionHelper.SetDataMemberValue( node, "material", material );

        if ( csg.MaterialOverride != null )
        {
          csg.MaterialOverride = null;
        }

        return;
      }

      if ( node is GpuParticles3D gp )
      {
        gp.ProcessMaterial = material;

        if ( gp.MaterialOverride != null )
        {
          gp.MaterialOverride = null;
        }
      }

      if ( node is GeometryInstance3D gi )
      {         
        gi.MaterialOverride = material;
        return;
      }

    }

    public static Material GetLastMaterial( Material material )
    {
      while ( material != null )
      {
        var nextMaterial = material.NextPass;

        if ( nextMaterial == null )
        {
          return material;
        }

        material = nextMaterial;
      }

      return null;
    }

    public static Material GetLastMaterialOverlay( Node node )
    {
      if ( node is GeometryInstance3D gi )
      { 
        return GetLastMaterial( gi.MaterialOverlay );
      }

      return null;
    }

    public static Material GetPreviousPass( Material firstMaterial, Material nextMaterial )
    {
      var material = firstMaterial;

      while ( material != null )
      {
        if ( material.NextPass == nextMaterial )
        {
          return material;
        }

        material = material.NextPass;
      }

      return null;
    }


    public static void AddOverlay( Node node, Material material, bool forceTop = true )
    {
      if ( node is GeometryInstance3D gi )
      {         
        if ( gi.MaterialOverlay == null || forceTop )
        {
          gi.MaterialOverlay = material;
          return;
        }
        
        GetLastMaterial( gi.MaterialOverlay ).NextPass = material;   

             

        return;
      }
      
    }

    public static void RemoveOverlay( Node node, Material material, bool forceTop = true )
    {
      if ( node is GeometryInstance3D gi )
      {      
        if ( gi.MaterialOverlay == material || forceTop )
        {
          gi.MaterialOverlay = null;
        }
        else 
        {
          var previous = GetPreviousPass( gi.MaterialOverlay, material );
          previous.NextPass = null;
        }

        return;
      }
    }

  }
}