using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;

namespace Syntriax.Engine.Physics2D;

public class PhysicsEngine2D : HierarchyObject, IPhysicsEngine2D
{
    public event IPhysicsEngine2D.PhysicsIterationEventHandler? OnPhysicsIteration = null;
    public event IPhysicsEngine2D.PhysicsStepEventHandler? OnPhysicsStep = null;

    private float physicsTicker = 0f;
    private int _iterationPerStep = 1;
    private float _iterationPeriod = 1f / 60f;

    protected readonly ICollisionDetector2D collisionDetector = null!;
    protected readonly ICollisionResolver2D collisionResolver = null!;

    protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new();
    protected BehaviourCollector<ICollider2D> colliderCollector = new();
    protected BehaviourCollector<IPhysicsUpdate> physicsUpdateCollector = new();

    public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
    public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); }

    public void Step(float deltaTime)
    {
        float intervalDeltaTime = deltaTime / IterationPerStep;

        for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++)
        {
            // Can Parallel
            foreach (IRigidBody2D rigidBody in rigidBodyCollector)
                StepRigidBody(rigidBody, intervalDeltaTime);

            // Can Parallel
            foreach (ICollider2D collider in colliderCollector)
                collider.Recalculate();

            // Can Parallel
            for (int x = 0; x < colliderCollector.Behaviours.Count; x++)
            {
                ICollider2D? colliderX = colliderCollector.Behaviours[x];
                if (!colliderX.IsActive)
                    return;

                for (int y = x + 1; y < colliderCollector.Behaviours.Count; y++)
                {
                    ICollider2D? colliderY = colliderCollector.Behaviours[y];

                    if (!colliderY.IsActive)
                        return;

                    if (colliderX.RigidBody2D == colliderY.RigidBody2D)
                        continue;

                    bool bothCollidersAreTriggers = colliderX.IsTrigger && colliderX.IsTrigger == colliderY.IsTrigger;
                    if (bothCollidersAreTriggers)
                        continue;

                    bool bothCollidersAreStatic = (colliderX.RigidBody2D?.IsStatic ?? true) && (colliderY.RigidBody2D?.IsStatic ?? true);
                    if (bothCollidersAreStatic)
                        continue;

                    if (collisionDetector.TryDetect(colliderX, colliderY, out CollisionDetectionInformation information))
                    {
                        if (colliderX.IsTrigger)
                        {
                            colliderX.Trigger(colliderY);
                            continue;
                        }
                        else if (colliderY.IsTrigger)
                        {
                            colliderY.Trigger(colliderX);
                            continue;
                        }

                        if (information.Detector == colliderX)
                        {
                            colliderX.Detect(information);
                            colliderY.Detect(information.Reverse());
                        }
                        else
                        {
                            colliderX.Detect(information.Reverse());
                            colliderY.Detect(information);
                        }

                        collisionResolver?.Resolve(information);
                    }
                }
            }

            OnPhysicsIteration?.Invoke(this, intervalDeltaTime);
        }

        foreach (IPhysicsUpdate physicsUpdate in physicsUpdateCollector)
            physicsUpdate.PhysicsUpdate(deltaTime);

        OnPhysicsStep?.Invoke(this, deltaTime);
    }

    private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
    {
        if (rigidBody.IsStatic || !rigidBody.IsActive)
            return;

        rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
        rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
    }

    protected override void OnEnteringHierarchy(IGameManager gameManager)
    {
        physicsUpdateCollector.Assign(gameManager);
        colliderCollector.Assign(gameManager);
        rigidBodyCollector.Assign(gameManager);

        gameManager.OnPreUpdate += OnEnginePreUpdate;
    }

    protected override void OnExitingHierarchy(IGameManager gameManager)
    {
        physicsUpdateCollector.Unassign();
        colliderCollector.Unassign();
        rigidBodyCollector.Unassign();

        gameManager.OnPreUpdate -= OnEnginePreUpdate;
    }

    private void OnEnginePreUpdate(IGameManager sender, EngineTime engineTime)
    {
        physicsTicker += engineTime.DeltaTime;

        while (physicsTicker >= IterationPeriod)
        {
            physicsTicker -= IterationPeriod;
            Step(physicsTicker);
        }
    }

    public PhysicsEngine2D()
    {
        collisionDetector = new CollisionDetector2D();
        collisionResolver = new CollisionResolver2D();
    }

    public PhysicsEngine2D(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
    {
        this.collisionDetector = collisionDetector;
        this.collisionResolver = collisionResolver;
    }
}