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


namespace Rokojori
{  
  public class Grid2D<T>
  { 
    float _gridX;
    float _gridY;
    
    MapList<Vector2I,T> _cells = new MapList<Vector2I, T>();
    Dictionary<T,Vector2I> _objectsInCell = new Dictionary<T, Vector2I>();

    Func<T,Vector2> _getPosition;
    
    public Grid2D( float sizeX, float sizeY, Func<T,Vector2> getPosition )
    {
      _gridX = sizeX;
      _gridY = sizeY;
      _getPosition = getPosition;
    } 

    public static Grid2D<N> XZfromNode3D<N>( float x, float z ) where N:Node3D
    {
      return new Grid2D<N>( x, z, t => Math2D.XZ( t.GlobalPosition ) );
    }

    public float WorldToGridX( float x )
    {
      return x / _gridX;
    }

    public float WorldToGridY( float y )
    {
      return  y / _gridY;
    }

    public int WorldToGridIDX( float x )
    {
      return Mathf.FloorToInt( WorldToGridX( x ) );
    }

    public int WorldToGridIDY( float y )
    {
     return Mathf.FloorToInt( WorldToGridY( y ) );
    }

    public Vector2I WorldToGridID( Vector2 worldPosition )
    {
      var idX = WorldToGridIDX( worldPosition.X );
      var idY = WorldToGridIDY( worldPosition.Y );
      return new Vector2I( idX, idY ); 
    }

    public Vector2 GridIDtoWorld( Vector2I id )
    {
      return new Vector2( id.X * _gridX,  id.Y * _gridY );
    }

    public bool Has( T obj )
    {
      return _objectsInCell.ContainsKey( obj );
    }

  
    public Vector2I? GetRegistratedCell( T t )
    {
      if ( ! _objectsInCell.ContainsKey( t ) )
      {
        return null;
      }

      return _objectsInCell[ t ];
    }

    public void Remove( T obj )
    {
      if ( ! Has( obj ) )
      {
        return;
      }

      var cell = _objectsInCell[ obj ];
      _objectsInCell.Remove( obj );
      _cells.Remove( cell, obj );
    }

    public void UpdateAfterPositionChange( T obj )
    {
      if ( ! Has( obj ) )
      {
        Add( obj );
        return;
      }

      var position = _getPosition( obj );
      var id = WorldToGridID( position );

      var currentID = _objectsInCell[ obj ];

      if ( currentID == id )
      {
        return;
      }

      _objectsInCell[ obj ] = id;
      _cells.Remove( currentID, obj );
      _cells.Add( id, obj );

    }

    public void AddAll( List<T> objects )
    {
      objects.ForEach( o => Add( o ) );
    }

    public void Add( T obj )
    {
      Remove( obj );

      var id = WorldToGridID( _getPosition( obj ) );

      _objectsInCell[ obj ] = id;
      _cells.Add( id, obj );
    }

    public List<T> GetAllAtID( Vector2I id )
    {
      if ( ! _cells.ContainsKey( id ) )
      {
        return _cells[ id ];
      }

      return new List<T>();
    }

    public List<T> GetAtWorldPosition( Vector2 worldPosition )
    {
      return GetAllAtID( WorldToGridID( worldPosition ) );
    }

    public void ForEachCell( Action<Vector2I,List<T>> action )
    {
      foreach ( var id_objects in _cells )
      {
        action( id_objects.Key, id_objects.Value );
      }
    }

    public List<T> GetInBox( Box2 box, List<T> outputList = null )
    {      
      var start = box.min;
      var end   = box.max;

      var minID = WorldToGridID( start );
      var maxID = WorldToGridID( end ) + Vector2I.One;

      outputList = outputList == null ? new List<T>() : outputList;

      for ( int i = minID.X; i < maxID.X; i++ )
      {
        for ( int j = minID.Y; j < maxID.Y; j++ )
        {
          var id = new Vector2I( i, j );
          
          if ( ! _cells.ContainsKey( id ) )
          {
            continue;
          }

          outputList.AddRange( _cells[ id ] );
        }
      }

      return outputList;

    }

     public void Iterate( Box2 box, Action<T> callback )
    {      
      var start = box.min;
      var end   = box.max;

      var minID = WorldToGridID( start );
      var maxID = WorldToGridID( end ) + Vector2I.One;

      for ( int i = minID.X; i < maxID.X; i++ )
      {
        for ( int j = minID.Y; j < maxID.Y; j++ )
        {
          var id = new Vector2I( i, j );
          
          if ( ! _cells.ContainsKey( id ) )
          {
            continue;
          }

          var entries = _cells[ id ];

          entries.ForEach(
            ( e )=>
            {
              var p = _getPosition( e );

              if ( ! box.ContainsPoint( p ) )
              {
                return;
              }

              callback( e );
            }
          );
        }
      }

    }

  }
}