import numpy as np
from abc import ABC
from typing import Optional, Tuple
from finRav.stoch._fbm_base import fBMBase
from finRav.stoch._hosking_method import Hosking
from finRav.stoch._davies_harte_method import DaviesHarte
from finRav.utils._timebase import TimeScaler

"""
fBM.py
------

Wrapper class that provides a unified interface for generating
fractional Brownian motion using different algorithms (Hosking or Davies–Harte).
"""

class fBM(fBMBase, ABC):
    """
    Fractional Brownian Motion simulator (Hosking or Davies–Harte).

    Parameters
    ----------
    N : int
        Number of samples.
    H : float
        Hurst exponent.
    method : {'Hosking','DaviesHarte'}, default='Hosking'
        Simulation method.
    dt : float, optional
        Explicit time step. If provided, overrides `interval`/`unit`.
    interval : str, optional
        Bar size like '1m','5m','1H','1D'. Used only if `dt` is None.
    unit : {'1D','1Y'}, default='1D'
        Time unit for the grid if `interval` is used:
        - '1D' → fraction of a trading day (390 minutes).
        - '1Y' → fraction of a trading year (252 trading days).
    """
    def __init__(self, N: int, H: float, unit: str = '1D', dt: Optional[float] = None,
                 interval: Optional[str] = None, method: str='Hosking'):

        # validation: require either dt or interval
        if dt is None and interval is None:
            raise ValueError("You must provide either 'dt' or 'interval'.")

        if dt is None:
            scaler = TimeScaler(interval=interval, unit=unit)
            dt = scaler.dt

        super().__init__(N=N, dt=dt, H=H, method=method)

    def simulate(self) -> Tuple[np.ndarray, np.ndarray]:
        """
        Generate fractional Brownian motion using the chosen method.

        Returns
        -------
        (t, B) : tuple[np.ndarray, np.ndarray]
            t : time grid (N points)
            B : fBM path simulated using the chosen method.
        """

        t = np.arange(self.N) * self.dt

        if self.H == .5: # Standard Geometric Brownian motion
            dW = np.random.normal(0.0, np.sqrt(self.dt), size=self.N - 1)
            B = np.zeros(self.N, dtype=float)
            B[1:] = np.cumsum(dW)

        else:

            if self.method == 'Hosking':
                B = Hosking(N=self.N, dt=self.dt, H=self.H).simulate()
            elif self.method == 'DaviesHarte':
                B = DaviesHarte(N=self.N, dt=self.dt, H=self.H).simulate()

        return t, B