import csv
import os
import pprint
import subprocess

from base import Write

DRY_RUN = False
SCRIPT = os.path.relpath(__file__, os.getcwd()).replace("\\", "/")
REMAP_MODES = {
    "DEFAULT": "RemapDefault",
    "PARTIAL_REMAP": "RemapPartial1",
    "PARTIAL_REMAP1": "RemapPartial1",
    "PARTIAL_REMAP2": "RemapPartial2",
    "FULL_REMAP": "RemapFull",
    "REMAP": "RemapFull",
}

CFG_TABLE = {
    "TIM1": '#[cfg(any(feature = "f100", feature = "f103", feature = "connectivity"))]',
    "TIM4": '#[cfg(feature = "medium")]',
    "TIM5": '#[cfg(any(feature = "high", feature = "connectivity"))]',
    "TIM6": '#[cfg(any(feature = "f100", feature = "high", feature = "connectivity"))]',
    "TIM7": '#[cfg(any(all(feature = "high", any(feature = "f101", feature = "f103")),any(feature = "f100", feature = "connectivity")))]',
    "TIM8": '#[cfg(all(feature = "f103", feature = "high"))]',
    "TIM9": '#[cfg(feature = "xl")]',
    "TIM10": '#[cfg(feature = "xl")]',
    "TIM11": '#[cfg(feature = "xl")]',
    "TIM12": '#[cfg(any(feature = "xl", all(feature = "f100", feature = "high",)))]',
    "TIM13": '#[cfg(any(feature = "xl", all(feature = "f100", feature = "high",)))]',
    "TIM14": '#[cfg(any(feature = "xl", all(feature = "f100", feature = "high",)))]',
    "TIM15": '#[cfg(feature = "f100")]',
    "TIM16": '#[cfg(feature = "f100")]',
    "TIM17": '#[cfg(feature = "f100")]',
}


def match_filter(filter: str, name: str) -> bool:
    if filter == "UART":
        return name.startswith("UART") or name.startswith("USART")
    return name.startswith(filter)


def func_pin_name(filter: str, func: str) -> str:
    return filter[0] + filter[1:].lower() + func[0] + func[1:].lower() + "Pin"


REG_TEMPLATE = """impl RemapMode<{peri}> for {mode}<{peri}> {{
    fn remap(afio: &mut Afio) {{
        {op}
    }}
}}
"""


def write_reg_operation(d: dict, filter: str, w: Write) -> None:
    w.write("\n// Register operations ------------\n\n")
    for peri, remap_modes in sorted(d.items()):
        if match_filter(filter, peri):
            for mode_name, mode_info in sorted(remap_modes.items()):
                mode = REMAP_MODES[mode_name]
                reg = mode_info["reg"]
                bits: str = mode_info["bits"]
                if reg == "none":
                    op = ""
                elif peri == "TIM5":
                    b = "set_bit" if bits[2] == "1" else "clear_bit"
                    op = f"afio.{reg}.modify_mapr(|_, w| w.{peri.lower()}ch4_iremap().{b}());"
                elif len(bits) == 3:
                    b = "set_bit" if bits[2] == "1" else "clear_bit"
                    op = f"afio.{reg}.modify_mapr(|_, w| w.{peri.lower()}_remap().{b}());"
                elif len(bits) == 4:
                    b = f"unsafe {{|_, w| w.{peri.lower()}_remap().bits({bits})}}"
                    op = f"afio.{reg}.modify_mapr({b});"
                else:
                    continue
                w.write(CFG_TABLE.get(peri, ""))
                w.write(REG_TEMPLATE.format(mode=mode, peri=peri, op=op))


BINDER_BODY = """ {{
    #[inline(always)]
    fn is_pin(&self) -> bool {{
        {v}
    }}
}}
"""


def write_binder_type(d: dict, filter: str, w: Write) -> None:
    w.write("\n// Binder types ------------------\n\n")
    func_list: list[str] = []
    for peri, remap_modes in d.items():
        if match_filter(filter, peri):
            for mode_info in remap_modes.values():
                for pin_func in mode_info["pins"].keys():
                    func_list.append(pin_func)

    func_list = sorted(list(set(func_list)))
    for func in func_list:
        name = func_pin_name(filter, func)
        if filter in ["UART", "TIM"]:
            w.write(f"pub trait {name}<REMAP>" + BINDER_BODY.format(v="true"))
            w.write(f"impl<T> {name}<T> for NonePin" + BINDER_BODY.format(v="false"))
        else:
            w.write(f"pub trait {name}<REMAP>{{}}")

    w.write("\n")


IMPL_TEMPLATE_LIST = [
    (
        ["UartTxPin", "UartCkPin", "TimCh1Pin", "TimCh2Pin", "TimCh3Pin", "TimCh4Pin"],
        "impl {func}<{mode}<{peri}>> for {pin}<Alternate<PushPull>>",
    ),
    (["UartRxPin"], "impl<PULL: UpMode> {func}<{mode}<{peri}>> for {pin}<Input<PULL>>"),
    (["I2cSclPin", "I2cSdaPin"], "impl {func}<{mode}<{peri}>> for {pin}<Alternate<OpenDrain>>"),
]


def get_impl_template(func: str) -> str:
    for item in IMPL_TEMPLATE_LIST:
        if func in item[0]:
            return item[1]
    return ""


def write_item(filter: str, peri: str, mode: str, pins: dict[str, str], w: Write) -> None:
    for pin_func, pin in sorted(pins.items()):
        func = func_pin_name(filter, pin_func)
        impl = get_impl_template(func)
        if impl:
            cfg = CFG_TABLE.get(peri, "")
            if cfg:
                w.write(cfg)

            w.write(impl.format(func=func, mode=mode, peri=peri, pin=pin))
            w.write("{}")


def write_table(d: dict, filter: str, csv_file: str, target_file: str) -> None:
    with open(target_file, "r", encoding="utf-8") as f:
        code = f.read()
        i = code.find("// table") + len("// table")
        before = code[:i]
        code = code[i:]

    w = Write(target_file, DRY_RUN)
    w.write(before)
    w.write("\n// Do NOT manually modify the code.\n")
    w.write(
        f"// It's generated by {SCRIPT} from {csv_file}\n",
    )
    write_binder_type(d, filter, w)
    w.write("\n// Bind pins ---------------------\n\n")
    for peri, remap_modes in sorted(d.items()):
        if match_filter(filter, peri):
            for mode_name, mode_info in sorted(remap_modes.items()):
                mode = REMAP_MODES[mode_name]
                write_item(filter, peri, mode, mode_info["pins"], w)
    w.write("\n")
    write_reg_operation(d, filter, w)
    w.close()
    subprocess.run(["rustfmt", target_file])


def parse_remap_info(row: list[str], ret_d: dict) -> None:
    peripheral = row[0]
    reg = row[1]
    remap_mode = row[2]
    reg_bits = row[3]
    pins: dict[str, str] = {}
    for pin in row[4:]:
        if pin:
            (func, pin) = pin.split(":")
            if "/" in func:
                (f1, f2) = func.split("/")
                pins[f1] = pin
                pins[f2] = pin
            else:
                pins[func] = pin

    p = ret_d.setdefault(peripheral, {})
    p[remap_mode] = {
        "reg": reg,
        "bits": reg_bits,
        "pins": pins,
    }


def csv_to_code(csv_file: str, show: bool = False) -> None:
    print(csv_file)
    d: dict = {}
    with open(csv_file, newline="", encoding="utf-8") as f:
        reader = csv.reader(f, delimiter=",", quotechar='"')
        for row in reader:
            if row[0]:
                parse_remap_info(row, d)

    if show:
        pprint.pprint(d)

    write_table(d, "UART", csv_file, "src/afio/uart_remap.rs")
    write_table(d, "TIM", csv_file, "src/afio/timer_remap.rs")
    write_table(d, "I2C", csv_file, "src/afio/i2c_remap.rs")


if __name__ == "__main__":
    csv_to_code("scripts/table/stm32f1_remap_peripheral.csv")
