#! /usr/bin/python3 -sP
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2019 gfduszynski
# This file's hyphenated name is the installed CLI command (cm-rgb-gui); it can't
# be made snake_case without changing the command users type, so the module-name
# check is disabled just for this module.
# pylint: disable=invalid-name
"""GTK front end that shells out to cm-rgb-cli to configure the logo/fan/ring LEDs."""
# pylint: enable=invalid-name

import subprocess
from collections import namedtuple

import gi

gi.require_version("Gtk", "3.0")
# pylint: disable-next=wrong-import-position
from gi.repository import Gtk  # noqa: E402

ChannelControls = namedtuple("ChannelControls", ["mode", "color", "brightness", "speed"])


def make_mode_combo():
    """Build a Static/Breathing mode selector combo box."""
    modes = Gtk.ListStore(str)
    modes.append(["Static"])
    modes.append(["Breathing"])

    combo = Gtk.ComboBox.new_with_model(modes)
    combo.set_active(0)
    renderer_text = Gtk.CellRendererText()
    combo.pack_start(renderer_text, True)
    combo.add_attribute(renderer_text, "text", 0)
    return combo


def build_channel_tab(stack, tab_name, title):
    """Build one Logo/Fan/Ring settings tab (mode, color, brightness, speed) and add it to stack."""
    grid = Gtk.Grid()
    grid.set_row_spacing(20)
    grid.set_column_spacing(20)
    stack.add_titled(grid, tab_name, title)

    grid.attach(Gtk.Label(label="Mode: "), 0, 0, 1, 1)
    mode = make_mode_combo()
    grid.attach(mode, 1, 0, 1, 1)

    grid.attach(Gtk.Label(label="Color: "), 0, 1, 1, 1)
    color = Gtk.ColorButton()
    grid.attach(color, 1, 1, 1, 1)

    # 1-5 with default 3, matching cm-rgb-cli's --brightness/--speed ranges
    grid.attach(Gtk.Label(label="Brightness: "), 0, 2, 1, 1)
    brightness = Gtk.SpinButton()
    brightness.set_adjustment(Gtk.Adjustment(3, 1, 5, 1, 1, 0))
    grid.attach(brightness, 1, 2, 1, 1)

    speed_label = Gtk.Label(label="Speed: ")
    grid.attach(speed_label, 0, 3, 1, 1)
    speed = Gtk.SpinButton()
    speed.set_adjustment(Gtk.Adjustment(3, 1, 5, 1, 1, 0))
    grid.attach(speed, 1, 3, 1, 1)

    # Without this, the window's later show_all() would unconditionally re-show
    # these two widgets regardless of the visibility set below.
    speed_label.set_no_show_all(True)
    speed.set_no_show_all(True)

    def toggle_speed_visibility(_combo):
        visible = mode.get_active() == 1
        speed_label.set_visible(visible)
        speed.set_visible(visible)

    toggle_speed_visibility(None)
    mode.connect("changed", toggle_speed_visibility)

    return ChannelControls(mode=mode, color=color, brightness=brightness, speed=speed)


def rgba_to_hex(rgba):
    """Convert a Gtk.RGBA (0..1 floats) into a "#rrggbb" string."""
    red = int(rgba.red * 255)
    green = int(rgba.green * 255)
    blue = int(rgba.blue * 255)
    return f"#{red:02x}{green:02x}{blue:02x}"


def channel_cli_args(controls):
    """Build the `cm-rgb-cli set <channel> ...` option list for one ChannelControls."""
    mode = "breathe" if controls.mode.get_active() == 1 else "static"
    color = rgba_to_hex(controls.color.get_rgba())
    brightness = controls.brightness.get_value_as_int()
    speed = controls.speed.get_value_as_int()
    return [f"--mode={mode}", f"--color={color}", f"--brightness={brightness}", f"--speed={speed}"]


class MainWindow(Gtk.Window):
    """Top level window: Logo/Fan/Ring tabs plus Apply / Disable all RGB buttons."""

    def __init__(self):
        super().__init__(title="CM RGB")
        self.set_border_width(10)

        grid = Gtk.Grid()
        grid.set_row_spacing(20)
        self.add(grid)

        stack = Gtk.Stack()
        stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        stack.set_transition_duration(500)

        self.logo = build_channel_tab(stack, "logo_grid", "Logo")
        self.fan = build_channel_tab(stack, "fan_grid", "Fan")
        self.ring = build_channel_tab(stack, "ring_grid", "Ring")

        stack_switcher = Gtk.StackSwitcher()
        stack_switcher.set_stack(stack)
        grid.attach(stack_switcher, 0, 0, 1, 1)
        grid.attach(stack, 0, 1, 1, 1)

        btn_apply = Gtk.Button(label="Apply")
        btn_apply.connect("clicked", self.on_apply_clicked)
        grid.attach(btn_apply, 0, 3, 1, 1)

        btn_disable = Gtk.Button(label="Disable all RGB")
        btn_disable.connect("clicked", self.on_disable_clicked)
        grid.attach(btn_disable, 0, 2, 1, 1)

    def on_apply_clicked(self, _button):
        """Apply button handler: run cm-rgb-cli with the settings from all three tabs."""
        args = [
            "cm-rgb-cli", "set",
            "logo", *channel_cli_args(self.logo),
            "fan", *channel_cli_args(self.fan),
            "ring", *channel_cli_args(self.ring),
            "save",
        ]
        subprocess.run(args, check=False)
        print("\nCommand ran:", " ".join(args))

    def on_disable_clicked(self, _button):
        """Disable all RGB button handler."""
        print("\nDisabling all RGB")
        subprocess.run(["cm-rgb-cli", "set", "logo", "--mode=off", "save"], check=False)


def main():
    """Show the main window and run the GTK event loop."""
    win = MainWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()


if __name__ == '__main__':
    main()
