"""c-generator.

Usage:
  validator.py validate <json>
  validator.py (-h | --help)
  validator.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
"""

import json
import logging

from .spec import *


check_table = {
    "sig": {},
    "msg": {},
    "dev": {},
    "spec": {},
    "cmd": {},
    "cfg": {},
    "log": {},
}


def check_decorator(category):
    def closure(f):
        if category not in check_table.keys():
            print(f"{category} category not available")
            return
        check_table[category][f.__name__] = f

    return closure


def fail_msg(node, msg):
    def location(node):
        if node.parent is None:
            return node.name

        return location(node.parent) + "/" + node.name

    return f"{location(node)}: {msg}"


@check_decorator("sig")
def signal_length(signal):
    if signal.length > 64 or signal.length <= 0:
        return fail_msg(signal, f"signal length is {signal.length}")


@check_decorator("sig")
def signal_start(signal):
    if signal.start > 63 or signal.start < 0:
        return fail_msg(signal, f"signal start is {signal.start}")


@check_decorator("sig")
def signal_total_length(signal):
    if signal.length + signal.offset > 64:
        return fail_msg(signal, f"signal end is bigger than 64")


@check_decorator("sig")
def signal_type(signal):
    types = ["unsigned", "signed", "double", "float"]
    if signal.type not in types:
        return fail_msg(
            signal, f"signal type is {signal.type}. Valid types are: {types}"
        )
    if signal.type == "float" and signal.length != 32:
        return fail_msg(signal, f"signal is float but length is {signal.length} != 32")

    if signal.type == "double" and signal.length != 64:
        return fail_msg(signal, f"signal is double but length is {signal.length} != 64")


@check_decorator("sig")
def signal_endianess(signal):
    endianess = ["little_endian", "big_endian"]

    if signal.byte_order not in endianess:
        return fail_msg(
            signal, f"signal endianess not supported. Valid values are: {endianess}"
        )


@check_decorator("sig")
def signal_name(signal):
    if not signal.name.isidentifier():
        return fail_msg(signal, f"{signal.name} is not a valid identifier")


@check_decorator("sig")
def signal_scaling(signal):
    if signal.scale == 1 and signal.offset != 0:
        return fail_msg(
            signal,
            f"{signal.name}: signal offset is different than 0 for a scaling of 1",
        )

    if signal.scale != 1 and signal.length > 32:
        return fail_msg(
            signal, f"scaling is not supported for variables bigger than 32 bits"
        )


@check_decorator("sig")
def signal_mux(signal):
    if signal.mux == 0:
        return fail_msg(signal, f"Mux count is 0, should be >= 1")


@check_decorator("msg")
def msg_mux(msg):
    muxeds = [signal.mux for signal in msg.signals.values() if signal.mux != ""]
    if len(muxeds) == 0:
        return

    mux = msg.signals.get(muxeds[0])
    if mux == None:
        return fail_msg(msg, f"Cannot find mux signal in multiplexed message")

    if 2 ** mux.length < mux.mux_count:
        return fail_msg(msg, f"Mux cannot fit all possible multiplexed values")


@check_decorator("msg")
def msg_name(msg):
    if not msg.name.isidentifier():
        return fail_msg(msg, f"Message name is not a valid identifier")


@check_decorator("msg")
def msg_id(msg):
    if msg.id > 64 or msg.id < 0:
        return fail_msg(msg, f"Message id is not valid: {msg.id}")


@check_decorator("msg")
def msg_dlc(msg):
    if msg.dlc > 8 or msg.dlc < 0:
        return fail_msg(msg, f"Message dlc is not valid: {msg.dlc}")


@check_decorator("msg")
def msg_frequency(msg):
    if msg.frequency < 0:
        return fail_msg(msg, f"Message frequency is not valid: {msg.frequency}")


@check_decorator("msg")
def msg_overlapping_signals(msg):
    values = []
    for signal in msg.signals.values():
        values += list(range(signal.start, signal.start + signal.length))

    s = set(values)

    if len(s) != len(values):
        return fail_msg(msg, f"Message has overlapping signals")


@check_decorator("dev")
def dev_name(dev):
    if not dev.name.isidentifier():
        return fail_msg(dev, f"Device name is not a valid identifier")


@check_decorator("dev")
def dev_id(dev):
    if dev.id > 31 or dev.id < 0:
        return fail_msg(dev, f"Device id is not valid: {dev.id}")


@check_decorator("dev")
def dev_overlapping_msg_ids(dev):
    ids = [msg.id for msg in dev.msgs.values()]
    ids_set = set(ids)

    if len(ids_set) != len(ids):
        return fail_msg(dev, f"Device has overlapping msg ids")


@check_decorator("log")
def log_id(log):
    if log.id > 255 and log.id < 0:
        return fail_msg(log, f"Log id is not valid: {log.id}")


@check_decorator("log")
def log_n_args(log):
    if log.n_args > 3 or log.n_args < 0:
        return fail_msg(log, f"Log n args is not valid: {log.n_args}")


@check_decorator("log")
def log_name(log):
    if not log.name.isidentifier():
        return fail_msg(log, f"Log name is not valid: {log.name}")


@check_decorator("cfg")
def cfg_name(cfg):
    if not cfg.name.isidentifier():
        return fail_msg(cfg, f"Cfg name is not valid: {cfg.name}")


@check_decorator("cfg")
def cfg_id(cfg):
    if cfg.id > 255 and cfg.id < 0:
        return fail_msg(cfg, f"Cfg id is not valid: {cfg.id}")


@check_decorator("cmd")
def cmd_name(cmd):
    if not cmd.name.isidentifier():
        return fail_msg(cmd, f"Cmd name is not valid: {cmd.name}")


@check_decorator("cmd")
def cmd_id(cmd):
    if cmd.id > 255 and cmd.id < 0:
        return fail_msg(cmd, f"Cmd id is not valid: {cmd.id}")


@check_decorator("cmd")
def cmd_n_args(cmd):
    if cmd.n_args > 3 and cmd.n_args < 0:
        return fail_msg(cmd, f"Cmd n args is not valid: {cmd.n_args}")


@check_decorator("spec")
def spec_overlapping_dev_ids(spec):
    ids = [dev.id for dev in spec.devices.values()]
    ids_set = set(ids)

    if len(ids_set) != len(ids):
        return fail_msg(spec, f"There are overlapping device ids")


def spec_repeated_names(spec):
    def get_signal_names(spec):
        for dev in spec.devices.values():
            for msg in dev.msgs.values():
                for signal in msg.signals.values():
                    yield signal.name

    names = get_signal_names(spec)

    if len(set(names)) != len(names):
        return fail_msg(spec, f"There are repeated signal names")


def check(category, arg):
    failed = []

    if category not in check_table.keys():
        print(f"{category} category not available")
        return failed

    for check in check_table[category].values():
        r = check(arg)

        if r:
            failed.append(r)

    return failed


def validate(logger, j, spec):
    failed = []
    failed += check("spec", spec)

    for log in spec.logs.values():
        failed += check("log", log)
    for message in spec.common.msgs.values():
        failed += check("msg", message)
    for device in spec.devices.values():
        failed += check("dev", device)
        for message in device.msgs.values():
            failed += check("msg", message)
            for signal in message.signals.values():
                failed += check("sig", signal)

        for cfg in device.cfgs.values():
            failed += check("cfg", cfg)

        for cmd in device.cmds.values():
            failed += check("cmd", cmd)

    return failed
