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



namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class MassRenderer:Node3D
  {
    public enum Mode
    {
      MultiMeshInstance3D,
      Combined,
      MeshInstance3D
    }

    [Export]
    public Mode mode = Mode.MultiMeshInstance3D;

    [Export]
    public int randomizeQueue = 1984;

    [ExportGroup("Mesh")]
    [Export]
    public Mesh mesh;

    [Export]
    public GeometryInstance3D.ShadowCastingSetting castShadows = GeometryInstance3D.ShadowCastingSetting.On;

    [Export]
    public Material materialOveride;

    [ExportGroup("Layout")]
    [Export]
    public Vector3[] positions = new Vector3[ 0 ];

    [Export]
    public Vector4[] rotations = new Vector4[ 0 ];

    [Export]
    public Vector3[] scales = new Vector3[ 0 ];

    [ExportGroup("Outputs")]
    [Export]
    public LODMultiMeshInstance3D[] multiMeshNodes;

    [Export]
    public float multiMeshSplitSize = 25;

    [Export]
    public float cullDistance = 100;

    [Export]
    public float cullRange = 50;

    [Export]
    public Vector3 multiMeshSplitNoiseAmount = new Vector3( 2, 0, 2 );

    [Export]
    public Vector3 multiMeshSplitNoiseFrequency = new Vector3( 10, 10, 10 );

    Dictionary<Vector3I,LODMultiMeshInstance3D> multiMeshMapping = new Dictionary<Vector3I, LODMultiMeshInstance3D>();

    [Export]
    public Node3D meshesContainer;
    [Export]
    public MeshInstance3D combinedMesh;

    List<Transform3D> _queuedTransforms = new List<Transform3D>();



    public void QueueObject( Transform3D transform )
    {
      _queuedTransforms.Add( transform );
    }

    public void AddQueued()
    {
      if ( positions == null )
      {
        positions = new Vector3[0];
      }

      if ( rotations == null )
      {
        rotations = new Vector4[0];
      }

      if ( scales == null )
      {
        scales = new Vector3[0];
      }   

      if ( randomizeQueue >= 0 )
      {
        _queuedTransforms = RandomList<Transform3D>.Randomize( _queuedTransforms, LCG.WithSeed( randomizeQueue ) );
      }

      var queuedPositions = Lists.Map( _queuedTransforms, t => t.Origin );
      var queuedRotations = Lists.Map( _queuedTransforms, t => Math3D.QuaternionToVector4( t.Basis.GetRotationQuaternion() ) );
      var queuedScales    = Lists.Map( _queuedTransforms, t => t.Basis.Scale );

      positions = Arrays.Concat( positions, queuedPositions.ToArray() );
      rotations = Arrays.Concat( rotations, queuedRotations.ToArray() );
      scales = Arrays.Concat( scales, queuedScales.ToArray() );

      _queuedTransforms.Clear();
    }

    public void Create()
    {
      AddQueued();

      if ( Mode.MultiMeshInstance3D == mode )
      {
        CreateMultiMeshes();
      }
      else if ( Mode.MeshInstance3D == mode )
      { 
        CreateMeshes();
      } 
      else if ( Mode.Combined == mode )
      {
        CreateCombined();
      }
      
    }

    public void Clear()
    {
      if ( multiMeshNodes != null )
      {
        for ( int i = 0 ; i < multiMeshNodes.Length; i++ )
        {
          multiMeshNodes[ i ] = Nodes.EnsureValid( multiMeshNodes[ i ] );

          if ( multiMeshNodes[ i ] != null )
          {
            Nodes.RemoveAndDelete( multiMeshNodes[ i ] );
          }
        }

        multiMeshNodes = [];
      }

      meshesContainer = Nodes.EnsureValid( meshesContainer );
      combinedMesh = Nodes.EnsureValid( combinedMesh );


      if ( meshesContainer != null )
      { 
        Nodes.RemoveAndDeleteChildren( meshesContainer );
      } 

      if ( combinedMesh != null )
      {
        combinedMesh.Mesh = null;
      }
    }

    void CreateMultiMeshes()
    {
      
      var instancesSorted = new MapList<Vector3I,int>();



      for ( int i = 0; i < positions.Length; i++ )
      {
        var floatIndex = ( positions[ i ] / multiMeshSplitSize );
        floatIndex += Noise.Perlin3( positions[ i ] * multiMeshSplitNoiseFrequency ) * multiMeshSplitNoiseAmount;
        var index = floatIndex.RoundToInt();

        instancesSorted.Add( index, i );
      }  

      var keys = instancesSorted.Keys.ToList();
      var numMultiMeshes = keys.Count;
      
      multiMeshNodes = new LODMultiMeshInstance3D[ numMultiMeshes ];

      for ( int i = 0; i < numMultiMeshes; i++ )
      { 
        var multiMeshNode = this.CreateChild<LODMultiMeshInstance3D>();
        var center = new Vector3( keys[ i ].X, keys[ i ].Y, keys[ i ].Z ) * multiMeshSplitSize;
        multiMeshNode.GlobalPosition = center;
        multiMeshNode.cullDistance = cullDistance;
        multiMeshNode.cullRange = cullRange;
        multiMeshNode.Multimesh = new MultiMesh();
        multiMeshNode.Multimesh.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D;

        multiMeshNodes[ i ] = multiMeshNode;

        var mm = multiMeshNode.Multimesh;
        var meshIndices = instancesSorted[ keys[ i ] ];

        mm.Mesh = mesh;
        multiMeshNode.CastShadow = castShadows;
        mm.InstanceCount = meshIndices.Count;
        
        if ( materialOveride != null )
        {
          multiMeshNode.MaterialOverride = materialOveride;
        }        

        // this.LogInfo( i, ">>", keys[ i ], ">>", meshIndices.Count );

        for ( int j = 0; j < meshIndices.Count; j++ )
        {
          var index = meshIndices[ j ];
          var trsf = Math3D.TRS( positions[ index ] - center, rotations[ index ], scales[ index ]  );
          mm.SetInstanceTransform( j, trsf );
        }   

      }
         
      
    }

    

    void CreateMeshes()
    {
      if ( meshesContainer == null )
      {
        meshesContainer = this.CreateChild<Node3D>();
      }

      for ( int i = 0; i < positions.Length; i++ )
      {
        var trsf = Math3D.TRS( positions[ i ], rotations[ i ], scales[ i ]  );

        var child = meshesContainer.CreateChild<MeshInstance3D>();
        child.Mesh = mesh;

        if ( materialOveride != null )
        {
          child.MaterialOverride = materialOveride;
        }

        child.GlobalTransform = trsf;
      }
    }

    void CreateCombined()
    {
      if ( meshesContainer == null )
      {
        combinedMesh = this.CreateChild<MeshInstance3D>();
      }

      var combinedMG = new MeshGeometry();

      var meshMG = MeshGeometry.From( mesh as ArrayMesh );

      for ( int i = 0; i < positions.Length; i++ )
      {
        var trsf = Math3D.TRS( positions[ i ], rotations[ i ], scales[ i ]  );
        combinedMG.Add( meshMG, trsf );
      }

      if ( materialOveride != null )
      {
        combinedMesh.MaterialOverride = materialOveride;
      }      

    }
    

  }
}