namespace QuanTAlib;

public class Tema : AbstractBase
{
    private readonly int _period;
    private double _lastEma1, _p_lastEma1;
    private double _lastEma2, _p_lastEma2;
    private double _lastEma3, _p_lastEma3;
    private double _k, _e, _p_e;

    public Tema(int period) : base()
    {
        if (period < 1)
        {
            throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
        }
        _period = period;
        Name = "Tema";
        double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
        WarmupPeriod = (int)Math.Ceiling(-period * Math.Log(1 - percentile));
        Init();
    }

    public Tema(object source, int period) : this(period)
    {
        var pubEvent = source.GetType().GetEvent("Pub");
        pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
    }

    public override void Init()
    {
        base.Init();
        _k = 2.0 / (_period + 1);
        _e = 1.0;
        _lastEma1 = _lastEma2 = _lastEma3 = 0;
    }

    protected override void ManageState(bool isNew)
    {
        if (isNew)
        {
            _p_lastEma1 = _lastEma1;
            _p_lastEma2 = _lastEma2;
            _p_lastEma3 = _lastEma3;
            _p_e = _e;
            _index++;
        }
        else
        {
            _lastEma1 = _p_lastEma1;
            _lastEma2 = _p_lastEma2;
            _lastEma3 = _p_lastEma3;
            _e = _p_e;
        }
    }

    protected override double Calculation()
    {
        double result, _ema1, _ema2, _ema3;
        ManageState(Input.IsNew);
        
        _e = (_e > 1e-10) ? (1 - _k) * _e : 0;
        double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1;

        _ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;

        _ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2;

        _ema3 = _k * (_ema2 * _invE - _lastEma3) + _lastEma3;

        double _tema = 3 * _ema1 * _invE - 3 * _ema2 * _invE + _ema3 * _invE;

        result = _tema;
        _lastEma1 = _ema1;
        _lastEma2 = _ema2;
        _lastEma3 = _ema3;

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