using Syntriax.Engine.Core.Abstract;

namespace Syntriax.Engine.Core;

[System.Diagnostics.DebuggerDisplay("Name: {HierarchyObject.Name, nq} Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform2D : Behaviour, ITransform2D
{
    public event ITransform2D.PositionChangedEventHandler? OnPositionChanged = null;
    public event ITransform2D.ScaleChangedEventHandler? OnScaleChanged = null;
    public event ITransform2D.RotationChangedEventHandler? OnRotationChanged = null;

    private Vector2D _position = Vector2D.Zero;
    private Vector2D _scale = Vector2D.One;
    private float _rotation = 0f;

    private Vector2D _localPosition = Vector2D.Zero;
    private Vector2D _localScale = Vector2D.One;
    private float _localRotation = 0f;

    private ITransform2D? parentTransform = null;

    public Vector2D Position
    {
        get => _position;
        set
        {
            if (value == _position)
                return;

            Vector2D previousPosition = _position;
            _position = value;

            UpdateLocalPosition();
            OnPositionChanged?.Invoke(this, _position);
        }
    }

    public Vector2D Scale
    {
        get => _scale;
        set
        {
            if (value == _scale)
                return;

            Vector2D previousScale = _scale;
            _scale = value;

            UpdateLocalScale();
            OnScaleChanged?.Invoke(this, previousScale);
        }
    }

    public float Rotation
    {
        get => _rotation;
        set
        {
            if (value == _rotation)
                return;

            float previousRotation = _rotation;
            _rotation = value;

            UpdateLocalRotation();
            OnRotationChanged?.Invoke(this, previousRotation);
        }
    }

    public Vector2D LocalPosition
    {
        get => _localPosition;
        set
        {
            if (value == _localPosition)
                return;

            Vector2D previousPosition = _position;
            _localPosition = value;

            UpdatePosition();
            OnPositionChanged?.Invoke(this, previousPosition);
        }
    }

    public Vector2D LocalScale
    {
        get => _localScale;
        set
        {
            if (value == _localScale)
                return;

            Vector2D previousScale = _scale;
            Vector2D previousPosition = _position;
            _localScale = value;

            UpdateScale();
            UpdatePosition();
            OnScaleChanged?.Invoke(this, previousScale);
            OnPositionChanged?.Invoke(this, previousPosition);
        }
    }

    public float LocalRotation
    {
        get => _localRotation;
        set
        {
            if (value == _localRotation)
                return;

            float previousRotation = _rotation;
            _localRotation = value;

            UpdateRotation();
            OnRotationChanged?.Invoke(this, previousRotation);
        }
    }

    private void RecalculatePosition(ITransform2D _, Vector2D previousPosition)
    {
        if (parentTransform is null)
            return;

        UpdatePosition();

        OnPositionChanged?.Invoke(this, previousPosition);
    }

    private void RecalculateScale(ITransform2D _, Vector2D previousScale)
    {
        if (parentTransform is null)
            return;

        Vector2D previousPosition = _position;

        UpdateScale();
        UpdatePosition();

        OnScaleChanged?.Invoke(this, previousScale);
        OnPositionChanged?.Invoke(this, previousPosition);
    }

    private void RecalculateRotation(ITransform2D _, float previousRotation)
    {
        if (parentTransform is null)
            return;

        Vector2D previousPosition = Position;

        UpdateRotation();
        UpdatePosition();

        OnRotationChanged?.Invoke(this, previousRotation);
        OnPositionChanged?.Invoke(this, previousPosition);
    }

    private void UpdateLocalPosition()
    {
        if (parentTransform is null)
            _localPosition = Position;
        else
            _localPosition = parentTransform.Position.FromTo(Position).Scale(parentTransform.Scale);
    }

    private void UpdateLocalScale()
    {
        if (parentTransform is null)
            _localScale = Scale;
        else
            _localScale = Scale.Scale(new(1f / parentTransform.Scale.X, 1f / parentTransform.Scale.Y));
    }

    private void UpdateLocalRotation()
    {
        if (parentTransform is null)
            _localRotation = Rotation;
        else
            _localRotation = Rotation - parentTransform.Rotation;
    }

    private void UpdatePosition()
    {
        if (parentTransform is null)
            _position = LocalPosition.Rotate(0f * Math.DegreeToRadian);
        else
            _position = parentTransform.Position + LocalPosition.Scale(new(parentTransform.Scale.X, parentTransform.Scale.Y)).Rotate(parentTransform.Rotation * Math.DegreeToRadian);
    }

    private void UpdateScale()
    {
        if (parentTransform is null)
            _scale = LocalScale;
        else
            _scale = (parentTransform?.Scale ?? Vector2D.One).Scale(_localScale);
    }

    private void UpdateRotation()
    {
        if (parentTransform is null)
            _rotation = LocalRotation;
        else
            _rotation = parentTransform.Rotation + LocalRotation;
    }

    protected override void InitializeInternal()
    {
        UpdateReferences(HierarchyObject.Parent);
        HierarchyObject.OnParentChanged += OnParentChanged;
    }

    protected override void FinalizeInternal()
    {
        HierarchyObject.OnParentChanged -= OnParentChanged;
    }

    private void UpdateReferences(IHierarchyObject? parent)
    {
        ITransform2D? previousParent = parentTransform;
        if (previousParent is not null)
        {
            previousParent.BehaviourController.HierarchyObject.RemoveChild(HierarchyObject);
            previousParent.OnPositionChanged -= RecalculatePosition;
            previousParent.OnScaleChanged -= RecalculateScale;
            previousParent.OnRotationChanged -= RecalculateRotation;
            previousParent.BehaviourController.HierarchyObject.OnParentChanged -= OnParentChanged;
            previousParent.BehaviourController.OnBehaviourAdded -= LookForTransform2D;
        }

        parentTransform = parent?.BehaviourController.GetBehaviour<ITransform2D>();

        if (parentTransform is not null)
        {
            parentTransform.BehaviourController.HierarchyObject.AddChild(HierarchyObject);
            parentTransform.OnPositionChanged += RecalculatePosition;
            parentTransform.OnScaleChanged += RecalculateScale;
            parentTransform.OnRotationChanged += RecalculateRotation;
            parentTransform.BehaviourController.HierarchyObject.OnParentChanged += OnParentChanged;

            UpdatePosition();
            UpdateScale();
            UpdateRotation();
        }
        else if (HierarchyObject.Parent is not null)
            HierarchyObject.Parent.BehaviourController.OnBehaviourAdded += LookForTransform2D;

        UpdateLocalPosition();
        UpdateLocalScale();
        UpdateLocalRotation();

        OnPositionChanged?.Invoke(this, Position);
        OnScaleChanged?.Invoke(this, Scale);
        OnRotationChanged?.Invoke(this, Rotation);
    }

    private void LookForTransform2D(IBehaviourController sender, IBehaviour behaviourAdded)
    {
        if (behaviourAdded is not ITransform2D transform2D)
            return;

        UpdateReferences(HierarchyObject.Parent);
    }

    private void OnParentChanged(IHierarchyObject sender, IHierarchyObject? previousParent, IHierarchyObject? newParent)
    {
        UpdateReferences(newParent);
    }
}