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

namespace Rokojori
{
  public class Box3
	{
    public Vector3 min = Vector3.Zero;
    public Vector3 max = Vector3.Zero;

    public Vector3 center => ( max + min ) / 2f;

    public float width => max.X - min.X;
    public float height => max.Y - min.Y;
    public float depth => max.Z - min.Z;

    public static implicit operator Box3( Aabb aabb )
    {  
      return Box3.Create( aabb.Position, aabb.End );
    }

    public static implicit operator Aabb( Box3 box )
    {  
      return new Aabb( box.min, box.size ); 
    }

    public void Translate( Vector3 translation )
    {
      min += translation;
      max += translation;
    }

    public static Box3 FromPositionAndScale( Vector3 position, Vector3 scale )
    {
      var max = scale * 0.5f;
      var min = -max;

      return Box3.Create( min + position, max + position );
    } 

    public static Box3 Create( Vector3 min, Vector3 max )
    {
      var b = new Box3();
      b.min = min;
      b.max = max;

      return b;
    }

    public static Box3 WithSize( Vector3 size )
    {
      size = size.Abs();
      var max = size * 0.5f;
      var min = -max;

      return Box3.Create( min, max );
    }

    public static Box3 WithSize( float size )
    {
      return WithSize( Vector3.One * size );
    }

    public static Box3 Create<T>( List<T> data, System.Func<T,Vector3> getPosition )
    {
      var min = new Vector3( float.MaxValue, float.MaxValue, float.MaxValue );
      var max = new Vector3( -float.MaxValue, -float.MaxValue, -float.MaxValue );

      for ( int i = 0; i < data.Count; i++ )
      {
        var p = getPosition( data[ i ] );
        min = min.Min( p );
        max = max.Max( p );
      }

      return Create( min, max );
    }

    public Vector3 size => max - min;

    public void IncludePoint( Vector3 p )
    {
      min = min.Min( p );
      max = max.Max( p );
    }

    public Box2 AsXZBox2()
    {
      var b = new Box2();
      b.min = Math2D.XZ( min );
      b.max = Math2D.XZ( max );

      return b;
    }

    public Vector3 Constrain( Vector3 point )
    {
      point = min.Max( point );
      point = max.Min( point );

      return point;
    }

    public void EnsureYBounds( float minY, float maxY )
    {
      min.Y = Mathf.Min( minY, min.Y );
      max.Y = Mathf.Max( maxY, max.Y );
    }

    public float maxDistance => ( max - min ).Length();

    public static Vector3 Constrain( Vector3 point, Vector3 min, Vector3 max )
    {
      var before = point;
      point = min.Max( point );
      point = max.Min( point );

      // RJLog.Log( before, point, min, max );
      return point;
    }

    public bool Overlaps( Box3 other )
    {
      return Overlap3D.Has( this, other );
    }
  
    public float DistanceTo( Sphere a )
    {
      return Mathf.Sqrt( SquareDistanceTo( a ) );
    }

    public float SquareDistanceTo( Sphere a )
    {
      var squareDistance = 0.0f;    

      for ( int i = 0; i < 3; i++ )
      {
        var dimension = center[ i ] ;

        if ( dimension < min[ i ] )
        {
          var dimensionDistance = dimension - min[ i ];
          squareDistance += dimensionDistance * dimensionDistance;
        }
        else if ( dimension > max[ i ] )
        {
          var distance = dimension - max[ i ];
          squareDistance += distance * distance;
        }
      }

      return squareDistance;
    }

    public bool ContainsPoint( Vector3 point )
    {
      if ( ! Range.Contains( min.X, max.X, point.X ) )
      {
        return false;
      }

      if ( ! Range.Contains( min.Z, max.Z, point.Z ) )
      {
        return false;
      }

      if ( ! Range.Contains( min.Y, max.Y, point.Y ) )
      {
        return false;
      }

      return true;
    }


    public void EnsureCorrectness()
    {
      if ( max.X < min.X )
      {
        var b = max.X; max.X = min.X; min.X = b;
      }

      if ( max.Y < min.Y )
      {
        var b = max.Y; max.Y = min.Y; min.Y = b;
      }

      if ( max.Z < min.Z )
      {
        var b = max.Z; max.Z = min.Z; min.Z = b;
      }

    }

  }

}