""" sexpr.py -- Walk through a scalar expression and populate elements """

import logging
from typing import List
from xuml_populate.config import mmdb
from xuml_populate.populate.actions.expressions.instance_set import InstanceSet
from xuml_populate.populate.actions.read_action import ReadAction
from xuml_populate.exceptions.action_exceptions import ScalarOperationOrExpressionExpected
from xuml_populate.populate.flow import Flow
from xuml_populate.populate.actions.aparse_types import Flow_ap, MaxMult, Content, Activity_ap, Boundary_Actions
from scrall.parse.visitor import Scalar_RHS_a, MATH_a, BOOL_a, INST_a, N_a, Projection_a, Op_chain_a, INST_PROJ_a
from xuml_populate.populate.actions.project_action import ProjectAction

from pyral.relation import Relation  # Keep for debugging

_logger = logging.getLogger(__name__)


class ScalarExpr:
    """
    For reference, a scalar expression in the scrall grammar consists of terms

    term = (NOT SP*)? UNARY_MINUS? (scalar / "(" SP* sexpr SP* ")")
    scalar = value / QTY? scalar_chain
    scalar_chain = (ITS op_chain) / ((scalar_source / instance_set projection?) op_chain?)
    scalar_source = type_selector / input_param
    op_chain = ('.' (scalar_op / name))*
    scalar_op = name supplied_params

    So we need to walk through the parse tree through the nested operations, possibly
    building instance sets.
    """
    text = None  # A text representation of the expression
    activity_data = None
    component_flow = None
    output_tflow_id = None
    action_inputs = None
    action_outputs = None
    anum = None
    domain = None

    @classmethod
    def process(cls, rhs: Scalar_RHS_a, input_instance_flow: Flow_ap,
                activity_data: Activity_ap) -> (Boundary_Actions, List[Flow_ap]):
        """
        Walks through a scalar expression on the right hand side of a scalar assignment to
        obtain a tuple flow with one or more attributes. Each attribute value will be assigned.
        The order in which attributes are specified in the action language is returned along with
        the tuple flow.

        :param rhs: The right hand side of a table assignment
        :param input_instance_flow:
        :param activity_data:
        :return:  The output tuple flow and the attribute names as ordered in the RHS text expression
        """
        cls.domain = activity_data.domain
        cls.anum = activity_data.anum
        cls.activity_data = activity_data

        cls.action_outputs = {}  # ID's of all Action output Data Flows
        cls.action_inputs = {}  # ID's of all Action input Data Flows

        rhs = cls.walk(sexpr=rhs.expr, input_flow=input_instance_flow)

        all_ins = {v for s in cls.action_inputs.values() for v in s}
        all_outs = {v for s in cls.action_outputs.values() for v in s}
        init_aids = {a for a in cls.action_inputs.keys() if not cls.action_inputs[a].intersection(all_outs)}
        final_aids = {a for a in cls.action_outputs.keys() if not cls.action_outputs[a].intersection(all_ins)}

        return Boundary_Actions(ain=init_aids, aout=final_aids), rhs

    @classmethod
    def resolve_iset(cls, iset: INST_a, op_chain: Op_chain_a = None, projection: Projection_a = None) -> List[Flow_ap]:
        pass

    @classmethod
    def walk(cls, sexpr: INST_PROJ_a | MATH_a | BOOL_a | N_a, input_flow: Flow_ap) -> [Flow_ap]:
        """

        :param sexpr:  Parsed scalar expression
        :param input_flow:
        :return:  Output scalar flow
        """
        component_flow = input_flow
        match type(sexpr).__name__:
            case 'INST_PROJ_a':
                action_input = component_flow
                initial_aid, final_aid, component_flow = InstanceSet.process(input_instance_flow=action_input,
                                                                             iset_components=sexpr.iset.components,
                                                                             activity_data=cls.activity_data)
                # Add the output flow generated by the instance set expression to the set of ouput flows
                if initial_aid:
                    # For an InstanceSet with a single labled flow component, no action is created
                    # So don't process action inputs and outputs unless there is an initial_aid
                    cls.action_inputs[initial_aid] = {action_input.fid}
                    if final_aid:
                        cls.action_outputs[final_aid] = {component_flow.fid}
                action_input = component_flow
                project_attrs = tuple([a.name for a in sexpr.projection.attrs])
                aid, sflows = ReadAction.populate(input_single_instance_flow=action_input,
                                                  attrs=project_attrs, anum=cls.anum, domain=cls.domain)
                cls.action_inputs[aid] = {action_input.fid}
                cls.action_outputs[aid] = {s.fid for s in sflows}
                return sflows
            case 'N_a':
                pass
            case 'BOOL_a':
                pass
            case 'MATH_a':
                action_input = component_flow
                operand_flows = []
                op_name = sexpr.op
                for o in sexpr.operands:
                    match type(o).__name__:
                        case 'INST_PROJ_a':
                            component_flow = InstanceSet.process(input_instance_flow=action_input,
                                                                 iset_components=o.iset.components,
                                                                 activity_data=cls.activity_data)
                            if o.iset.select:
                                pass
                            if o.projection:
                                tflow = ProjectAction.populate(projection=o.projection,
                                                               input_nsflow=action_input,
                                                               activity_data=cls.activity_data)
                                pass
                            pass
                        case _:
                            pass

                    operand_flows.append(cls.walk(sexpr=o, input_flow=component_flow))
                pass
            case 'Op_chain_a':
                pass
            case _:
                _logger.error(
                    f"Expected .... but received {type(sexpr).__name__} during sexpr walk")
                raise ScalarOperationOrExpressionExpected
        # Process optional header, selection, and projection actions for the TEXPR
        return component_flow
