# SPDX-License-Identifier: Apache-2.0
# Copyright © 2022-2023 Intel Corporation

"""Provides helpers for building AST

This is meant to make building Meson AST from foreign (largely declarative)
build descriptions easier.
"""

from __future__ import annotations
import dataclasses
import typing as T

from .. import mparser

if T.TYPE_CHECKING:
    import builtins


@dataclasses.dataclass
class Builder:

    filename: str

    def _token(self, tid: str, value: mparser.TV_TokenTypes) -> mparser.Token[mparser.TV_TokenTypes]:
        """Create a Token object, but with the line numbers stubbed out.

        :param tid: the token id (such as string, number, etc)
        :param filename: the filename that the token was generated from
        :param value: the value of the token
        :return: A Token object
        """
        return mparser.Token(tid, self.filename, -1, -1, -1, (-1, -1), value)

    def _symbol(self, val: str) -> mparser.SymbolNode:
        return mparser.SymbolNode(self._token('', val))

    def assign(self, value: mparser.BaseNode, varname: str) -> mparser.AssignmentNode:
        return mparser.AssignmentNode(self.identifier(varname), self._symbol('='), value)

    def string(self, value: str) -> mparser.StringNode:
        """Build A StringNode

        :param value: the value of the string
        :return: A StringNode
        """
        return mparser.StringNode(self._token('string', value))

    def number(self, value: int) -> mparser.NumberNode:
        """Build A NumberNode

        :param value: the value of the number
        :return: A NumberNode
        """
        return mparser.NumberNode(self._token('number', str(value)))

    def bool(self, value: builtins.bool) -> mparser.BooleanNode:
        """Build A BooleanNode

        :param value: the value of the boolean
        :return: A BooleanNode
        """
        return mparser.BooleanNode(self._token('bool', value))

    def array(self, value: T.List[mparser.BaseNode]) -> mparser.ArrayNode:
        """Build an Array Node

        :param value: A list of nodes to insert into the array
        :return: An ArrayNode built from the arguments
        """
        args = mparser.ArgumentNode(self._token('array', 'unused'))
        args.arguments = value
        return mparser.ArrayNode(self._symbol('['), args, self._symbol(']'))

    def dict(self, value: T.Dict[mparser.BaseNode, mparser.BaseNode]) -> mparser.DictNode:
        """Build an Dictionary Node

        :param value: A dict of nodes to insert into the dictionary
        :return: An DictNode built from the arguments
        """
        args = mparser.ArgumentNode(self._token('dict', 'unused'))
        for key, val in value.items():
            args.set_kwarg_no_check(key, val)
        return mparser.DictNode(self._symbol('{'), args, self._symbol('}'))

    def identifier(self, value: str) -> mparser.IdNode:
        """Build A IdNode

        :param value: the value of the boolean
        :return: A BooleanNode
        """
        return mparser.IdNode(self._token('id', value))

    def method(self, name: str, id_: mparser.BaseNode,
               pos: T.Optional[T.List[mparser.BaseNode]] = None,
               kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
               ) -> mparser.MethodNode:
        """Create a method call.

        :param name: the name of the method
        :param id_: the object to call the method of
        :param pos: a list of positional arguments, defaults to None
        :param kw: a dictionary of keyword arguments, defaults to None
        :return: a method call object
        """
        args = mparser.ArgumentNode(self._token('array', 'unused'))
        if pos is not None:
            args.arguments = pos
        if kw is not None:
            args.kwargs = {self.identifier(k): v for k, v in kw.items()}
        return mparser.MethodNode(id_, self._symbol('.'), self.identifier(name), self._symbol('('), args, self._symbol(')'))

    def function(self, name: str,
                 pos: T.Optional[T.List[mparser.BaseNode]] = None,
                 kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
                 ) -> mparser.FunctionNode:
        """Create a function call.

        :param name: the name of the function
        :param pos: a list of positional arguments, defaults to None
        :param kw: a dictionary of keyword arguments, defaults to None
        :return: a method call object
        """
        args = mparser.ArgumentNode(self._token('array', 'unused'))
        if pos is not None:
            args.arguments = pos
        if kw is not None:
            args.kwargs = {self.identifier(k): v for k, v in kw.items()}
        return mparser.FunctionNode(self.identifier(name), self._symbol('('), args, self._symbol(')'))

    def equal(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
        """Create an equality operation

        :param lhs: The left hand side of the equal
        :param rhs: the right hand side of the equal
        :return: A comparison node
        """
        return mparser.ComparisonNode('==', lhs, self._symbol('=='), rhs)

    def not_equal(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
        """Create an inequality operation

        :param lhs: The left hand side of the "!="
        :param rhs: the right hand side of the "!="
        :return: A comparison node
        """
        return mparser.ComparisonNode('!=', lhs, self._symbol('!='), rhs)

    def in_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
        """Create an "in" operation

        :param lhs: The left hand side of the "in"
        :param rhs: the right hand side of the "in"
        :return: A comparison node
        """
        return mparser.ComparisonNode('in', lhs, self._symbol('in'), rhs)

    def not_in(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
        """Create an "not in" operation

        :param lhs: The left hand side of the "not in"
        :param rhs: the right hand side of the "not in"
        :return: A comparison node
        """
        return mparser.ComparisonNode('notin', lhs, self._symbol('not in'), rhs)

    def or_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode:
        """Create and OrNode

        :param lhs: The Left of the Node
        :param rhs: The Right of the Node
        :return: The OrNode
        """
        return mparser.OrNode(lhs, self._symbol('or'), rhs)

    def and_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.AndNode:
        """Create an AndNode

        :param lhs: The left of the And
        :param rhs: The right of the And
        :return: The AndNode
        """
        return mparser.AndNode(lhs, self._symbol('and'), rhs)

    def not_(self, value: mparser.BaseNode) -> mparser.NotNode:
        """Create a not node

        :param value: The value to negate
        :return: The NotNode
        """
        return mparser.NotNode(self._token('not', ''), self._symbol('not'), value)

    def block(self, lines: T.List[mparser.BaseNode]) -> mparser.CodeBlockNode:
        block = mparser.CodeBlockNode(self._token('node', ''))
        block.lines = lines
        return block

    def plus(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ArithmeticNode:
        """Create an addition node

        :param lhs: The left of the addition
        :param rhs: The right of the addition
        :return: The ArithmeticNode
        """
        return mparser.ArithmeticNode('add', lhs, self._symbol('+'), rhs)

    def plusassign(self, value: mparser.BaseNode, varname: str) -> mparser.PlusAssignmentNode:
        """Create a "+=" node

        :param value: The value to add
        :param varname: The variable to assign
        :return: The PlusAssignmentNode
        """
        return mparser.PlusAssignmentNode(self.identifier(varname), self._symbol('+='), value)

    def if_(self, condition: mparser.BaseNode, block: mparser.CodeBlockNode) -> mparser.IfClauseNode:
        """Create a "if" block

        :param condition: The condition
        :param block: Lines inside the condition
        :return: The IfClauseNode
        """
        clause = mparser.IfClauseNode(condition)
        clause.ifs.append(mparser.IfNode(clause, self._symbol('if'), condition, block))
        clause.elseblock = mparser.EmptyNode(-1, -1, self.filename)
        return clause

    def foreach(self, varnames: T.List[str], items: mparser.BaseNode, block: mparser.CodeBlockNode) -> mparser.ForeachClauseNode:
        """Create a "foreach" loop

        :param varnames: Iterator variable names (one for list, two for dict).
        :param items: The list of dict to iterate
        :param block: Lines inside the loop
        :return: The ForeachClauseNode
        """
        varids = [self.identifier(i) for i in varnames]
        commas = [self._symbol(',') for i in range(len(varnames) - 1)]
        return mparser.ForeachClauseNode(self._symbol('foreach'), varids, commas, self._symbol(':'), items, block, self._symbol('endforeach'))
