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



namespace Rokojori
{
  public enum MaterialSlot
  {
    None,
    MeshSurface,
    MeshSurfaceOverride,
    Override,
    Overlay
  }

  public abstract class MaterialSurfaceContainer
  {
    

    protected Node3D _owner;
    protected int _surfaceIndex;
    public int surfaceIndex => _surfaceIndex;

    protected MaterialSurfaceContainer( Node3D owner, int surfaceIndex )
    {
      _owner = owner;
      _surfaceIndex = surfaceIndex;
    }

    public abstract MaterialSlot GetActiveMaterialSlot();
    public abstract T GetMaterialInSlot<T>( MaterialSlot slot ) where T:Material;
    public abstract void SetMaterialInSlot( MaterialSlot slot, Material material );
    public abstract void RemoveMaterialInSlot( MaterialSlot slot );

    public static Material GetActiveFrom( Node3D owner, int surfaceIndex = 0 )
    {
      var msc = From( owner, surfaceIndex );
      return msc.GetActiveMaterial<Material>();
    }

    public static T GetActiveFrom<T>( Node3D owner, int surfaceIndex = 0 ) where T:Material
    {
      var msc = From( owner, surfaceIndex );
      return msc.GetActiveMaterial<T>();
    }

    public static void SetMaterialInSlot( Node3D owner, int surfaceIndex, MaterialSlot slot, Material material )
    {
      var c = MaterialSurfaceContainer.From( owner, surfaceIndex );
      c.SetMaterialInSlot( slot, material );
    }

    public static T GetMaterialInSlot<T>( Node3D owner, int surfaceIndex, MaterialSlot slot ) where T:Material
    {
      var c = MaterialSurfaceContainer.From( owner, surfaceIndex );
      return c.GetMaterialInSlot<T>( slot );
    }

    public void SetActiveMaterial( Material material )
    {
      SetMaterialInSlot( GetActiveMaterialSlot(), material );
    }

    public T GetActiveMaterial<T>() where T:Material
    {
      return (T)GetMaterialInSlot<T>( GetActiveMaterialSlot() );
    }

    protected abstract void _MakeUnique( bool materials );
    
    public void MakeUnique( bool materials )
    {
      if ( ! isOwner )
      {
        return;
      }  

      _MakeUnique( materials );
    }

    public bool isOwner => _surfaceIndex == -1;


    public virtual List<MaterialSurfaceContainer> GetOwnedSurfaceContainers()
    {
      if ( ! isOwner )
      {
        return null;
      }

      var surfaces = numSurfaces;
      var type = GetType();

      var list = new List<MaterialSurfaceContainer>();

      for ( int i = 0; i < surfaces; i++ )
      {
        var surfaceContainer = ReflectionHelper.Create<MaterialSurfaceContainer>( type, _owner, i );
        list.Add( surfaceContainer );
      }

      return list;
    }

    public virtual void ForAllSurfaces( Action<MaterialSurfaceContainer> action )
    {
      var surfaces = GetOwnedSurfaceContainers();
      surfaces.ForEach( s => action( s ) );
    }

    public abstract int numSurfaces { get; } 

    public static void SetMaterialInSlot( Node3D owner, MaterialSlot slot, Material material, int surfaceIndex = 0 )
    {
      var mc = From( owner, surfaceIndex );

      if ( mc == null )
      {
        return;
      }

      mc.SetMaterialInSlot( slot, material );
    }

    public static T GetMaterialInSlot<T>( Node3D owner, MaterialSlot slot, int surfaceIndex = 0 ) where T:Material
    {
      var mc = From( owner, surfaceIndex );

      if ( mc == null )
      {
        return null;
      }

      return mc.GetMaterialInSlot<T>( slot );
    }

    

    

    public static MaterialSurfaceContainer From( Node3D owner, int surfaceIndex = -1 )
    {
      if ( owner is MeshInstance3D mesh )
      {
        return new MeshSurfaceContainer( mesh, surfaceIndex );
      }

      if ( owner is MultiMeshInstance3D multi )
      {
        return new MultiMeshSurfaceContainer( multi, surfaceIndex );
      }

      return null;
    }
  }

  public abstract class MaterialSurfaceContainer<T>:MaterialSurfaceContainer where T:Node3D
  {
    public T node => (T)_owner;
    protected MaterialSurfaceContainer( Node3D owner, int surfaceIndex ):base( owner, surfaceIndex ){}

  }

  public class MeshSurfaceContainer: MaterialSurfaceContainer<MeshInstance3D>
  {
    public MeshSurfaceContainer( MeshInstance3D owner, int surfaceIndex = -1 ):base( owner, surfaceIndex ){}

    public override int numSurfaces => node == null || node.Mesh == null ? -1 : node.Mesh.GetSurfaceCount();

    public override MaterialSlot GetActiveMaterialSlot()
    {
      if ( node.MaterialOverride != null )
      {
        return MaterialSlot.Override;
      }

      if ( node.Mesh == null || surfaceIndex == -1 )
      {
        return MaterialSlot.None;
      }

      if ( node.GetSurfaceOverrideMaterial( surfaceIndex ) != null )
      {
        return MaterialSlot.MeshSurfaceOverride;
      }

      var material = node.Mesh.SurfaceGetMaterial( surfaceIndex );

      return material == null ? MaterialSlot.None : MaterialSlot.MeshSurface;
    }

    public override void RemoveMaterialInSlot( MaterialSlot slot )
    {
      if ( surfaceIndex == -1 || MaterialSlot.None == slot || node == null )
      {
        return;
      }

      Material material = null;

      if ( MaterialSlot.MeshSurface == slot )
      {
        node.Mesh.SurfaceSetMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.MeshSurfaceOverride == slot )
      {
        node.SetSurfaceOverrideMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.Override == slot )
      {
        node.MaterialOverride = material;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        node.MaterialOverlay = material;
      }
    }

    public override void SetMaterialInSlot( MaterialSlot slot, Material material )
    {
      if ( surfaceIndex == -1 || MaterialSlot.None == slot || material == null || node == null )
      {
        return;
      }

      if ( MaterialSlot.MeshSurface == slot )
      {
        node.Mesh.SurfaceSetMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.MeshSurfaceOverride == slot )
      {
        node.SetSurfaceOverrideMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.Override == slot )
      {
        node.MaterialOverride = material;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        node.MaterialOverlay = material;
      }
    }

    public override T GetMaterialInSlot<T>( MaterialSlot slot )
    {
      if ( MaterialSlot.None == slot )
      {
        return null;
      }
      else if ( MaterialSlot.MeshSurface == slot )
      {
        return (T) node.Mesh.SurfaceGetMaterial( surfaceIndex );
      }
      else if ( MaterialSlot.MeshSurfaceOverride == slot )
      {
        return (T) node.GetSurfaceOverrideMaterial( surfaceIndex );
      }
      else if ( MaterialSlot.Override == slot )
      {
        return (T) node.MaterialOverride;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        return (T) node.MaterialOverlay;
      }

      return null;
    }

    

 

    protected override void _MakeUnique( bool materials )
    {  
      if ( node.Mesh == null )
      {
        return;
      }

      node.Mesh = (Mesh)node.Mesh.Duplicate();
      
      if ( ! materials )
      {
        return;
      }

      for ( int i = 0; i < numSurfaces; i++ )
      {
        var m = node.Mesh.SurfaceGetMaterial( i );
        node.Mesh.SurfaceSetMaterial( i, (Material)m.Duplicate() );
      }

    }
    
  }

  public class MultiMeshSurfaceContainer: MaterialSurfaceContainer<MultiMeshInstance3D>
  {
    public MultiMeshSurfaceContainer( MultiMeshInstance3D mi, int surfaceIndex = -1 ):base( mi, surfaceIndex ){}

    public override int numSurfaces => node == null || node.Multimesh == null || node.Multimesh.Mesh == null ? -1 : 
                       node.Multimesh.Mesh.GetSurfaceCount();

    public override MaterialSlot GetActiveMaterialSlot()
    {
      if ( node.MaterialOverride != null )
      {
        return MaterialSlot.Override;
      }

      if ( node.Multimesh == null || node.Multimesh.Mesh == null || surfaceIndex == -1 )
      {
        return MaterialSlot.None;
      }


      var material = node.Multimesh.Mesh.SurfaceGetMaterial( surfaceIndex );

      return material == null ? MaterialSlot.None : MaterialSlot.MeshSurface;
    }

    public override void RemoveMaterialInSlot( MaterialSlot slot )
    {
      if ( surfaceIndex == -1 || MaterialSlot.None == slot )
      {
        return;
      }

      Material material = null;

      if ( MaterialSlot.MeshSurface == slot )
      {
        node.Multimesh.Mesh.SurfaceSetMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.Override == slot )
      {
        node.MaterialOverride = material;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        node.MaterialOverlay = material;
      }
    }

    public override void SetMaterialInSlot( MaterialSlot slot, Material material )
    {
      if ( surfaceIndex == -1 || MaterialSlot.None == slot )
      {
        return;
      }

      if ( MaterialSlot.MeshSurface == slot )
      {
        node.Multimesh.Mesh.SurfaceSetMaterial( surfaceIndex, material );
      }
      else if ( MaterialSlot.Override == slot )
      {
        node.MaterialOverride = material;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        node.MaterialOverlay = material;
      }
    }

    public override T GetMaterialInSlot<T>( MaterialSlot slot )
    {
      if ( MaterialSlot.None == slot )
      {
        return null;
      }
      else if ( MaterialSlot.MeshSurface == slot )
      {
        return (T) node.Multimesh.Mesh.SurfaceGetMaterial( surfaceIndex );
      }
      else if ( MaterialSlot.Override == slot )
      {
        return (T) node.MaterialOverride;
      }
      else if ( MaterialSlot.Overlay == slot )
      {
        return (T) node.MaterialOverlay;
      }

      return null;
    }

    protected override void _MakeUnique( bool materials )
    {  
      if ( node.Multimesh == null || node.Multimesh.Mesh == null )
      {
        return;
      }

      node.Multimesh.Mesh  = (Mesh)node.Multimesh.Mesh.Duplicate();
      
      if ( ! materials )
      {
        return;
      }

      for ( int i = 0; i < numSurfaces; i++ )
      {
        var m = node.Multimesh.Mesh .SurfaceGetMaterial( i );
        node.Multimesh.Mesh.SurfaceSetMaterial( i, (Material)m.Duplicate() );
      }

    }
  }
}
