using Godot;

namespace Rokojori
{
  public class Plane3
  {
    public Vector3 normal = Vector3.Up; 
    public float constant = 0;
    

    public Plane3(){}

    public static Plane3 CreateFromNode3D( Node3D n )
    {
      var p = new Plane3();
      p.SetFromNormalAndCoplanarPoint( n.GlobalUp(), n.GlobalPosition );
      
      return p;

    } 

    public void Set( Vector3 normal, float constant = 0 )
    {
      this.normal = normal;
      this.constant = constant;
    }

    public Vector3 ConstrainToPlane( Vector3 p, float distance = 10000 )
    { 
      var line3 = new Line3();
      line3.Set( p - normal * distance, p + normal * distance );

      var intersection = IntersectLine( line3 );

      if ( intersection == null )
      {
        return p; 
      }

      return (Vector3) intersection;
    }

    public Vector3 ClosestPointTo( Vector3 p )
    {
      return ConstrainToPlane( p );
    }

    public float DistanceTo( Vector3 p, float normalIntersectionDistance = 5000 )
    {
      var line = Line3.Create( p - normal * normalIntersectionDistance, p + normal * normalIntersectionDistance );
      var isc  = IntersectLine( line );

      if ( isc == null )
      {
        return float.PositiveInfinity;
      }   

      var xp = (Vector3)isc;

      return ( p - xp ).Length();
    }

    public Ray3 GetIntersectionRay( Plane3 other )
    {
      var lineDirection = normal.Cross( other.normal );

      var det = lineDirection.LengthSquared();

      if ( det == 0 )
      {
        return null;
      }

      var r = new Ray3();
      r.offset = ( ( lineDirection.Cross( other.normal ) * constant ) +
                 ( normal.Cross( lineDirection ) * other.constant ) ) / det;

      r.direction = lineDirection;

      return r;
    }

    public Vector3? IntersectLine( Line3 line )
    {
      var direction = line.direction;

  		var denominator = Math3D.Dot( normal, direction );

  		if ( denominator == 0 ) 
      {
        return null;
  		}

  		var t = - ( Math3D.Dot( line.start, normal ) + constant ) / denominator;

  		if ( t < 0 || t > 1 ) 
      {
  			return null;
  		}

  		return direction * t + line.start;
    }

    public static Plane3 GetZeroUp()
    {
      var plane = new Plane3();
      plane.Set( Vector3.Up, 0 );
      return plane;
    }

    public void SetFromNormalAndCoplanarPoint( Vector3 normal, Vector3 point ) 
    {
      this.normal = normal;
      this.constant = - Math3D.Dot( point, this.normal );
    }       

    public void SetFromCoplanarPoints( Vector3 a, Vector3 b, Vector3 c ) 
    {
      var normal = Math3D.ComputeNormal( a, b, c );
      SetFromNormalAndCoplanarPoint( normal, a );
    }

    public static Plane3 CreateFromNormalAndCoplanarPoint( Vector3 normal, Vector3 point ) 
    {
      var p = new Plane3();
      p.SetFromNormalAndCoplanarPoint( normal, point );
      return p;
    }

    public static Plane3 FromCoplanarPoints( Vector3 a, Vector3 b, Vector3 c ) 
    {
      var p = new Plane3();
      p.SetFromCoplanarPoints( a, b, c );

      return p;
    }
  }
}