import pytest

from sacrerouge.common.testing.metric_test_cases import ReferenceBasedMetricTestCase
from sacrerouge.common.testing.util import sacrerouge_command_exists
from sacrerouge.data import MetricsDict
from sacrerouge.metrics import PythonRouge, Rouge
from sacrerouge.metrics.python_rouge import shorten_summary


class TestPythonRouge(ReferenceBasedMetricTestCase):
    def test_normalize_and_tokenize(self):
        """
        Tests to ensure the python version of Rouge correctly implements the
        perl implementation. The expected tokens were generated by editing the
        perl script to print the tokens during processing. The hyphens were added
        to the original because the perl implementation uses them to separate words.
        """
        rouge = PythonRouge(use_porter_stemmer=False, remove_stopwords=False)
        original = 'Xu Wenli, Wang Youchai, and Qin Yongmin, leading dissidents and '\
            'prominent members of the China-Democracy-Party, were found guilty of subversion ' \
            'and sentenced to 13, 11, and 12 years in prison, respectively.'
        expected = 'xu wenli wang youchai and qin yongmin leading dissidents and ' \
            'prominent members of the china democracy party were found guilty of subversion ' \
            'and sentenced to 13 11 and 12 years in prison respectively'.split()
        actual = rouge.normalize_and_tokenize_sentence(original)
        assert expected == actual

        rouge = PythonRouge(use_porter_stemmer=True, remove_stopwords=False)
        expected = 'xu wenli wang youchai and qin yongmin lead dissid and promin '\
            'member of the china democraci parti be find guilti of subvers and sentenc '\
            'to 13 11 and 12 year in prison respect'.split()
        actual = rouge.normalize_and_tokenize_sentence(original)
        assert expected == actual

        rouge = PythonRouge(use_porter_stemmer=False, remove_stopwords=True)
        expected = 'xu wenli wang youchai qin yongmin leading dissidents prominent '\
            'members china democracy party found guilty subversion sentenced 13 11 '\
            '12 years prison'.split()
        actual = rouge.normalize_and_tokenize_sentence(original)
        assert expected == actual

        rouge = PythonRouge(use_porter_stemmer=True, remove_stopwords=True)
        expected = 'xu wenli wang youchai qin yongmin lead dissid promin member china '\
            'democraci parti find guilti subvers sentenc 13 11 12 year prison'.split()
        actual = rouge.normalize_and_tokenize_sentence(original)
        assert expected == actual

    def test_shorten_summary(self):
        original = [
            'This is an example sentence, which is first.',
            'This is the second one.'
            'Finally, the third.'
        ]

        expected = [
            'This is an example'
        ]
        actual = shorten_summary(original, max_words=4)
        assert expected == actual

        expected = [
            'This is an example sentence, which is first.',
        ]
        actual = shorten_summary(original, max_words=8)
        assert expected == actual

        expected = [
            'This is an example sentence, which is first.',
            'This is'
        ]
        actual = shorten_summary(original, max_words=10)
        assert expected == actual

        actual = shorten_summary(original, max_words=16)
        assert original == actual
        actual = shorten_summary(original, max_words=100)
        assert original == actual

        expected = [
            'This is an'
        ]
        actual = shorten_summary(original, max_bytes=10)
        actual = shorten_summary(original, max_bytes=11)

        expected = [
            'This is an example sentence, which is first.',
        ]
        actual = shorten_summary(original, max_bytes=44)
        assert expected == actual

        expected = [
            'This is an example sentence, which is first.',
            'T'
        ]
        actual = shorten_summary(original, max_bytes=45)
        assert expected == actual

        actual = shorten_summary(original, max_bytes=87)
        assert original == actual
        actual = shorten_summary(original, max_bytes=88)
        assert original == actual

    def test_preprocessing(self):
        rouge = PythonRouge(max_words=5, remove_stopwords=True)
        original = [
            'Xu Wenli, Wang Youchai, and Qin Yongmin, leading dissidents and'
        ]
        expected = ['xu wenli wang youchai'.split()]
        actual = rouge.preprocess_summary(original)
        assert expected == actual

    def assert_same_as_rouge(self, python: MetricsDict, perl: MetricsDict):
        assert perl['rouge-1']['precision'] == pytest.approx(python['python-rouge-1']['precision'], abs=1e-2)
        assert perl['rouge-1']['recall'] == pytest.approx( python['python-rouge-1']['recall'], abs=1e-2)
        assert perl['rouge-1']['f1'] == pytest.approx( python['python-rouge-1']['f1'], abs=1e-2)
        assert perl['rouge-2']['precision'] == pytest.approx( python['python-rouge-2']['precision'], abs=1e-2)
        assert perl['rouge-2']['recall'] == pytest.approx( python['python-rouge-2']['recall'], abs=1e-2)
        assert perl['rouge-2']['f1'] == pytest.approx( python['python-rouge-2']['f1'], abs=1e-2)
        assert perl['rouge-l']['precision'] == pytest.approx( python['python-rouge-l']['precision'], abs=1e-2)
        assert perl['rouge-l']['recall'] == pytest.approx( python['python-rouge-l']['recall'], abs=1e-2)
        assert perl['rouge-l']['f1'] == pytest.approx( python['python-rouge-l']['f1'], abs=1e-2)

    def test_python_rouge_correctness(self):
        summary = [
            "His tenacity holds despite the summary trials and harsh punishments for Xu, Wang Youcai and Qin Yongmin prominent party principals from the provinces who were sentenced to 11 and 12 years and despite threatening signs from the ruling Communist Party.",
            "The dissidents Xu Wenli, who was sentenced Monday to 13 years in prison, Wang Youcai, who received an 11-year sentence, and Qin Yongming, who was reported to have received 12 years were charged with subversion.",
            "As police moved against Xu's friends, labor rights campaigner Liu Nianchun was taken from a prison camp outside Beijing and, with his wife and daughter, was put on a plane to Canada and then New York, his first taste of freedom in more than 3 1/2 years."
        ]
        gold_summaries = [
            [
                "While China plans to sign the International Covenant on Civil and Political Rights at the U.N., it is still harassing and arresting human rights campaigners.",
                "Three prominent leaders of the China Democratic Party were put to trial and sentenced to 11-, 12- and 13-year prison terms.",
                "Germany and the U.S. condemned the arrests.",
                "A labor rights activist was released and exiled to the U.S. to blunt any opposition to Communist rule.",
                "U.S. policy to encourage trade and diplomacy in hope of democratic reforms evidences failure, but the U.S. is continuing its policy of encouragement.",
                "Friends of jailed dissidents state that they will continue to campaign for change."
            ],
            [
                "The US trade-driven policy of expanded ties encouraging Chinese democracy is questioned.",
                "China signed rights treaties and dissidents used new laws to set up China Democracy Party, but China violates the new laws by persecuting dissidents.",
                "It regularly frees activists from prison then exiles them so they lose local influence.",
                "It arrested an activist trying to register a rights monitoring group.",
                "CP leader Jiang's hard-line speech and publicity for activists sentenced to long prison terms signals a renewed Chinese crackdown.",
                "A rights activist expected to be sacrificed in the cause of democracy.",
                "Germany called China's sentencing of dissidents unacceptable."
            ],
            [
                "After 2 years of wooing the West by signing international accords, apparently relaxing controls on free speech, and releasing and exiling three dissenters, China cracked down against political dissent in Dec 1998.",
                "Leaders of the China Democracy Party (CDP) were arrested and three were sentenced to jail terms of 11 to 13 years.",
                "The West, including the US, UK and Germany, reacted strongly.",
                "Clinton's China policy of engagement was questioned.",
                "China's Jiang Zemin stated economic reform is not a prelude to democracy and vowed to crush any challenges to the Communist Party or \"social stability\".",
                "The CDP vowed to keep working, as more leaders awaited arrest."
            ],
            [
                "Xu Wenli, Wang Youchai, and Qin Yongmin, leading dissidents and prominent members of the China Democracy Party, were found guilty of subversion and sentenced to 13, 11, and 12 years in prison, respectively.",
                "Soon after the sentencing, China's president, Jiang Zemin, delivered speeches in which he asserted that Western political system must not be adopted and vowed to crush challenges to Communist Party rule.",
                "The harsh sentences and speeches signal a crackdown on dissent, but Zha Jianguo, another Democracy Party leader, says he will continue to push for change.",
                "Western nations condemned the sentences as violations of U.N. rights treaties signed by China."
            ]
        ]

        compute_rouge_l = True
        use_porter_stemmer = False
        remove_stopwords = False
        rouge = Rouge(max_ngram=2,
                      compute_rouge_l=compute_rouge_l,
                      use_porter_stemmer=use_porter_stemmer,
                      remove_stopwords=remove_stopwords)
        python_rouge = PythonRouge(compute_rouge_l=compute_rouge_l,
                                   use_porter_stemmer=use_porter_stemmer,
                                   remove_stopwords=remove_stopwords)
        expected_metrics = rouge.score(summary, gold_summaries)
        actual_metrics = python_rouge.score(summary, gold_summaries)
        self.assert_same_as_rouge(actual_metrics, expected_metrics)

        use_porter_stemmer = False
        remove_stopwords = True
        rouge = Rouge(max_ngram=2,
                      compute_rouge_l=compute_rouge_l,
                      use_porter_stemmer=use_porter_stemmer,
                      remove_stopwords=remove_stopwords)
        python_rouge = PythonRouge(compute_rouge_l=compute_rouge_l,
                                   use_porter_stemmer=use_porter_stemmer,
                                   remove_stopwords=remove_stopwords)
        expected_metrics = rouge.score(summary, gold_summaries)
        actual_metrics = python_rouge.score(summary, gold_summaries)
        self.assert_same_as_rouge(actual_metrics, expected_metrics)

        use_porter_stemmer = True
        remove_stopwords = False
        rouge = Rouge(max_ngram=2,
                      compute_rouge_l=compute_rouge_l,
                      use_porter_stemmer=use_porter_stemmer,
                      remove_stopwords=remove_stopwords)
        python_rouge = PythonRouge(compute_rouge_l=compute_rouge_l,
                                   use_porter_stemmer=use_porter_stemmer,
                                   remove_stopwords=remove_stopwords)
        expected_metrics = rouge.score(summary, gold_summaries)
        actual_metrics = python_rouge.score(summary, gold_summaries)
        self.assert_same_as_rouge(actual_metrics, expected_metrics)

        use_porter_stemmer = True
        remove_stopwords = True
        rouge = Rouge(max_ngram=2,
                      compute_rouge_l=compute_rouge_l,
                      use_porter_stemmer=use_porter_stemmer,
                      remove_stopwords=remove_stopwords)
        python_rouge = PythonRouge(compute_rouge_l=compute_rouge_l,
                                   use_porter_stemmer=use_porter_stemmer,
                                   remove_stopwords=remove_stopwords)
        expected_metrics = rouge.score(summary, gold_summaries)
        actual_metrics = python_rouge.score(summary, gold_summaries)
        self.assert_same_as_rouge(actual_metrics, expected_metrics)

    def test_python_rouge_multiling(self):
        use_porter_stemmer = True
        remove_stopwords = False
        compute_rouge_l = True
        max_words = 100

        rouge = Rouge(max_ngram=2,
                      use_porter_stemmer=use_porter_stemmer,
                      remove_stopwords=remove_stopwords,
                      max_words=max_words,
                      compute_rouge_l=compute_rouge_l)
        python_rouge = PythonRouge(use_porter_stemmer=use_porter_stemmer,
                                   remove_stopwords=remove_stopwords,
                                   max_words=max_words,
                                   compute_rouge_l=compute_rouge_l)
        expected_metrics, _ = rouge.evaluate(self.summaries, self.references_list)
        actual_metrics, _ = python_rouge.evaluate(self.summaries, self.references_list)
        self.assert_same_as_rouge(actual_metrics, expected_metrics)

    def test_python_rouge_order_invariant(self):
        metric = PythonRouge()
        self.assert_order_invariant(metric)

    def test_command_exists(self):
        assert sacrerouge_command_exists(['python-rouge'])

    def test_setup_command_exists(self):
        assert sacrerouge_command_exists(['setup-metric', 'python-rouge'])