from dataclasses import dataclass
import struct


@dataclass
class ColorRGBW:
    r: float
    g: float
    b: float
    w: float

    def repr(self):
        return f"ColorRGBW({self.r}, {self.g}, {self.b}, {self.w}"

    def as_bytes(self) -> bytes:
        assert self.is_valid(), "Trying to send invalid " + repr(self)
        return struct.pack("<BBBB", int(self.r * 255), int(self.g * 255), int(self.b * 255),
                           int(self.w * 255))

    def is_valid(self):
        vals = (self.r, self.g, self.b, self.w)
        return all(0 <= v <= 1 for v in vals)


@dataclass
class ColorHSV:
    h: float
    s: float
    v: float

    def as_bytes(self) -> bytes:
        return struct.pack("<fff", self.h, self.s, self.v)

    def is_valid(self):
        if not 0 <= self.h <= 360:
            return False
        if not 0 <= self.s <= 1:
            return False
        if not 0 <= self.v <= 2:
            return False
        return True


@dataclass
class EffectStaticConfig:
    color: ColorRGBW

    def as_bytes(self) -> bytes:
        return self.color.as_bytes()


@dataclass
class EffectAlexaSwipeConfig:
    primary_color_width: float = 20  # in degrees
    transition_width: float = 30  # in degrees
    swipe_speed: float = 3 * 360  # in degrees per second
    bell_curve_width_in_leds: float = 3
    start_position: float = 180  # in degrees
    forward: bool = True
    primary_color: ColorRGBW = ColorRGBW(0, 0, 1, 0)
    secondary_color: ColorRGBW = ColorRGBW(0, 200 / 255, 1, 0)

    def as_bytes(self) -> bytes:
        return struct.pack(
            "<fffff?", self.primary_color_width, self.transition_width, self.swipe_speed,
            self.bell_curve_width_in_leds, self.start_position,
            self.forward) + self.primary_color.as_bytes() + self.secondary_color.as_bytes()


@dataclass
class EffectRandomTwoColorInterpolationConfig:
    cycle_durations_ms: int = 6000
    start_with_existing: bool = True
    num_segments: int = 3
    hue1_random: bool = False
    hue2_random: bool = False
    color1: ColorHSV = ColorHSV(240, 1, 1)
    color2: ColorHSV = ColorHSV(192, 1, 1)

    def as_bytes(self) -> bytes:
        return struct.pack("<i?i??", self.cycle_durations_ms, self.start_with_existing,
                           self.num_segments, self.hue1_random,
                           self.hue2_random) + self.color1.as_bytes() + self.color2.as_bytes()


@dataclass
class EffectCircularConfig:
    speed: float = 360  # in degrees per second
    width: float = 180  # in degrees
    color: ColorRGBW = ColorRGBW(0, 0, 1, 0)

    def as_bytes(self) -> bytes:
        return struct.pack("<ff", self.speed, self.width) + self.color.as_bytes()


@dataclass
class EffectSwipeAndChange:
    swipe: EffectAlexaSwipeConfig = EffectAlexaSwipeConfig()
    change: EffectRandomTwoColorInterpolationConfig = EffectRandomTwoColorInterpolationConfig()

    def as_bytes(self) -> bytes:
        return self.swipe.as_bytes() + self.change.as_bytes()