try:
    from plugins.TreesAreMemory2.SimpleConstituent import SimpleConstituent
    from plugins.TreesAreMemory2.Feature import Feature
    from plugins.TreesAreMemory2.route_utils import *
    from plugins.TreesAreMemory2.Parser import read_lexicon
except ImportError:
    from SimpleConstituent import SimpleConstituent
    from Feature import Feature
    from route_utils import *
    from Parser import read_lexicon
import time
from collections import Counter, defaultdict
from itertools import chain
from pprint import pprint

debug_parse = False

show_bad_routes = True

use_classic_phrase_structure_exporter = False


def is_positive(feat):
    return feat.sign == '' or feat.sign == '*'


class NumNode:
    def __init__(self, n, const):
        self.n = n or 0
        self.const = const
        self.active = False

    def activate(self):
        self.active = True

    def deactivate(self):
        self.active = False

    def __hash__(self):
        return self.n

    def __str__(self):
        return f'N{self.n}:{self.const}'

    def __repr__(self):
        return f'N{self.n}'

class ConstNode:
    def __init__(self, label, features):
        self.label = label
        self.pos_features = []
        self.neg_features = []
        self.features = features
        for feature in features:
            if is_negative(feature):
                self.neg_features.append(feature)
            else:
                self.pos_features.append(feature)

    def is_arg_for(self, head):
        for neg_feat in head.neg_features:
            for pos_feat in self.pos_features:
                if neg_feat in pos_feat.satisfies:
                    return pos_feat, neg_feat

    def __repr__(self):
        return f'{self.label}{self.features}'


class FeatNode:
    def __init__(self, feat):
        self.id = str(feat)
        self.sign = feat.sign
        self.name = feat.name
        self.value = feat.value
        self.satisfies = []

    def __repr__(self):
        return self.id

# class Route:
#     def __init__(self, route=None):
#         if route:
#             self.ops = list(route.ops)
#             self.free_precedents = route.free_precedents
#         else:
#             self.ops = []
#             self.free_precedents = []
#
#     def append(self, op):
#         self.ops.append(op)


def is_long_distance(num_node):
    for feat in num_node.const.features:
        if feat.name == 'ld':
            return True


class RouteItem:
    def __init__(self, parent, precedent, top, head):
        self.parent = parent
        self.precedent = precedent
        self.top = top
        self.head = head

    def evaluate_route(self, expected_size):
        found = set()
        route = []
        node = self
        while node:
            if not isinstance(node.top, tuple):
                found.add(node.top)
            route.append(node.top)
            node = self.parent
        return list(reversed(route)), len(found) == expected_size


class NodeParser:
    def __init__(self, lexicon, forest=None):
        self.exporter = None
        self.results = []
        self.correct = []
 
       self.lexicon = lexicon or {}
        self.moves = defaultdict(set)
        self.states = {}
        self.total = 0
        self.total_good_r
outes = 0
        self.last_const_id = 0
        self.num_nodes = []
        self.const_nodes = {}
        self.features = {}
        self.routes = []
        self.prepare_network()

    def prepare_network(self):
        self.num_nodes = []
        for key, li_entries in self.lexicon.items():
            for li_entry in li_entries[:1]:
                for li_part in li_entry:
                    fnodes = [self.add_or_get_feat(feat) for feat in li_part.features]
                    self.const_nodes[li_part.label] = ConstNode(li_part.label, fnodes)

    def add_or_get_feat(self, feat):
        f_str = str(feat)
        f_node = self.features.get(f_str, None)
        if f_node:
            return f_node
        f_node = FeatNode(feat)
        if is_negative(feat):
            for pos_node in self.features.values():
                if pos_node.sign == '' and pos_node.name == f_node.name and ((not f_node.value) or f_node.value == pos_node.value):
                    pos_node.satisfies.append(f_node)
        else:
            for neg_node in self.features.values():
                if neg_node.sign == '=' and f_node.name == neg_node.name and ((not neg_node.value) or neg_node.value == f_node.value):
                    f_node.satisfies.append(neg_node)
        return f_node

    def set_forest(self, forest):
        self.exporter.forest = forest

    def get_const_uid(self, const):
        self.last_const_id += 1
        return f'{const.label}{self.last_const_id}'

    # route koostuu tupleista ((Num, Feat), (Num, Feat))? Tässä Feat on redundantti. Num riittäisi. (Num1, Num2)
    # add: (NumT, None)
    # (prec, top, head)
    # comp: (NumP, NumT, NumP)
    # spec: (NumP, NumT, NumT)
    # adj: (NumP, NumT, (NumP, NumT))
    # N1 = Pekka
    # N2 = nukkuu
    # N3 = nukkuu1
    # N4 = kotona

    # [(Pekka, nukkuu, nukkuu), (nukkuu, nukkuu1, nukkuu), (nukkuu1, kotona, nukkuu1)]

    # adjunkti tarkoittaa että kun NumA aktivoituu niin myös NumB aktivoituu ja toisinpäin.

    # Jos teen tietyn operaation, niin voiko siitä kelata taaksepäin mitkä parsinnat mahdollistivat tämän operaation?
    # Muuten kyllä, mutta siirtymät hankaloittaa.
    # Kaikkien edellisten edeltäjien joukko on helppo käsitellä, mutta varhaisemmat edeltäjät ovat joukko listoja jotka voivat olla
    # eri järjestyksissä ja niissä ei saisi hyppiä ohi mahdollisten. Koko asia että miten estää etäisempi operaatio silloin kun läheisempi on tarjolla
    # on vaikea käsitellä.

    def _node_parse(self, sentence):
        tick = 0
        n = 0
        self.route_ends = []
        for word in sentence.split():
            lex_consts = self.lexicon.get(word, [[]])
            lex_const_parts = lex_consts[0]  # cannot handle homonyms that have different lengths in numeration yet
            for lex_const_part in lex_const_parts:
                num_node = NumNode(n, self.const_nodes[lex_const_part.label])
                self.num_nodes.append(num_node)
                num_node.activate()
                for route_item in list(self.route_ends):
                    self.do_operations(route_item, num_node)
                tick += 1
                n += 1
        good_routes = []
        for route_item in self.route_ends:
            route, is_good = route_item.evaluate_route(n)
            if is_good:
                good_routes.append(route)
        print(f'good routes: {len(good_routes)} of {len(routes)}')
        return routes

    def get_from_lexicon(self, word):
        original_consts = self.lexicon.get(word, [[SimpleConstituent(label=word)]])
        consts = []
        for original_complex_const in original_consts:
            complex_const = []
            for const in original_complex_const:
                if isinstance(const, SimpleConstituent):
                    const = const.copy()
                else:
                    const = SimpleConstituent(label=const.label, features=[x.copy() for x in const.features])
                const.uid = self.get_const_uid(const)
                const.head = const
                complex_const.append(const)
            consts.append(complex_const)
        return consts

    def do_operations(self, precedent_ri, top):
        precedent = precedent_ri.node
        print('do operations, top: ', top.const.label, ' precedent: ', precedent.const.label)
        result = {top}
        top_features = top.const.features
        prev_features = precedent.const.features
        found_spec = False
        # Aluksi spec -mahdollisuus, eli viimeisin sana on head ja nostetaan lähin vapaa edeltävä head argumentiksi
        matches = find_matches(prev_features, top_features, '=')
        self.route_ends.remove(precedent_ri)
        if matches:
            print(f'adding spec {precedent.n} ({precedent.const.label}) for head {top.n} ({top.const.label})')
            self.route_ends.append(RouteItem(precedent_ri, precedent, top, top))
 