# © 2021 Qualcomm Innovation Center, Inc. All rights reserved.
#
# 2019 Cog Systems Pty Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause

from lark import Transformer, v_args
from ir import (
    Include, Symbol, Type, ConstExpr, Priority, Result, ExpectedArgs, Param,
    Selectors, SelectorParam, CountParam, Module, Event, HandledEvent,
    MultiEvent, SetupEvent, SelectorEvent, Subscription, Optional, Public,
    Handler, Constant, Unwinder, Success, LockAnnotation, LockName, NoReturn)

import collections
import logging
import math


logger = logging.getLogger(__name__)


class TransformToIR(Transformer):
    def __init__(self, module_dict, event_dict):
        super().__init__()
        self.module_dict = module_dict
        self.event_dict = event_dict
        self.cur_event_dict = {}
        self.errors = 0

    def _add_without_duplicates(self, type_string, in_dict, new):
        if new.name in in_dict:
            existing = in_dict[new.name]
            new_meta = new.name.meta
            old_meta = existing.name.meta
            logger.error("%s:%d:%d: error: duplicate definition of %s '%s'",
                         new_meta.filename, new_meta.line, new_meta.column,
                         type_string, new.name)
            logger.info("%s:%d:%d: note: previous definition of %s '%s'",
                        old_meta.filename, old_meta.line,
                        old_meta.column, type_string, new.name)
            self.errors += 1
        else:
            in_dict[new.name] = new

    def _general_event(self, event_class, children, meta):
        # Check for duplicated parameters
        params = {}
        for c in children:
            if isinstance(c, Param):
                self._add_without_duplicates('parameter', params, c)

        event = event_class(children)
        event.meta = meta
        self._add_without_duplicates('event', self.cur_event_dict, event)
        return event

    @v_args(meta=True)
    def module(self, children, meta):
        assert not self.cur_event_dict
        m = Module(children)
        m.meta = meta
        if m.name in self.module_dict:
            self.module_dict[m.name].merge(m)
        else:
            self.module_dict[m.name] = m
        return m

    @v_args(meta=True)
    def interface(self, children, meta):
        interface_name = next(c for c in children if isinstance(c, Symbol))
        for name, event in self.cur_event_dict.items():
            if not name.startswith(interface_name + "_"):
                meta = event.name.meta
                logger.error("%s:%d:%d: error: incorrect name: "
                             "'%s' should start with '%s_'",
                             meta.filename, meta.line, meta.column, name,
                             interface_name)
                self.errors += 1
            self.event_dict[name] = event
        self.cur_event_dict = {}
        return self.module(children, meta)

    def include(self, children):
        return Include(''.join(str(c) for c in children))

    @v_args(meta=True)
    def publish_event(self, children, meta):
        return self._general_event(Event, children, meta)

    @v_args(meta=True)
    def publish_handled_event(self, children, meta):
        return self._general_event(HandledEvent, children, meta)

    @v_args(meta=True)
    def publish_multi_event(self, children, meta):
        return self._general_event(MultiEvent, children, meta)

    @v_args(meta=True)
    def publish_setup_event(self, children, meta):
        return self._general_event(SetupEvent, children, meta)

    @v_args(meta=True)
    def publish_selector_event(self, children, meta):
        return self._general_event(SelectorEvent, children, meta)

    @v_args(meta=True)
    def symbol(self, children, meta):
        sym = Symbol(*children)
        sym.meta = meta
        return sym

    @v_args(meta=True)
    def event_param(self, children, meta):
        p = Param(children)
        p.meta = meta
        return p

    @v_args(meta=True)
    def selector_param(self, children, meta):
        p = SelectorParam(children)
        p.meta = meta
        return p

    @v_args(meta=True)
    def count_param(self, children, meta):
        p = CountParam(children)
        p.meta = meta
        return p

    @v_args(meta=True)
    def result(self, children, meta):
        r = Result(children)
        r.meta = meta
        return r

    @v_args(meta=True)
    def void_result(self, children, meta):
        r = Result(children, void=True)
        r.meta = meta
        return r

    @v_args(meta=True)
    def type_decl(self, children, meta):
        t = Type(' '.join(str(c) for c in children))
        t.meta = meta
        return t

    @v_args(meta=True)
    def selector_const(self, children, meta):
        t = Type(' '.join(str(c) for c in children))
        t.meta = meta
        return t

    @v_args(meta=True)
    def subscribe(self, children, meta):
        s = Subscription(children)
        s.meta = meta
        return s

    @v_args(meta=True)
    def selector(self, children, meta):
        s = Selectors(children)
        s.meta = meta
        return s

    subscribe_public = subscribe

    def optional(self, children):
        return Optional()

    def public(self, children):
        return Public()

    @v_args(meta=True)
    def handler(self, children, meta):
        h = Handler(*children)
        h.meta = meta
        return h

    handler_public = handler

    @v_args(meta=True)
    def unwinder(self, children, meta):
        u = Unwinder(*children)
        u.meta = meta
        return u

    unwinder_public = unwinder

    constant = v_args(inline=True)(Constant)

    def expected_args(self, children):
        args = collections.OrderedDict()
        for c in children:
            c.name = c
            self._add_without_duplicates('argument', args, c)
        return ExpectedArgs(args.values())

    @v_args(meta=True)
    def priority(self, children, meta):
        if children[0] in ('first', 'max'):
            c = Priority(math.inf)
        elif children[0] in ('last', 'min'):
            c = Priority(-math.inf)
        elif children[0] == 'default':
            c = Priority(0)
        else:
            c = Priority(children[0])
        c.meta = meta
        return c

    @v_args(meta=True)
    def noreturn(self, children, meta):
        c = NoReturn()
        c.meta = meta
        return c

    @v_args(meta=True)
    def constexpr(self, children, meta):
        c = ConstExpr(' '.join(children))
        c.meta = meta
        return c

    @v_args(meta=True)
    def success(self, children, meta):
        c = Success(' '.join(children))
        c.meta = meta
        return c

    @v_args(meta=True)
    def lock_name(self, children, meta):
        c = LockName(''.join(children))
        c.meta = meta
        return c

    @v_args(meta=True)
    def lock_opt(self, children, meta):
        action, kind = children[0].rsplit()[-1].split('_')
        c = LockAnnotation(action, kind, children[1])
        c.meta = meta
        return c
