#!/usr/bin/python3

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import clapp
from clapp.cli import (
    Bool,
    LiteralPos,
    P_HELP,
    Pattern,
    ScalarPos,
)
from clapp.sh import (
    run_cmd,
    stdout_of,
)

from saturnin.context import get_config_repo

AttrsT = dict[str, Any]


def identity_fn(arg: Any) -> Any:
    return arg


def get_current_layout() -> str:
    for line in stdout_of(['setxkbmap', '-query']).splitlines():
        if not line.startswith('layout:'):
            continue
        parts = line.split(':')
        return parts[1].strip()
    raise clapp.AppError("could not detect current layout using setxkbmap")


@dataclass(frozen=True, slots=True)
class LayoutRepo:
    layouts: list[str]
    current_layout: str
    dry: bool

    @classmethod
    def from_config_data(cls, data, dry: bool = False):
        if not data.get('layout', []):
            raise clapp.AppError("no layouts defined in kb.ini")
        return cls(
            layouts=data['layout'],
            current_layout=get_current_layout(),
            dry=dry,
        )

    def get_home_layout(self) -> str:
        return self.layouts[0]

    def get_next_layout(self) -> str:
        taking_next = False
        for layout in self.layouts + self.layouts:
            if taking_next:
                return layout
            if layout == self.current_layout:
                taking_next = True
        raise clapp.AppError("could not determine next layout")

    def set_layout(self, layout: str) -> None:
        run_cmd(
            cmd=['setxkbmap', layout],
            dry=self.dry,
        )


def show_a(params: dict[str, Any], app: App) -> int:
    print(get_current_layout())
    return 0


def list_a(params: dict[str, Any], app: App) -> int:
    repo: LayoutRepo = params['repo']
    for layout in repo.layouts:
        print(layout)
    return 0


def next_a(params: dict[str, Any], app: App) -> int:
    repo: LayoutRepo = params['repo']
    repo.set_layout(repo.get_next_layout())
    return 0


def home_a(params: dict[str, Any], app: App) -> int:
    repo: LayoutRepo = params['repo']
    repo.set_layout(repo.get_home_layout())
    return 0


def switch_a(params: dict[str, Any], app: App) -> int:
    repo: LayoutRepo = params['repo']
    repo.set_layout(params['LAYOUT'])
    return 0


class App(clapp.BaseApp):

    name: str = 'bmo kb'

    @property
    def arg_scheme(self):
        set_options = {
            'DRY': Bool('-n|--dry'),
        }
        S = Pattern()
        S.add_pattern(P_HELP)
        S.add_pattern(Pattern(
            trigger={'clapp.action': show_a}
        )),
        S.add_pattern(Pattern(
            positionals=[
                ('_', LiteralPos('show')),
            ],
            trigger={'clapp.action': show_a}
        )),
        S.add_pattern(Pattern(
            positionals=[
                ('_', LiteralPos('list')),
            ],
            trigger={'clapp.action': list_a}
        )),
        S.add_pattern(Pattern(
            options=set_options,
            positionals=[
                ('_', LiteralPos('next')),
            ],
            trigger={'clapp.action': next_a}
        )),
        S.add_pattern(Pattern(
            options=set_options,
            positionals=[
                ('_', LiteralPos('home')),
            ],
            trigger={'clapp.action': home_a}
        )),
        S.add_pattern(Pattern(
            options=set_options,
            positionals=[
                ('_', LiteralPos('layout')),
                ('LAYOUT', ScalarPos()),
            ],
            trigger={'clapp.action': switch_a}
        )),
        return S

    def init_params(self, from_args: AttrsT) -> AttrsT:
        return {
            'repo': LayoutRepo.from_config_data(
                data=get_config_repo().data('kb'),
                dry=from_args.get('DRY', False),
            ),
            'clapp.action': from_args.get('clapp.action'),
            'clapp.Action': from_args.get('clapp.Action'),
        }

    @property
    def usage_patterns(self) -> list[str]:
        return [
            "[show]",
            "[-n|--dry] next",
            "[-n|--dry] home",
            "[-n|--dry] layout LAYOUT",
            "list",
        ]

    @property
    def help_lines(self) -> list[str]:
        return self.base_usage + [
            "",
            "Set or show current keyboard layout.",
        ]


if __name__ == "__main__":
    App.main()
