import math
import time


class DynamicValueGenerator:
    def current_value(self) -> float:
        raise NotImplementedError


class SineWaveGenerator(DynamicValueGenerator):
    def __init__(self, min_value: float, max_value: float, period: float, offset: float = 0.0) -> None:
        self.min = min_value
        self.max = max_value
        self.period = period
        self.offset = offset

    def current_value(self) -> float:
        now = time.time() - self.offset
        amplitude = (self.max - self.min) / 2.0
        midpoint = self.min + amplitude
        phase = (now / self.period) * 2.0 * math.pi
        return midpoint + amplitude * math.sin(phase)


class CosineWaveGenerator(DynamicValueGenerator):
    def __init__(self, min_value: float, max_value: float, period: float, offset: float = 0.0) -> None:
        self.min = min_value
        self.max = max_value
        self.period = period
        self.offset = offset

    def current_value(self) -> float:
        now = time.time() - self.offset
        amplitude = (self.max - self.min) / 2.0
        midpoint = self.min + amplitude
        phase = (now / self.period) * 2.0 * math.pi
        return midpoint + amplitude * math.cos(phase)


class SawtoothWaveGenerator(DynamicValueGenerator):
    def __init__(self, min_value: float, max_value: float, period: float, offset: float = 0.0) -> None:
        self.min = min_value
        self.max = max_value
        self.period = period
        self.offset = offset

    def current_value(self) -> float:
        now = time.time() - self.offset
        phase = (now / self.period) % 1.0
        return self.min + (self.max - self.min) * phase


class TriangleWaveGenerator(DynamicValueGenerator):
    def __init__(self, min_value: float, max_value: float, period: float, offset: float = 0.0) -> None:
        self.min = min_value
        self.max = max_value
        self.period = period
        self.offset = offset

    def current_value(self) -> float:
        now = time.time() - self.offset
        phase = (now / self.period) % 1.0
        normalized = phase * 2.0 if phase < 0.5 else 2.0 - phase * 2.0
        return self.min + (self.max - self.min) * normalized


class ConstantValueGenerator(DynamicValueGenerator):
    def __init__(self, value: float) -> None:
        self.value = value

    def current_value(self) -> float:
        return self.value


class CurveGenerator(DynamicValueGenerator):
    def __init__(
        self,
        min_value: float,
        max_value: float,
        period: float,
        offset: float,
        nodes: list,
        interpolation: str = "smooth",
        loop_start: float = 0.0,
        loop_end: float = 1.0,
        loop_mode: str = "forward",
    ) -> None:
        self.min = min_value
        self.max = max_value
        self.period = period
        self.offset = offset
        self.nodes = sorted(nodes, key=lambda n: n["time"])
        self.interpolation = interpolation
        self.loop_start = loop_start
        self.loop_end = loop_end
        self.loop_mode = loop_mode

    def current_value(self) -> float:
        now = time.time() - self.offset
        raw_t = now / self.period if self.period > 0 else 0
        period_count = int(raw_t)
        t = raw_t % 1.0

        if t >= self.loop_start:
            loop_duration = self.loop_end - self.loop_start
            if loop_duration > 0:
                time_since_loop_start = t - self.loop_start
                loop_count_in_period = int(time_since_loop_start / loop_duration)
                position_in_loop = time_since_loop_start % loop_duration

                if self.loop_mode == "forward":
                    t = self.loop_start + position_in_loop
                elif self.loop_mode == "backward":
                    t = self.loop_end - position_in_loop
                elif self.loop_mode == "alternating":
                    total_loop_count = period_count if loop_duration >= 1 else (period_count + loop_count_in_period)
                    is_reverse = total_loop_count % 2 == 1
                    if is_reverse:
                        t = self.loop_end - position_in_loop
                    else:
                        t = self.loop_start + position_in_loop
                else:
                    t = self.loop_start + position_in_loop

        t = max(0.0, min(1.0, t))

        normalized = self._interpolate(t)
        return self.min + normalized * (self.max - self.min)

    def _interpolate(self, t: float) -> float:
        if not self.nodes:
            return 0.0
        if len(self.nodes) == 1:
            return self.nodes[0]["value"]

        if t <= self.nodes[0]["time"]:
            return self.nodes[0]["value"]
        if t >= self.nodes[-1]["time"]:
            return self.nodes[-1]["value"]

        if self.interpolation == "smooth" and len(self.nodes) > 2:
            return self._catmull_rom(t)
        return self._linear(t)

    def _linear(self, t: float) -> float:
        for i in range(len(self.nodes) - 1):
            p0 = self.nodes[i]
            p1 = self.nodes[i + 1]
            if p0["time"] <= t <= p1["time"]:
                if p1["time"] == p0["time"]:
                    return p0["value"]
                segment_t = (t - p0["time"]) / (p1["time"] - p0["time"])
                return p0["value"] + segment_t * (p1["value"] - p0["value"])
        return self.nodes[-1]["value"]

    def _catmull_rom(self, t: float) -> float:
        segment_index = 0
        for i in range(len(self.nodes) - 1):
            if self.nodes[i]["time"] <= t <= self.nodes[i + 1]["time"]:
                segment_index = i
                break

        p0 = self.nodes[max(0, segment_index - 1)]
        p1 = self.nodes[segment_index]
        p2 = self.nodes[min(len(self.nodes) - 1, segment_index + 1)]
        p3 = self.nodes[min(len(self.nodes) - 1, segment_index + 2)]

        if p2["time"] == p1["time"]:
            segment_t = 0
        else:
            segment_t = (t - p1["time"]) / (p2["time"] - p1["time"])

        t2 = segment_t * segment_t
        t3 = t2 * segment_t

        v0 = p0["value"]
        v1 = p1["value"]
        v2 = p2["value"]
        v3 = p3["value"]

        return 0.5 * (
            (2 * v1)
            + (-v0 + v2) * segment_t
            + (2 * v0 - 5 * v1 + 4 * v2 - v3) * t2
            + (-v0 + 3 * v1 - 3 * v2 + v3) * t3
        )
