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



namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class MeshCombiner:Node3D
  {
    [Export]
    public Node3D[] sourceNodes = [];

    [ExportToolButton( "Combine")]
    public Callable CombineButton => Callable.From( Combine );

    [ExportGroup( "Mesh")]
    [Export]
    public bool combineMeshes = true;
    
    public enum UVCombineMode
    {
      Keep,
      Adjust_To_Combined_Material
    }

    [Export]
    public UVCombineMode uVCombineMode = UVCombineMode.Adjust_To_Combined_Material;

    [Export]
    public Node3D pivot;

    [ExportGroup( "Material")]
    [Export]
    public bool combineMaterials = true; 

    public enum TextureSizeMode
    {
      KeepOriginal,
      Custom
    }

    [Export]
    public TextureSizeMode textureSizeMode = TextureSizeMode.Custom;

    [Export]
    public Vector2I customTextureSize = new Vector2I( 1024, 1024 );
    
    
    [ExportGroup( "Output")]
    [Export]
    public MeshInstance3D outputMesh;
    [Export]
    public Material[] outputMaterials = [];

    

    MultiMap<Mesh,int,MeshGeometry> _meshGeometryCache = new MultiMap<Mesh,int,MeshGeometry>();

    MeshGeometry GetMeshGeometry( MeshSurface surface )
    {
      if ( ! _meshGeometryCache.Has( surface.mesh, surface.index ) )
      {
        var mg = MeshGeometry.From( surface.mesh as ArrayMesh, null, surface.index );
        _meshGeometryCache.Set( surface.mesh, surface.index, mg );

        this.LogInfo( "Created mesh with triangles:", mg.numTriangles );
      }

      return _meshGeometryCache[ surface.mesh ][ surface.index ];
    }
    
    List<Transformable<MeshSurface>> _surfaces;
    List<Material> _materials;
    MapList<Material,Transformable<MeshSurface>> _materiaList = new MapList<Material, Transformable<MeshSurface>>() ;
    Dictionary<Material,Transform2D> _uvTransform = new Dictionary<Material, Transform2D>();


    public void Combine()
    {
      GrabSurfaces();
      GrabMaterials();
      
      CombineMaterials();
  	  CombineMeshes();
    }

    void GrabSurfaces()
    {
      _surfaces = new List<Transformable<MeshSurface>>();
      
      foreach ( var n in sourceNodes )
      {      
        MeshExtractor.ExtractSurfacesInHierarchy( n, _surfaces ); 
      }
    }

    void GrabMaterials()
    {
      _materials = new List<Material>();
      _materiaList = new MapList<Material, Transformable<MeshSurface>>();

      var set = new HashSet<Material>();

      _surfaces.ForEach(
        ( s )=>
        {
          _materiaList.Add( s.item.material, s );
          
          if ( set.Contains( s.item.material ) )
          {
            return;
          }

          set.Add( s.item.material );
          _materials.Add( s.item.material );
          
        }
      );


    }

    void CombineMaterials()
    {

    }

    void CombineMeshes()
    {
      var arrayMesh = new ArrayMesh();

      this.LogInfo( "Combining", _surfaces.Count, "meshes" );

      var index = 0;
      
      _materials.ForEach(
        ( m )=>
        {
          Transform2D? uvTransform = _uvTransform.ContainsKey( m ) ? _uvTransform[ m ] : null;

          var surfaces = _materiaList[ m ];

          this.LogInfo( "Combining for Material", m, surfaces.Count, "meshes" );

          var meshGeometry = new MeshGeometry();

          surfaces.ForEach(
            ( s )=>
            {
              var smg = GetMeshGeometry( s.item ).Clone();

              if ( uvTransform != null )
              {
                smg.ApplyUVTransform( (Transform2D) uvTransform );
              }

              var trsf = s.transform;

              if ( pivot != null )
              {
                trsf.Origin -= pivot.GlobalPosition;
              }

              smg.ApplyTransform( trsf );

              meshGeometry.Add( smg );
            }
          );

          meshGeometry.GenerateMesh( Mesh.PrimitiveType.Triangles, arrayMesh );


          arrayMesh.SurfaceSetMaterial( index, m );
          index ++;

        }
      );

      if ( outputMesh == null )
      {
        outputMesh = this.CreateChild<MeshInstance3D>();
      }

      outputMesh.Mesh = arrayMesh;
      outputMaterials = _materials.ToArray();
    } 

    
  }
}