"""@package docstring
B-ASIC Signal Flow Graph Module.
TODO: More info.
"""

from typing import List, Dict, Optional, DefaultDict
from collections import defaultdict

from b_asic.operation import Operation
from b_asic.abstract_operation import AbstractOperation
from b_asic.signal import Signal
from b_asic.graph_id import GraphIDGenerator, GraphID
from b_asic.graph_component import GraphComponent, Name, TypeName


class SFG(AbstractOperation):
	"""Signal flow graph.
	TODO: More info.
	"""

	_graph_components_by_id: Dict[GraphID, GraphComponent]
	_graph_components_by_name: DefaultDict[Name, List[GraphComponent]]
	_graph_id_generator: GraphIDGenerator

	def __init__(self, input_signals: List[Signal] = None, output_signals: List[Signal] = None, \
				ops: List[Operation] = None, **kwds):
		super().__init__(**kwds)
		if input_signals is None:
			input_signals = []
		if output_signals is None:
			output_signals = []
		if ops is None:
			ops = []

		self._graph_components_by_id = dict() # Maps Graph ID to objects
		self._graph_components_by_name = defaultdict(list) # Maps Name to objects
		self._graph_id_generator = GraphIDGenerator()

		for operation in ops:
			self._add_graph_component(operation)

		for input_signal in input_signals:
			self._add_graph_component(input_signal)

		# TODO: Construct SFG based on what inputs that were given
		# TODO: Traverse the graph between the inputs/outputs and add to self._operations.
		# TODO: Connect ports with signals with appropriate IDs.

	def evaluate(self, inputs: list) -> list:
		return [] # TODO: Implement

	def _add_graph_component(self, graph_component: GraphComponent) -> GraphID:
		"""Adds the entered graph component to the SFG's dictionary of graph objects and
	 	returns a generated GraphID for it.

		Keyword arguments:
		graph_component: Graph component to add to the graph.
		"""
		# Add to name dict
		self._graph_components_by_name[graph_component.name].append(graph_component)

		# Add to ID dict
		graph_id: GraphID = self._graph_id_generator.get_next_id(graph_component.type_name)
		self._graph_components_by_id[graph_id] = graph_component
		return graph_id

	def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]:
		"""Finds a graph object based on the entered Graph ID and returns it. If no graph
		object with the entered ID was found then returns None.

		Keyword arguments:
		graph_id: Graph ID of the wanted object.
		"""
		if graph_id in self._graph_components_by_id:
			return self._graph_components_by_id[graph_id]

		return None

	def find_by_name(self, name: Name) -> List[GraphComponent]:
		"""Finds all graph objects that have the entered name and returns them
		in a list. If no graph object with the entered name was found then returns an
		empty list.

		Keyword arguments:
		name: Name of the wanted object.
		"""
		return self._graph_components_by_name[name]

	@property
	def type_name(self) -> TypeName:
		return "sfg"