using System.Collections.Generic;

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

namespace Syntriax.Engine.Physics2D;

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

    private readonly List<IRigidBody2D> rigidBodies = new(32);
    private readonly List<ICollider2D> colliders = new(64);

    private int _iterationCount = 1;

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

    public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }

    public void AddRigidBody(IRigidBody2D rigidBody)
    {
        if (rigidBodies.Contains(rigidBody))
            return;

        rigidBodies.Add(rigidBody);

        foreach (ICollider2D collider2D in rigidBody.BehaviourController.GetBehaviours<ICollider2D>())
            colliders.Add(collider2D);

        rigidBody.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
        rigidBody.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
    }

    public void RemoveRigidBody(IRigidBody2D rigidBody)
    {
        rigidBodies.Remove(rigidBody);
    }

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

        for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++)
        {
            // Can Parallel
            for (int i = 0; i < rigidBodies.Count; i++)
                StepRigidBody(rigidBodies[i], intervalDeltaTime);

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

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

                for (int y = x + 1; y < colliders.Count; y++)
                {
                    ICollider2D? colliderY = colliders[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(colliderY);
                            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);
        }
        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;
    }

    private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
    {
        if (behaviour is not ICollider2D collider2D)
            return;

        colliders.Add(collider2D);
    }

    private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
    {
        if (behaviour is not ICollider2D collider2D)
            return;

        colliders.Remove(collider2D);
    }

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

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