//TODO: consistency test

namespace QuanTAlib;

// https://efs.kb.esignal.com/hc/en-us/articles/6362791434395-2005-Mar-The-Secret-Behind-The-Filter-MedianAdaptiveFilter-efs

public class Maaf : AbstractBase
{
    private readonly CircularBuffer _priceBuffer;
    private readonly CircularBuffer _smoothBuffer;
    private double _prevFilter, _prevValue2, _threshold;
    private double _p_prevFilter, _p_prevValue2;

    private readonly int _period;

    public Maaf(int Period = 39, double Threshold = 0.002) : base()
    {
        _period = Period;
        _threshold = Threshold;
        _priceBuffer = new CircularBuffer(4);
        _smoothBuffer = new CircularBuffer(Period);
        Name = "MAAF";
        WarmupPeriod = Period;
        Init();
    }

    public Maaf(object source, int Period = 39, double Threshold = 0.002) : this(Period, Threshold)
    {
        var pubEvent = source.GetType().GetEvent("Pub");
        pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
    }

    public override void Init()
    {
        _priceBuffer.Clear();
        _smoothBuffer.Clear();
        _prevFilter = 0;
        _prevValue2 = 0;
        base.Init();
    }

    protected override void ManageState(bool isNew)
    {
        if (isNew)
        {
            _lastValidValue = Input.Value;
            _index++;
            _p_prevFilter = _prevFilter;
            _p_prevValue2 = _prevValue2;
        }
        else
        {
            _prevFilter = _p_prevFilter;
            _prevValue2 = _p_prevValue2;
        }
    }

    protected override double Calculation()
    {
        ManageState(IsNew);

        _priceBuffer.Add(Input.Value, Input.IsNew);

        if (_priceBuffer.Count < 4)
        {
            return Input.Value;
        }

        double smooth = (_priceBuffer[^1] + (2 * _priceBuffer[^2]) + (2 * _priceBuffer[^3]) + _priceBuffer[^4]) / 6;
        _smoothBuffer.Add(smooth, Input.IsNew);

        if (_smoothBuffer.Count < _period)
        {
            return smooth;
        }

        int length = _period;
        double value3 = 0.2;
        double value2 = _prevValue2;

        while (value3 > _threshold && length > 0)
        {
            double alpha = 2.0 / (length + 1);

            var sortedValues = _smoothBuffer.TakeLast(length).OrderBy(x => x).ToList();
            double value1 = sortedValues[length / 2];
            value2 = alpha * (smooth - _prevValue2) + _prevValue2;

            if (value1 != 0)
            {
                value3 = Math.Abs(value1 - value2) / value1;
            }

            length -= 2;
        }

        if (length < 3) length = 3;

        double finalAlpha = 2.0 / (length + 1);
        double filter = finalAlpha * (smooth - _prevFilter) + _prevFilter;

        _p_prevFilter = _prevFilter;
        _prevFilter = filter;
        _p_prevValue2 = _prevValue2;
        _prevValue2 = value2;

        IsHot = _index >= WarmupPeriod;
        return filter;
    }
}
