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

namespace Rokojori
{
  public class CollisionData
  {
    public bool hasCollision = false;
    public Node collider;
    public Vector3 normal;
    public Vector3 position;
    public Shape3D shape;
    public Rid rid;


    public static bool HasCollisionMask( Node n )
    {
      return n is CsgShape3D || n is CollisionObject3D;
    }

    public static uint GetCollisionMask( Node n )
    {
      if ( n is CsgShape3D )
      {
        return ( n as CsgShape3D ).CollisionMask;
      }

      if ( n is CollisionObject3D )
      {
        return ( n as CollisionObject3D ).CollisionMask;
      }

      return 0;
    }

    public static bool HasCollisionLayer( Node n )
    {
      return n is CsgShape3D || n is CollisionObject3D;
    }

    public static uint GetCollisionLayer( Node n )
    {
      if ( n is CsgShape3D )
      {
        return ( n as CsgShape3D ).CollisionLayer;
      }

      if ( n is CollisionObject3D )
      {
        return ( n as CollisionObject3D ).CollisionLayer;
      }

      return 0;
    }


    public void Get( PhysicsRayQueryParameters3D ray, PhysicsDirectSpaceState3D physicsState )
    {
      var result = physicsState.IntersectRay( ray );



      if ( ! result.ContainsKey( "collider" ) )
      {
        hasCollision = false;
        return;
      }

      hasCollision = true;

      

      collider = result[ "collider" ].As<Node>();
      normal   = result[ "normal" ].AsVector3();
      position = result[ "position" ].AsVector3();
      shape    = result[ "shape" ].As<Shape3D>();
      rid      = result[ "rid" ].AsRid();

      //  RJLog.Log( "Has Collision:", HierarchyName.Of( collider ), ">> at position:", position, "with normal:", normal  );
 
    }

    public static CollisionData FindCollision( World3D world, PhysicsRayQueryParameters3D rayParameters, Func<CollisionData,bool> predicate )
    {
      var physics = world.DirectSpaceState;

      var excludes = new Array<Rid>();

      var maxHits = 10000;

      for ( int i = 0; i < maxHits; i++ )
      {        
        rayParameters.Exclude = excludes;

        var collisionData = new CollisionData();
        collisionData.Get( rayParameters, physics );

        if ( ! collisionData.hasCollision )
        {
          return null;
        }

        if ( predicate( collisionData ) )
        {
          return collisionData;
        }
        
        excludes.Add( collisionData.rid );

      }

      return null;
    
    }

    public static int MultiRayCast( World3D world, PhysicsRayQueryParameters3D rayParameters, List<CollisionData> collisionsOutput )
    {
      var physics = world.DirectSpaceState;

      var excludes = new Array<Rid>();

      var numCollisions = 0;

      for ( int i = 0; i < collisionsOutput.Count; i++ )
      {        
        rayParameters.Exclude = excludes;

        var collisionData = collisionsOutput[ i ];
        collisionData.Get( rayParameters, physics );

        if ( ! collisionData.hasCollision )
        {
          return i;
        }
        
        excludes.Add( collisionData.rid );

        numCollisions ++;
      }

      return numCollisions;
    
    }

    public static List<CollisionData> MultiRayCast( World3D world, PhysicsRayQueryParameters3D rayParameters, int maxHits = 100 )
    {
      var physics = world.DirectSpaceState;

      var excludes = new Array<Rid>();

      var collisions = new List<CollisionData>();

      for ( int i = 0; i < maxHits; i++ )
      {        
        rayParameters.Exclude = excludes;

        var collisionData = new CollisionData();
        collisionData.Get( rayParameters, physics );

        if ( ! collisionData.hasCollision )
        {
          return collisions;
        }
        
        excludes.Add( collisionData.rid );

        collisions.Add( collisionData );
      }

      return collisions;
    
    }
  }
}