using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

namespace TagFighter.Resources
{
    public interface IAccessibleResource<TUnitType> where TUnitType : IUnitType
    {
        public Unit<TUnitType> GetCapacity();
        public Unit<TUnitType> GetCurrent();
    }


    [Serializable]
    public class Resource<TUnitType> : MonoBehaviour, IWatchableResource, IAccessibleResource<TUnitType> where TUnitType : IUnitType
    {
        [SerializeField] Stat<Current> _current;
        [SerializeField] Stat<Capacity> _capacity;

        readonly Unit<TUnitType> _minimumCapacity = (Unit<TUnitType>)0;

        public ResourceChangeArgs Status => (ResourceChangeArgs)this;

        public Unit<TUnitType> GetCapacity() {
            var value = _capacity.Total;
            if (value < _minimumCapacity) {
                value = _minimumCapacity;
            }
            return value;
        }

        public void AddCapacityModifier(StatModifier<TUnitType> modifier, CancellationToken cancelToken) {
            AddModifier(modifier, _capacity, cancelToken);
        }

        public Unit<TUnitType> GetCurrent() {
            var value = _current.Total;
            value = Unit<TUnitType>.Clamp(value, _minimumCapacity, GetCapacity());
            return value;
        }

        public void AddCurrentModifier(StatModifier<TUnitType> modifier, CancellationToken cancelToken) {
            AddModifier(modifier, _current, cancelToken);
        }

        void AddModifier<TStatType>(StatModifier<TUnitType> modifier, Stat<TStatType> stat, CancellationToken cancelToken)
            where TStatType : IStatType {
            cancelToken.Register(() => {
                OnResourceChanged((ResourceChangeArgs)this);
            });

            stat.AddModifier(modifier, cancelToken);

            Debug.Log($"AddModifier {transform.name}.{GetType().Name}.{typeof(TStatType).Name}");
            OnResourceChanged((ResourceChangeArgs)this);
        }

        public event EventHandler<ResourceChangeArgs> ResourceChanged;
        public static explicit operator ResourceChangeArgs(Resource<TUnitType> resource) {
            return new((int)resource.GetCurrent(), (int)resource._current.Base, (int)resource._current.Modified,
                       (int)resource.GetCapacity(), (int)resource._capacity.Base, (int)resource._capacity.Modified,
                       resource.transform);
        }

        void OnResourceChanged(ResourceChangeArgs e) {
            ResourceChanged?.Invoke(this, e);
        }

        public interface IStatType { }
        class Current : IStatType { }
        class Capacity : IStatType { }

        [Serializable]
        public class Stat<TStatType> where TStatType : IStatType
        {
            [SerializeField] Unit<TUnitType> _base;
            LinkedList<StatModifier<TUnitType>> _modifiers = new();

            public Unit<TUnitType> Base {
                get {
                    return _base;
                }
                private set {
                    _base = value;
                }
            }

            public Unit<TUnitType> Modified {
                get;
                private set;
            }

            public Unit<TUnitType> Total => Base + Modified;

            public void AddModifier(StatModifier<TUnitType> modifier, CancellationToken cancelToken) {
                if (cancelToken == CancellationToken.None) {
                    Base += modifier.Amount;
                }
                else {
                    AddTransientModifier(modifier, cancelToken);
                }
            }

            void AddTransientModifier(StatModifier<TUnitType> modifier, CancellationToken cancelToken) {
                LinkedListNode<StatModifier<TUnitType>> node = new(modifier);

                _modifiers.AddFirst(node);
                Modified += modifier.Amount;

                cancelToken.Register(() => {
                    _modifiers.Remove(node);
                    Modified -= modifier.Amount;
                });
            }
        }
    }
}