# import re
# import numpy as np
# import pkg_resources
# import yaml
# import inspect
#
# import gherkan.utils.constants as c
#
# class SignalCondition():
#     def __init__(self, string, kind, negated=False):
#         self.kind = kind
#         self.string = string
#         self.variable = None
#         self.value = None
#         self.variableTr = None
#         self.templateID = None
#         self.variableNLP = None
#         self.variableNLPCZ = None
#         self.variableNL_full = None
#         self.variableNL_full_cs = None
#
#         if self.kind == c.EQUALITY or kind == c.INEQUALITY:
#             match = re.search(r'(?P<variable>\w+)\s*[=!]+\s*(?P<value>\w+)', self.string)
#             if negated:
#                 if self.kind == c.EQUALITY:
#                     self.kind = c.INEQUALITY
#                 else:
#                     self.kind = c.EQUALITY
#         elif self.kind == c.BOOL:
#             match = re.search(r'(?P<variable>\w+)', self.string)
#             self.value = 1 if not negated else 0
#         elif self.kind == c.EDGE:
#             match = re.search(r'edge\((?P<variable>\w+)\s*,\s*(?P<value>\w+)', self.string)
#         elif self.kind == c.FORCE:
#             match = re.search(r'force\((?P<variable>\w+)\s*,\s*(?P<value>\w+)', self.string)
#         elif self.kind == c.UNFORCE:
#             match = re.search(r'unforce\((?P<variable>\w+)', self.string)
#         else:
#             raise Exception("Error while parsing a signal condition (whaaat?). Related string: '{}'. Unexpected condition kind: '{}'.".format(self.string, self.kind))
#         if match:
#             self.variable = match.group("variable")
#             if "value" in match.groupdict().keys():
#                 self.value = match.group("value")
#         else:
#             raise Exception("Error while parsing a signal condition. Phrase matching failed!\nRelated string: '{}'\nCondition kind: '{}'".format(self.string, self.kind))
#
#
# class NLCondition():
#     def __init__(self, strFullEn = None, strFullCs = None, strEng = None, strCZ = None):
#         self.kind = None
#         self.string = strFullEn
#         self.variable = None
#         self.value = None
#         self.variableTr = None
#         self.templateID = None
#         self.variableNL_full = strFullEn
#         self.variableNL_full_cs = strFullCs
#         self.variableNLP = strEng
#         self.variableNLPCZ = strCZ
#
#
# # TODO defined elsewhere, remove
# # class SignalStatementTreeNode():
# #
# #     def __init__(self, kind=None, data=None):
# #         self.__kind = kind
# #         self.__data = data
# #         self.__parent = None
# #
# #     def setParent(self, parent):
# #         self.__parent = parent
# #
# #     @property
# #     def kind(self):
# #         return self.__kind
# #
# #     @property
# #     def parent(self):
# #         return self.__parent
# #
# #     @property
# #     def data(self):
# #         return self.__data
# #
# #     def getIncompleteParent(self):
# #         raise NotImplementedError()
# #
# #
# #
# # class SignalStatementTreeOperatorNode(SignalStatementTreeNode):
# #     AND = 'and'
# #     OR = 'or'
# #
# #     def __init__(self, kind):
# #         super().__init__(kind.lower())
# #         self.__lchild = None  # type: SignalStatementTreeNode
# #         self.__rchild = None  # type: SignalStatementTreeNode
# #         self.templateID = None
# #         self.variableNLP = None
# #         self.variableNL_full = None
# #         self.variableNLPCZ = None
# #         self.variableNL_full_cs = None
# #
# #     @property
# #     def lchild(self):
# #         return self.__lchild
# #
# #     @property
# #     def rchild(self):
# #         return self.__rchild
# #
# #     @property
# #     def children(self):
# #         return [self.lchild, self.rchild]
# #
# #     @property
# #     def full(self):
# #         return (self.lchild is not None) and (self.rchild is not None)
# #
# #     def getIncompleteParent(self):
# #         if self.full:
# #             if self.parent:
# #                 return self.parent.getIncompleteParent()
# #             else:
# #                 return None
# #         else:
# #             return self
# #
# #     def attachChild(self, child: SignalStatementTreeNode):
# #         """
# #         Returns True if the node is "full" (i.e. both children are assigned)
# #         """
# #         child.setParent(self)
# #         if self.lchild:
# #             self.__rchild = child
# #             return True
# #         else:
# #             self.__lchild = child
# #             return False
# #
# #     def __str__(self):
# #         return 'Operator(type: {})'.format(self.kind.upper())
#
#
# # class SignalStatementTreeOperandNode(SignalStatementTreeNode):
# #
# #     def __init__(self, data: SignalCondition):
# #         super().__init__(data.kind, data)
# #
# #     def getIncompleteParent(self):
# #         self.parent.getIncompleteParent()
# #
# #     def __str__(self):
# #         return 'Operand(type: {}, variable: {}, value: {}, variableNLP: {}, variableNL_full: {})'.format(self.kind.upper() if self.kind else 'None', self.data.variable, self.data.value, self.data.variableNLP, self.data.variableNL_full)
# #
# #     @property
# #     def variable(self):
# #         return self.data.variable
# #
# #     @property
# #     def kind(self):
# #         return self.data.kind
# #
# #     @property
# #     def variableNLP(self):
# #         return self.data.variableNLP
# #
# #     @property
# #     def variableNL_full(self):
# #         return self.data.variableNL_full
# #
# #     @property
# #     def value(self):
# #         return self.data.value
# #
# #
# # class SignalStatementTree():
# #     dash, line, left, right = '\u2500', '\u2502', '\u251c\u2500\u2500 ', '\u2514\u2500\u2500 '
# #
# #     def __init__(self, root: SignalStatementTreeNode, string: str):
# #         self.__root = root
# #         self.__string = string
# #
# #     @property
# #     def root(self):
# #         return self.__root
# #
# #     @property
# #     def string(self):
# #         return self.__string
# #
# #     def __printNode(self, node: SignalStatementTreeNode, leftChild, levelsOpened):
# #         string = ''.join([SignalStatementTree.line + "\t" if lo else "\t" for lo in levelsOpened[:-1]])
# #         if leftChild:
# #             if node:
# #                 string += SignalStatementTree.left + str(str(node) if bool(node) else "ERROR!!!") + '\n'
# #         else:
# #             if node:
# #                 string += SignalStatementTree.right + str(str(node) if bool(node) else "ERROR!!!") + '\n'
# #
# #         if type(node) is SignalStatementTreeOperatorNode:
# #             string += self.__printNode(node.lchild, True, levelsOpened + [True])
# #             string += self.__printNode(node.rchild, False, levelsOpened + [False])
# #         return string
# #
# #     def __str__(self):
# #         levelsOpened = [False]
# #
# #         node = self.root
# #         string = str(node) + '\n'
# #         if type(node) is SignalStatementTreeOperatorNode:
# #             string += self.__printNode(node.lchild, True, levelsOpened + [True])
# #             string += self.__printNode(node.rchild, False, levelsOpened + [False])
# #         return string
# #
#
# class EvalFunction():
#     """
#     Place holder for functions to be evaluated on the SignalStatementTree.
#     Contains additional functionality to enable creation of more complex templates.
#
#     Attributes
#     ----------
#     func : Function
#         The function to be evaluated on SignalStatementTreeNode objects (altough the implementation does not enforce any class)
#
#     succeeded : bool
#         True if the function was evaluated to True sufficient number of times
#
#     successes : int
#         The number of times when the function was evaluated successfully
#     """
#
#     def __init__(self, func, minSuccesses=1, maxSuccesses=None, failFast=False):
#         """
#         Parameters
#         ----------
#         func : Function
#             The function to be evaluated on SignalStatementTreeNode objects
#
#         minSuccesses : int
#             The minimum number of times the function needs to evaluate to True
#             to declare overall success. Use this in combination with maxSuccesses
#             to have a range of possible nodes that comply to the requirements
#             enforced by the function.
#
#         maxSuccesses : int
#             The maximum number of cases the function can be evaluated to True.
#             After the maxSuccesses has been reached, all following evaluations
#             will fail, even if they would succeed otherwise. Set this value
#             to a very large number to achieve unlimited number of successful
#             evaluations.
#
#         failFast : bool
#             If True, the first case when the function is evaluated to False
#             will result in complete failure of this condition. I.e. all future
#             evaluations will only fail and overall success will be False
#             no matter what. Use this to enforce a condition on all tested nodes.
#             failFast will also cause permanent failure if number of maxSuccesses
#             is exceeded. This can be used to limit the number of nodes in a subtree.
#             E.g., EvalFunction(lambda x: True, maxSuccesses=5, failFast=True)
#             Will generate a function that will make sure that there are at most 5
#             nodes in the subtree.
#         """
#         self.__func = func
#         self.__succeeded = None
#         self.__successes = 0
#         self.__minSuccesses = minSuccesses
#         self.__failFast = failFast
#         self.__valid = True
#         if self.__failFast:
#             self.__maxSuccesses = maxSuccesses if maxSuccesses else np.iinfo(np.int).max
#         else:
#             self.__maxSuccesses = maxSuccesses if maxSuccesses else minSuccesses
#
#     def invalidate(self):
#         self.__valid = False
#
#     def __bool__(self):
#         if self.__succeeded is None:
#             if self.successes >= self.minSuccesses:
#                 self.__succeeded = True
#                 return True
#             else:
#                 return False
#         elif self.__succeeded is True:
#             return True
#         elif self.__succeeded is False:
#             return False
#
#     def __call__(self, value):
#         """Calls the associated function and evaluates the result depending on other conditions as well.
#         If maxSuccesses has been exceeded, the call will fail no matter the actual result
#         (the function won't even be evaluated). It the number of successes (evaluations/calls resulting in True)
#         is smaller than maxSuccesses, the function, associated with this placeholder, is called with the provided value.
#         If the call returns True, number of successes is increased. The success, i.e. the result of the call is returned.
#         If failFast is set to True, any call that will result in return value equal to False will also fail the success
#         of the entire placeholder (EvalFunction instance)
#
#         Parameters
#         ----------
#         value : any
#             Function parameters
#
#         Returns
#         -------
#         bool
#             The success of the call (depending on other parameters)
#         """
#         if self.__succeeded is not None and not self.__succeeded:
#             return False
#         if self.successes >= self.maxSuccesses:
#             if self.failFast and self.func(value):
#                 self.__succeeded = False
#             return False
#         success = self.func(value)
#         if success:
#             self.__successes += 1
#         elif self.failFast:
#             self.__succeeded = False
#         return success
#
#     @property
#     def successes(self):
#         return self.__successes
#
#     @property
#     def succeeded(self):
#         return bool(self)
#
#     @property
#     def func(self):
#         return self.__func
#
#     @property
#     def minSuccesses(self):
#         return self.__minSuccesses
#
#     @property
#     def maxSuccesses(self):
#         return self.__maxSuccesses
#
#     @property
#     def valid(self):
#         return self.__valid
#
#     @property
#     def failFast(self):
#         return self.__failFast
#
#     def __repr__(self):
#         sig = inspect.signature(self.func)
#
#         def displayParam(p, d):
#             return "\t\t\t{}:\n{}".format(p, "\n".join(["\t\t\t\t{}".format(item) for item in d])) if type(d) is list else "\t\t\t{}: {}".format(p, d)
#
#         return "\n".join([
#             ">>> EvalFunction object <<<",
#             "\tNumber of successes = {}".format(self.successes),
#             "\tMinimum number of successes = {}".format(self.minSuccesses),
#             "\tMaximum number of successes = {}".format(self.maxSuccesses),
#             "\tSucceeded = {}".format(self.succeeded),
#             "\tAssociated function:",
#             "\t\tSignature: {}".format(str(sig)),
#             "\t\tParameters: ",
#             "\n".join(["\t\t\t" + param if sig.parameters[param].default is inspect._empty else displayParam(param, sig.parameters[param].default) for param in sig.parameters]),
#             "\t\tSource code: \n" + inspect.getsource(self.func).strip()
#         ])
#
#
# class EvalFunctionList():
#
#         def __init__(self, functionList):
#             """
#             Helper function to generate so called structure list from a list of functions.
#             Changed to generate a list of EvalFunction objects
#             For example:
#             [lambda x: x + 2, lambda x: x * 3] -> [EvalFunction(lambda x: x + 2), EvalFunction(lambda x: x * 3)]
#             """
#             self.evalList = []
#             for item in functionList:
#                 if callable(item):  # just the function; default arguments will be used
#                     evalList.append(EvalFunction(item))
#                 elif type(item) is tuple:  # a list of positional arguments
#                     evalList.append((EvalFunction(*item)))
#                 elif type(item) is dict:  # dictionary with kwargs
#                     evalList.append((EvalFunction(**item)))
#                 else:
#                     raise Exception("Unknown object type encountered while constructing structure list.")
#
# # TODO make iterable and validable
# # TODO incorporate additional methods into SignalStatementTree
#
# # class SignalTemplateTree(SignalStatementTree):
# #
# #     def __init__(self, signalTree: SignalStatementTree):
# #         super().__init__(signalTree.root, signalTree.string)
# #
# #     def __isTwig(self, node: SignalStatementTreeNode):
# #         """
# #         Checks if a node is an operator node but has only leaves (i.e. no consecutive operator nodes == no further branching)
# #         """
# #         if type(node) is SignalStatementTreeOperatorNode:
# #             return (type(node.lchild) is SignalStatementTreeOperandNode) and (type(node.rchild) is SignalStatementTreeOperandNode)
# #         else:
# #             return False
# #
# #     def __recursiveBoolEval(self, node: SignalStatementTreeNode, func):
# #         """
# #         Recursively attempts to evaluate the function on all subsequent nodes.
# #         Returns True if all evaluations were successful. That is, if the function
# #         is holds true for all nodes in the subtree.
# #         """
# #         result = func(node)
# #         if type(node) is SignalStatementTreeOperandNode:
# #             return result
# #         elif type(node) is SignalStatementTreeOperatorNode:
# #             return result and self.__recursiveBoolEval(node.lchild, func) and self.__recursiveBoolEval(node.rchild, func)
# #         else:
# #             # Unknown node type
# #             return False
# #
# #     def __recursiveStructureEval(self, node: SignalStatementTreeNode, structureList: list):
# #         """
# #         Recursively enters nodes and attempts to evalute functions in the structureList list.
# #         If one of the evaluations is successful it then attempts to continue with evaluations
# #         on the node's children.
# #
# #         Old version:
# #         "However, the successfully evaluated function is omitted
# #         from further evaluations. That is, each function must succeed exactly once!"
# #
# #         Newer version:
# #         "The number of successful evaluations depends on the specific conditions
# #         imposed by the EvalFunction object"
# #
# #         Functions are evaluated in a greedy manner. The order of the functions
# #         should therefore be considered - e.g., if some function A can succeed on more nodes
# #         than other function B in the list (i.e., domain of B(x) = True is a subset of A(x) = True),
# #         B should precede A in the list. Otherwise function A might take node that should
# #         normally be captured by B.
# #         """
# #         if len(structureList) == 0:  # either empty list has been provided or some node emptied it (see !success condition below)
# #             return False
# #         success = False
# #         allTrue = True
# #         for i, evalFunc in enumerate(structureList):
# #             if not evalFunc.valid:
# #                 # TODO how to handle allTrue?????
# #                 continue
# #             # Run the eval function on the node.
# #             # The boolean value of the function object is assigned to the allTrue variable to check if the structure is completed
# #             # the internal success of the function is handled by the EvalFunction object
# #             if not success:  # if no function succeeded so far
# #                 # call the function with the current node as a parameter
# #                 success = evalFunc(node)  # if the call returned True, i.e. the node comforms to the condition imposed by the function
# #                 # capture the first successful evaluation and do not call any other function
# #                 # the list has to be iterated through though to check if all function succeeded already
# #             allTrue &= bool(evalFunc)
# #             if success and not allTrue:
# #                 break
# #         if allTrue:  # structureList has been completed
# #             return True
# #         if success:
# #             if type(node) is SignalStatementTreeOperatorNode:
# #                 # call function on both children
# #                 self.__recursiveStructureEval(node.lchild, structureList)
# #                 self.__recursiveStructureEval(node.rchild, structureList)
# #                 if len(structureList) > 0:  # if the list hasn't been emptied yet - the structure check did not fail so far
# #                     return all([bool(e) for e in structureList])  # return the current completion status of the structure check
# #             return False
# #         else:
# #             # if none of the functions in the list succeeded (i.e. the node is not part of the structure), then the structure check failed
# #             # that is, this was not the structure we were looking for...
# #             for evalFunc in structureList:
# #                 evalFunc.invalidate()
# #             return False
# #
# #     def __recursiveSubTreeSearch(self, node: SignalStatementTreeNode, func):
# #         """
# #         Returns all subtrees (returns their respective roots in a list, to be more exact),
# #         for which the function evalutes to True.
# #
# #         Parameters
# #         ----------
# #         node : SignalStatementTreeNode
# #             A node from the tree on which the function should be evaluted
# #
# #         func: Function
# #             Function to be evaluated
# #
# #         Returns
# #         -------
# #         list OR bool:
# #             List of roots of subtrees for which the function was evaluted to True.
# #             Returns just boolean True if this node evaluted to True. Otherwise,
# #             if this node evaluted to False but its child evaluted to True,
# #             a list containing this child will be returned.
# #         """
# #         if func(node):
# #             return node
# #         else:
# #             if type(node) is not SignalStatementTreeOperatorNode:
# #                 return False
# #             else:
# #                 rootList = []
# #                 # Evaluate the function on children and iterate over the results
# #                 for res in [self.__recursiveSubTreeSearch(node.lchild, func), self.__recursiveSubTreeSearch(node.rchild, func)]:
# #                     if not res:
# #                         continue
# #                     if type(res) is list:
# #                         rootList += res
# #                     elif type(res) is SignalStatementTreeNode:
# #                         rootList += [res]
# #
# #                 return rootList
# #
# #     def __genNextTemplate(self):
# #         """
# #         Generates function that should search for templates in the tree.
# #
# #         Raises:
# #             Exception -- Raises an exception if the ID of the requested template
# #             does not exist. That is, function for the template is not specified, yet.
# #         """
# #         with pkg_resources.resource_stream("gherkan", '/'.join(["containers", "templates_dic.yaml"])) as f:
# #             templateDict = yaml.load(f)
# #
# #         for templateID, templateSignature in templateDict.items():
# #             if "SignalTemplate" in templateSignature:  # find signal template that has the property called "SignalTemplate"
# #                 signalTemplateSignature = templateSignature["SignalTemplate"]
# #                 template = None
# #                 # find what kind of template it has (currently only structure is supported)
# #                 if type(signalTemplateSignature) is list or "structure" in signalTemplateSignature:
# #                     if type(signalTemplateSignature) is list:
# #                         templateStructure = signalTemplateSignature
# #                     elif type(signalTemplateSignature) is dict:
# #                         templateStructure = signalTemplateSignature["structure"]
# #                     structureList = []
# #                     for item in templateStructure:
# #                         if type(item) is str:
# #                             structureList.append(lambda nd, ndkind=item: type(nd) is SignalStatementTreeOperatorNode and nd.kind == ndkind)
# #                         elif type(item) is dict:
# #                             lamlist = []
# #                             kind, paramdict = item.popitem()
# #                             for key, value in paramdict.items():
# #                                 lamlist.append(lambda nd, var=key, val=value:  val.lower() in getattr(nd.data, var).lower())
# #                             structureList.append(lambda nd, ndkind=kind, paramCheckList=lamlist: type(nd) is SignalStatementTreeOperandNode and nd.kind == ndkind and all([check(nd) for check in paramCheckList]))
# #                         else:
# #                             raise Exception("Unknown type in signalTemplateSignature.\n{}".format(signalTemplateSignature))
# #
# #                     def template(node: SignalStatementTreeNode, structure=self.__genStructureFromFunctionList(structureList)):
# #                         def searchFunction(node: SignalStatementTreeNode):
# #                             return self.__recursiveStructureEval(node, structure)
# #                         return self.__recursiveSubTreeSearch(node, searchFunction)
# #
# #                 elif "condition" in signalTemplateSignature:
# #                     pass
# #                 elif "script" in signalTemplateSignature:
# #                     pass
# #
# #                 yield((templateID, template))
# #
# #     def parseTemplates(self):
# #         for templateID, template in self.__genNextTemplate():
# #             if not template:
# #                 raise Exception("Error while generating template from SignalTemplateTree!")
# #             tempResult = template(self.root)
# #             if tempResult:
# #                 print(tempResult)
# #                 if type(tempResult) is not list:
# #                     tempList = [tempResult]
# #                 for subtreeRoot in tempList:
# #                     subtreeRoot.templateID = templateID
#
