using System;

namespace QuanTAlib
{
    public class Frama : AbstractBase
    {
        private readonly int _period;
        private readonly double _fc;
        private CircularBuffer _buffer;
        private double _lastFrama;
        private double _prevLastFrama;

        public Frama(int period, double fc = 0.5) : base()
        {
            if (period < 2)
                throw new ArgumentException("Period must be at least 2", nameof(period));

            _period = period;
            _fc = fc;
            _buffer = new CircularBuffer(period);
            WarmupPeriod = period;
        }

        public override void Init()
        {
            base.Init();
            _buffer.Clear();
            _lastFrama = 0;
            _prevLastFrama = 0;
        }

        protected override void ManageState(bool isNew)
        {
            if (isNew)
            {
                _prevLastFrama = _lastFrama;
                _index++;
            }
            else
            {
                _lastFrama = _prevLastFrama;
            }
        }

        protected override double Calculation()
        {
            ManageState(Input.IsNew);
            
            _buffer.Add(Input.Value, Input.IsNew);

            if (_buffer.Count < _period)
            {
                _lastFrama = _buffer.Average();
                return _lastFrama;
            }

            int half = _period / 2;
            double hh = double.MinValue, ll = double.MaxValue;
            double hh1 = double.MinValue, ll1 = double.MaxValue;
            double hh2 = double.MinValue, ll2 = double.MaxValue;

            for (int i = 0; i < _period; i++)
            {
                double price = _buffer[i];
                hh = Math.Max(hh, price);
                ll = Math.Min(ll, price);

                if (i < half)
                {
                    hh1 = Math.Max(hh1, price);
                    ll1 = Math.Min(ll1, price);
                }
                else
                {
                    hh2 = Math.Max(hh2, price);
                    ll2 = Math.Min(ll2, price);
                }
            }

            double n1 = (hh - ll) / _period;
            double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2);

            double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2);

            double alpha = Math.Exp(-4.6 * (d - 1));
            alpha = Math.Max(Math.Min(alpha, 1), 0.01);  // Ensure alpha is between 0.01 and 1

            _lastFrama = alpha * (Input.Value - _lastFrama) + _lastFrama;

            IsHot = _index >= WarmupPeriod;
            return _lastFrama;
        }

        protected override double GetLastValid()
        {
            return _lastFrama;
        }
    }
}