#!/usr/bin/env python3
"""
 Generate interface from dbc file

 Copyright (c) 2023 ROX Automation - Jev Kuznetsov
"""

from pathlib import Path
import cantools
from odrive_can import get_dbc

script_name = Path(__file__).name

HEADER = """#!/usr/bin/env python3
## interface generated by {script_name}

from abc import ABC, abstractmethod

class DbcInterface(ABC):
    @abstractmethod
    async def request(self, msg_name: str, timeout: float = 0.5) -> dict:
        pass
"""


GET_TEMPLATE = """
    async def {method_name}(self) -> dict:
        return await self.request("{param}")\n"""


def split_message_name(message_name: str) -> tuple[str, str, str | None]:
    """split to axis, method_name, prefix (get/set)"""
    axis, method_name = message_name.split("_", 1)

    if method_name.lower().startswith("get"):
        prefix = "get"

    elif method_name.lower().startswith("set"):
        prefix = "set"

    else:
        prefix = None

    return axis, method_name, prefix


def get_method(message: cantools.db.Message) -> str:
    """get method string"""
    _, method_name, _ = split_message_name(message.name)

    code = GET_TEMPLATE.format(method_name=method_name.lower(), param=method_name)

    return code


def set_method(message: cantools.db.Message) -> str:
    """Generate a set method string from a CAN message."""
    _, method_name, _ = split_message_name(message.name)

    # Extracting signal names and types
    params = []
    param_dict = {}
    for signal in message.signals:
        signal_name = signal.name
        param_type = "int"
        if signal.is_float:
            param_type = "float"
        elif isinstance(signal.choices, dict):
            param_type = "str"  # Enumerated signal

        params.append(f"{signal_name.lower()}: {param_type}")
        param_dict[signal_name] = signal_name.lower()

    params_str = ", ".join(params)
    params_dict_str = ", ".join([f'"{k}": {v}' for k, v in param_dict.items()])

    # Template for the set method
    set_template = f"""
    def {method_name.lower()}(self, {params_str}):
        self._send_message("{method_name}", {{{{{params_dict_str}}}}})\n"""

    return set_template.format(
        method_name=method_name,
        message_name=message.name,
    )


def dbc_to_code(db, dest):
    """create interface from dbc database"""

    get_methods = []
    set_methods = []

    for message in db.messages[:28]:
        # split message name by first underscore

        print(f"Processing {message.name}")
        _, _, prefix = split_message_name(message.name)

        if prefix == "get":
            code = get_method(message)
            get_methods.append(code)

        elif prefix == "set":
            code = set_method(message)
            set_methods.append(code)

    print(f"Generating {dest}")
    # Open the Markdown file for writing
    with open(dest, "w", encoding="utf8") as output_file:
        # Write a header
        code = HEADER.format(script_name=script_name)
        output_file.write(code)

        for method in get_methods:
            output_file.write(method)

        for method in set_methods:
            output_file.write(method)


db = get_dbc("odrive-cansimple-0.5.6")
dbc_to_code(db, "interface.py")
