import unittest

import pydmrs.matching.match_evaluation
from examples import examples_dmrs
from pydmrs._exceptions import PydmrsTypeError
from pydmrs.components import InstanceSortinfo, RealPred
from pydmrs.core import Link, DictDmrs, Node
from pydmrs.matching import general_matching


class TestMatch(unittest.TestCase):
    def setUp(self):
        self.match = general_matching.Match([(2, 3), (4, 2)], [(Link(4, 5, 'RSTR', 'H'),
                                                                Link(1, 2, 'RSTR', 'H'))])

    def test_Match_init(self):
        self.assertEqual(general_matching.Match().nodeid_pairs, [])
        self.assertEqual(general_matching.Match().link_pairs, [])
        self.assertCountEqual(self.match.nodeid_pairs, [(2, 3), (4, 2)])
        self.assertCountEqual(self.match.link_pairs, [(Link(4, 5, 'RSTR', 'H'),
                                                       Link(1, 2, 'RSTR', 'H'))])

    def test_Match_len(self):
        self.assertEqual(len(self.match), 3)
        self.assertEqual(len(general_matching.Match()), 0)

    def test_Match_add(self):
        self.assertIsNone(self.match.add(general_matching.Match()))
        self.assertCountEqual(self.match.nodeid_pairs, [(2, 3), (4, 2)])
        self.assertCountEqual(self.match.link_pairs, [(Link(4, 5, 'RSTR', 'H'),
                                                       Link(1, 2, 'RSTR', 'H'))])

        incompatible_match = general_matching.Match([(1, 2), (8, 1)], [(Link(1, 8, 'RSTR', 'H'),
                                                                        Link(1, 2, 'RSTR', 'H'))])
        self.match.add(incompatible_match)
        self.assertCountEqual(self.match.nodeid_pairs, [(2, 3), (4, 2), (8, 1)])
        self.assertCountEqual(self.match.link_pairs, [(Link(4, 5, 'RSTR', 'H'),
                                                       Link(1, 2, 'RSTR', 'H'))])

        compatible_match = general_matching.Match([(1, 5), (3, 4)], [(Link(1, 3, 'ARG1', 'NEQ'),
                                                                      Link(1, 5, 'ARG2', 'NEQ'))])
        self.match.add(compatible_match)
        self.assertCountEqual(self.match.nodeid_pairs, [(2, 3), (4, 2), (1, 5), (8, 1), (3, 4)])
        self.assertCountEqual(self.match.link_pairs, [(Link(4, 5, 'RSTR', 'H'),
                                                       Link(1, 2, 'RSTR', 'H')),
                                                      (Link(1, 3, 'ARG1', 'NEQ'),
                                                       Link(1, 5, 'ARG2', 'NEQ'))])


class TestGeneralMatching(unittest.TestCase):
    def setUp(self):
        self.large_dmrs = examples_dmrs.the_dog_chases_the_cat_and_the_cat_chases_the_mouse()
        self.small_dmrs = examples_dmrs.the_dog_chases_the_cat()
        self.cat_dmrs = examples_dmrs.the_cat()
        self.reverse_dmrs = examples_dmrs.the_cat_chases_the_dog()

    def test_find_best_matches(self):
        # Match "the cat" onto "the dog chases the cat" (exact fit)
        matches = general_matching.find_best_matches(self.cat_dmrs, self.small_dmrs)

        self.assertEqual(len(matches), 1)
        self.assertCountEqual(matches[0].nodeid_pairs, [(2, 5), (1, 4)])
        self.assertCountEqual(matches[0].link_pairs,
                              [(Link(1, 2, 'RSTR', 'H'), Link(4, 5, 'RSTR', 'H'))])

        # Match "the dog chases the cat" onto "the cat chases the dog" (inexact fit)
        matches = general_matching.find_best_matches(self.small_dmrs, self.reverse_dmrs)
        self.assertEqual(len(matches), 1)
        self.assertCountEqual(matches[0].nodeid_pairs, [(5, 2), (4, 1), (3, 3), (2, 5), (1, 4)])
        self.assertCountEqual(matches[0].link_pairs,
                              [(Link(4, 5, 'RSTR', 'H'), Link(1, 2, 'RSTR', 'H')),
                               (Link(1, 2, 'RSTR', 'H'), Link(4, 5, 'RSTR', 'H'))])

        # No match found
        matches = general_matching.find_best_matches(examples_dmrs.the_mouse(), self.reverse_dmrs)
        self.assertIsNone(matches)

        # More than one match found.
        matches = general_matching.find_best_matches(self.cat_dmrs, self.large_dmrs)
        self.assertEqual(len(matches), 2)
        self.assertCountEqual(matches[0].nodeid_pairs, [(2, 5), (1, 4)])
        self.assertCountEqual(matches[0].link_pairs,
                              [(Link(1, 2, 'RSTR', 'H'), Link(4, 5, 'RSTR', 'H'))])
        self.assertCountEqual(matches[1].nodeid_pairs, [(2, 8), (1, 7)])
        self.assertCountEqual(matches[1].link_pairs,
                              [(Link(1, 2, 'RSTR', 'H'), Link(7, 8, 'RSTR', 'H'))])

    def test_get_matched_subgraph(self):
        match = general_matching.find_best_matches(self.cat_dmrs, self.small_dmrs)[0]
        subgraph = general_matching.get_matched_subgraph(self.small_dmrs, match)
        expected = DictDmrs(nodes=[Node(nodeid=4, pred=RealPred('the', 'q')),
                                   Node(nodeid=5, pred=RealPred('cat', 'n', '1'),
                                        sortinfo=InstanceSortinfo(pers='3', num='sg', ind='+'))],
                            links=[Link(start=4, end=5, rargname='RSTR', post='H')])
        self.assertListEqual(subgraph.nodes, expected.nodes)
        self.assertListEqual(subgraph.links, expected.links)

    def test_get_recall_fscore(self):
        exact_matches = general_matching.find_best_matches(self.cat_dmrs, self.small_dmrs)
        inexact_matches = general_matching.find_best_matches(self.small_dmrs, self.reverse_dmrs)
        # Exact
        self.assertEqual(pydmrs.matching.match_evaluation.get_recall(exact_matches[0], self.cat_dmrs), 1)
        self.assertEqual(pydmrs.matching.match_evaluation.get_fscore(exact_matches[0], self.cat_dmrs), 1)
        # Inexact
        self.assertAlmostEqual(pydmrs.matching.match_evaluation.get_recall(inexact_matches[0], self.small_dmrs),
                               7 / 9)
        self.assertAlmostEqual(pydmrs.matching.match_evaluation.get_fscore(inexact_matches[0], self.small_dmrs),
                               0.875)

        # List of matches instead of Match.
        with self.assertRaises(PydmrsTypeError):
            pydmrs.matching.match_evaluation.get_recall(exact_matches, self.cat_dmrs)
        with self.assertRaises(PydmrsTypeError):
            pydmrs.matching.match_evaluation.get_fscore(exact_matches, self.cat_dmrs)

    def test_get_missing_elements(self):
        match = general_matching.find_best_matches(examples_dmrs.the_dog_chases_the_mouse(),
                                                   self.small_dmrs)[0]
        missing = pydmrs.matching.match_evaluation.get_missing_elements(match,
                                                                        examples_dmrs.the_dog_chases_the_mouse())
        self.assertCountEqual(missing, [4, 5, Link(3, 5, 'ARG2', 'NEQ'), Link(4, 5, 'RSTR', 'H')])
