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



namespace Rokojori
{
  public class MeshExtractor
  {
    public static List<MaterialSurfaceContainer> ExtractMaterialContainersInHierarchy( Node n, List<MaterialSurfaceContainer> list = null, bool ownersOnly = false ) 
    {
      list = list == null ? new List<MaterialSurfaceContainer>() : list;

      Nodes.ForEach<Node3D>( n, 

        n => 
        {
          ExtractMaterialContainers( n, list, ownersOnly );
        }
      );

      return list;
    }
    

    public static List<MaterialSurfaceContainer> ExtractMaterialContainers( Node n, List<MaterialSurfaceContainer> list = null, bool ownersOnly = false ) 
    {
      list = list == null ? new List<MaterialSurfaceContainer>() : list;

      
      if ( n is MeshInstance3D mi && mi.Mesh != null )
      {        
        if ( ! ownersOnly )
        {
          for ( int i = 0; i < mi.Mesh.GetSurfaceCount(); i++ )
          {
            list.Add( new MeshSurfaceContainer( mi, i ) );          
          }
        }
        else
        {
          list.Add( new MeshSurfaceContainer( mi, -1 ) );          
          
        }

      }

      if ( n is MultiMeshInstance3D mmi && mmi.Multimesh != null && mmi.Multimesh.Mesh != null )
      {
        if ( ! ownersOnly )
        {
          for ( int i = 0; i < mmi.Multimesh.Mesh.GetSurfaceCount(); i++ )
          {
            list.Add( new MultiMeshSurfaceContainer( mmi, i ) );          
          }
        }
        else
        {
          list.Add( new MultiMeshSurfaceContainer( mmi, -1 ) );          
        }
      }

      return list;
    }

    public static bool CanExtract( Node3D n ) 
    {
      if ( n is MeshInstance3D mi )
      {
        return mi.Mesh != null;
      }

      if ( n is MultiMeshInstance3D mmi )
      {
        var mm = mmi.Multimesh;
        return mm.Mesh != null;
      }

      return false;
    }
    
    public static List<Transformable<SingleMaterialMesh>> ExtractMeshesInHierarchy( Node n, List<Transformable<SingleMaterialMesh>> list = null ) 
    {
      list = list == null ? new List<Transformable<SingleMaterialMesh>>() : list;

      Nodes.ForEach<Node3D>( n, 

        n => 
        {
          ExtractMeshes( n, list );
        }
      );

      return list;
    }

    public static List<Transformable<SingleMaterialMesh>> ExtractMeshes( Node n, List<Transformable<SingleMaterialMesh>> list = null ) 
    {
      list = list == null ? new List<Transformable<SingleMaterialMesh>>() : list;

      
      if ( n is MeshInstance3D mi )
      {       
        var mesh = mi.Mesh;

        var singleMaterialMesh = new SingleMaterialMesh( mesh );

        if ( mi.GetSurfaceOverrideMaterialCount() > 0 && mi.GetSurfaceOverrideMaterial( 0 ) != null )
        {
          singleMaterialMesh.material = mi.GetSurfaceOverrideMaterial( 0 );
        }
        
        if ( mi.MaterialOverride != null )
        {
          singleMaterialMesh.material = mi.MaterialOverride;
        }

        list.Add( new Transformable<SingleMaterialMesh>( singleMaterialMesh, mi.GlobalTransform ) );
      }

      if ( n is MultiMeshInstance3D mmi )
      {
        var mm = mmi.Multimesh;
        
        for ( var j = 0; j < mm.InstanceCount; j++ )
        {
          var mesh = mm.Mesh;
          var transform = mm.GetInstanceTransform( j );

          var singleMaterialMesh = new SingleMaterialMesh( mesh );

          if ( mmi.MaterialOverride != null )
          {
            singleMaterialMesh.material = mmi.MaterialOverride;
          }

          list.Add( new Transformable<SingleMaterialMesh>( singleMaterialMesh, transform ) );
        }
      }

      return list;
    }

    public static List<Transformable<MeshSurface>> ExtractSurfacesInHierarchy( Node n, List<Transformable<MeshSurface>> list = null ) 
    {
      list = list == null ? new List<Transformable<MeshSurface>>() : list;

      Nodes.ForEach<Node3D>( n, 

        n => 
        {
          ExtractSurfaces( n, list );
        }
      );

      return list;
    }

    public static List<Transformable<MeshSurface>> ExtractSurfaces( Node n, List<Transformable<MeshSurface>> list = null ) 
    {
      list = list == null ? new List<Transformable<MeshSurface>>() : list;

      if ( n is CsgShape3D c )
      {
        // RJLog.Log( "Extracting:", c, c.IsRootShape() );

        if ( ! c.IsRootShape() )
        {
          return list;
        }

        var meshesData = c.GetMeshes();
        
        // RJLog.Log( "MeshData:", meshesData );
        // RJLog.Log( "MeshData 0:",meshesData[ 0 ] );
        // RJLog.Log( "MeshData 1:", meshesData[ 1 ] );
        var trsf = (Transform3D) meshesData[ 0 ] * c.Transform;
        var mesh = (ArrayMesh) meshesData[ 1 ];

        var mg = MeshGeometry.From( mesh );

        
        Material material = null;

        if ( c is CsgBox3D bx ){ material = bx.Material; }
        if ( c is CsgCylinder3D cy ){ material = cy.Material; }
        if ( c is CsgMesh3D ms ){ material = ms.Material; }
        if ( c is CsgPolygon3D pl ){ material = pl.Material; }
        if ( c is CsgSphere3D sp ){ material = sp.Material; }
        if ( c is CsgTorus3D tr ){ material = tr.Material; }

        RJLog.Log( "MeshInfo ", c, mesh, material,  "\n", 
        "tris:",  mg.numTriangles
        ); 

        var surface = new MeshSurface( mesh, material, 0, c );
        
        list.Add( new Transformable<MeshSurface>( surface, trsf ) );
      }
      
      if ( n is MeshInstance3D mi )
      {  
        var owner = mi;   
        var mesh = mi.Mesh;
        

        for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
        {
          var surface = new MeshSurface( mesh, mesh.SurfaceGetMaterial( i ), i, owner );

          if ( i < mi.GetSurfaceOverrideMaterialCount() &&  mi.GetSurfaceOverrideMaterial( i ) != null )
          {
            surface.material = mi.GetSurfaceOverrideMaterial( i );
          }

          if ( mi.MaterialOverride != null )
          {
            surface.material = mi.MaterialOverride;
          }

          list.Add( new Transformable<MeshSurface>( surface, mi.GlobalTransform ) );
        }
      }


      if ( n is MultiMeshInstance3D mmi )
      {
        var owner = mmi;
        var mm = mmi.Multimesh;
        
        for ( var j = 0; j < mm.InstanceCount; j++ )
        {
          var mesh = mm.Mesh;
          var transform = mm.GetInstanceTransform( j );

          for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
          {
            var surface = new MeshSurface( mesh, mesh.SurfaceGetMaterial( i ), i, mmi, j );

            if ( mmi.MaterialOverride != null )
            {
              surface.material = mmi.MaterialOverride;
            }

            list.Add( new Transformable<MeshSurface>( surface, transform ) );
          }
        }
      }

      return list;
    }

  }

}