/*
 * SPDX-License-Identifier: MIT
 */
package org.ta4j.core.indicators.averages;

import org.ta4j.core.Indicator;
import org.ta4j.core.indicators.RecursiveCachedIndicator;
import org.ta4j.core.num.Num;

import static org.ta4j.core.num.NaN.NaN;

/**
 * Base class for Exponential Moving Average implementations.
 *
 * <p>
 * Seed 방식: 불안정 구간 직후 첫 번째 유효 EMA 값은 직전 barCount 개 봉의
 * SMA(단순이동평균)를 초기값으로 사용한다. TradingView / 업비트와 동일한 방식으로,
 * 이전 구현(현재 입력값 그대로 사용)보다 초기 수십 봉의 정확도가 높아진다.
 */
public abstract class AbstractEMAIndicator extends RecursiveCachedIndicator<Num> {

    private final Indicator<Num> indicator;
    private final int barCount;
    private final Num multiplier;

    /**
     * Constructor.
     *
     * @param indicator  the {@link Indicator}
     * @param barCount   the time frame
     * @param multiplier the multiplier
     */
    protected AbstractEMAIndicator(Indicator<Num> indicator, int barCount, double multiplier) {
        super(indicator);
        this.indicator = indicator;
        this.barCount = barCount;
        this.multiplier = getBarSeries().numFactory().numOf(multiplier);
    }

    @Override
    protected Num calculate(int index) {
        // Return NaN during unstable period
        int beginIndex = getBarSeries().getBeginIndex();
        int unstableBars = getCountOfUnstableBars();
        if (index < beginIndex + unstableBars) {
            return NaN;
        }

        Num current = indicator.getValue(index);

        // Check for NaN in current value
        if (Num.isNaNOrNull(current)) {
            return NaN;
        }

        // Get previous value
        Num prevValue = getValue(index - 1);
        if (Num.isNaNOrNull(prevValue)) {
            // 첫 번째 유효 EMA: 직전 barCount 개 봉의 SMA 를 seed 로 사용 (TradingView/업비트 방식).
            // 단순히 current 를 쓰는 것보다 초기 수렴 정확도가 높다.
            return computeSmaSeed(index);
        }

        // Standard EMA calculation
        return prevValue.plus(current.minus(prevValue).multipliedBy(multiplier));
    }

    /**
     * 불안정 구간 직후 인덱스에서 직전 barCount 개 봉의 SMA 를 계산하여 EMA seed 로 반환.
     * beginIndex 보다 이전으로 거슬러 올라가지 않도록 실제 사용 가능한 봉 수로 제한한다.
     */
    private Num computeSmaSeed(int index) {
        int beginIndex = getBarSeries().getBeginIndex();
        int start = Math.max(beginIndex, index - barCount + 1);
        int count = index - start + 1;
        if (count <= 0) {
            return indicator.getValue(index);
        }
        Num sum = getBarSeries().numFactory().numOf(0);
        for (int i = start; i <= index; i++) {
            Num v = indicator.getValue(i);
            if (Num.isNaNOrNull(v)) {
                return indicator.getValue(index);
            }
            sum = sum.plus(v);
        }
        return sum.dividedBy(getBarSeries().numFactory().numOf(count));
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " barCount: " + barCount;
    }

    public int getBarCount() {
        return barCount;
    }
}
