import unittest
from aminer.analysis.HistogramAnalysis import LinearNumericBinDefinition, ModuloTimeBinDefinition, HistogramData, HistogramAnalysis, \
    PathDependentHistogramAnalysis
from aminer.input.LogAtom import LogAtom
from aminer.parsing.ParserMatch import ParserMatch
import time
from datetime import datetime
from aminer.parsing.MatchElement import MatchElement
from unit.TestBase import TestBase


class HistogramAnalysisTest(TestBase):
    """Unittests for the HistogramAnalysis."""

    __expected_string_histogram_analysis = '%s Histogram report\n%s: "%s" (%d lines)\n  Histogram report from %s till %s\n  %s\n\n'
    __expected_string_path_dependent_histogram_analysis = '%s Histogram report\n%s: "%s" (%d lines)\n  Path histogram report from %s ' \
                                                          'till %s\n%s\n\n'

    match_crontab = 'match/crontab'
    datetime_format_string = '%Y-%m-%d %H:%M:%S'

    def test1linear_numeric_bin_definition_get_bin_names(self):
        """This test case aims to test the functionality of the LinearNumericBinDefinition's get_bin_names method."""
        linear_numeric_bin_definition = LinearNumericBinDefinition(0, 1, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin_names(), [
            '...-0]', '[0-1]', '[1-2]', '[2-3]', '[3-4]', '[4-5]', '[5-6]', '[6-7]', '[7-8]', '[8-9]', '[9-10]', '[10-...'])

        linear_numeric_bin_definition = LinearNumericBinDefinition(0, 2, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin_names(), [
            '...-0]', '[0-2]', '[2-4]', '[4-6]', '[6-8]', '[8-10]', '[10-12]', '[12-14]', '[14-16]', '[16-18]', '[18-20]', '[20-...'])

    def test2linear_numeric_bin_definition_get_bin(self):
        """This test case aims to test the functionality of the LinearNumericBinDefinition's get_bin method."""
        linear_numeric_bin_definition = LinearNumericBinDefinition(0, 1, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin(2), 3)

        linear_numeric_bin_definition = LinearNumericBinDefinition(1, 1, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin(2), 2)

        linear_numeric_bin_definition = LinearNumericBinDefinition(2, 1, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin(2), 1)

        linear_numeric_bin_definition = LinearNumericBinDefinition(0, 4, 10, True)
        self.assertEqual(linear_numeric_bin_definition.get_bin(2), 1)

    def test3linear_numeric_bin_definition_get_bin_p_values(self):
        """This test case aims to test the functionality of the LinearNumericBinDefinition's get_bin_p_values method."""
        linear_numeric_bin_definition = LinearNumericBinDefinition(0, 1, 10, True)
        self.assertNotEqual(linear_numeric_bin_definition.get_bin_p_value(2, 10, [2, 2]), None,
                            'Probably the scipy module could not be loaded. Please check your installation.')

    def test4_modulo_time_bin_definition_get_bin(self):
        """This test case aims to test the functionality of the ModuloTimeBinDefinition's getBin method."""
        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        self.assertEqual(modulo_time_bin_definition.get_bin(57599), 15)
        self.assertEqual(modulo_time_bin_definition.get_bin(57600), 16)
        self.assertEqual(modulo_time_bin_definition.get_bin(61199), 16)
        self.assertEqual(modulo_time_bin_definition.get_bin(61200), 17)

    def test5_histogram_data_add_value(self):
        """This test case aims to test the addition of Values to HistogramData class."""
        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData("crontab", modulo_time_bin_definition)
        histogram_data.add_value(57600)
        self.assertEqual(histogram_data.bin_data[16], 1)
        self.assertEqual(histogram_data.total_elements, 1)
        self.assertEqual(histogram_data.binned_elements, 0)

        histogram_data.add_value(61200)
        self.assertEqual(histogram_data.bin_data[16], 1)
        self.assertEqual(histogram_data.bin_data[17], 1)
        self.assertEqual(histogram_data.total_elements, 2)
        self.assertEqual(histogram_data.binned_elements, 0)

        histogram_data.add_value(61500)
        self.assertEqual(histogram_data.bin_data[16], 1)
        self.assertEqual(histogram_data.bin_data[17], 2)
        self.assertEqual(histogram_data.total_elements, 3)
        self.assertEqual(histogram_data.binned_elements, 0)

        histogram_data.add_value(100000)
        # 100000%86400 = 13600 -> 3
        self.assertEqual(histogram_data.bin_data[3], 1)
        self.assertEqual(histogram_data.bin_data[16], 1)
        self.assertEqual(histogram_data.bin_data[17], 2)
        self.assertEqual(histogram_data.total_elements, 4)
        self.assertEqual(histogram_data.binned_elements, 0)

    def test6_histogram_data_reset(self):
        """This test case aims to test resetting the Values of the HistogramData class."""
        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData("crontab", modulo_time_bin_definition)
        histogram_data.add_value(57600)
        histogram_data.add_value(61200)
        histogram_data.reset()
        self.assertEqual(histogram_data.total_elements, 0)
        self.assertEqual(histogram_data.binned_elements, 0)
        for item in histogram_data.bin_data:
            self.assertEqual(item, 0)

    def test7_histogram_data_clone(self):
        """This test case aims to test cloning a HistogramData object."""
        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData("crontab", modulo_time_bin_definition)
        histogram_data.add_value(57600)
        histogram_data.add_value(61200)
        clone = histogram_data.clone()

        self.assertEqual(clone.bin_data[16], 1)
        self.assertEqual(clone.bin_data[17], 1)
        self.assertEqual(clone.total_elements, 2)
        self.assertEqual(clone.binned_elements, 0)

        clone.add_value(1)
        self.assertEqual(clone.bin_data[0], 1)
        self.assertEqual(histogram_data.bin_data[0], 0)

    def test8_histogram_data_to_string(self):
        """This test case aims to test the functionality of the HistogramData's to_string method."""
        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData("crontab", modulo_time_bin_definition)
        histogram_data.add_value(57600)

        histogram_data.add_value(61200)

        clone = histogram_data.clone()

        self.assertEqual(clone.bin_data[16], 1)
        self.assertEqual(clone.bin_data[17], 1)
        self.assertEqual(clone.total_elements, 2)
        self.assertEqual(clone.binned_elements, 0)

        clone.add_value(1)
        self.assertEqual(clone.bin_data[0], 1)
        self.assertEqual(histogram_data.bin_data[0], 0)
        self.assertEqual(clone.to_string(''), 'Property "crontab" (3 elements):\n* [0-1]: 1 (ratio = 3.33e-01, p = 1.20e-01)\n* [16-17]: '
                                              '1 (ratio = 3.33e-01, p = 1.20e-01)\n* [17-18]: 1 (ratio = 3.33e-01, p = 1.20e-01)')

    def test9HistogramAnalysisReceiveAtomNoReport(self):
        """This test case aims to test the functionality of the HistogramAnalysis's receive_atom method, when NO report is expected."""
        description = "Test9HistogramAnalysis"
        start_time = 57600
        end_time = 662600
        diff = 30000

        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData(self.match_crontab, modulo_time_bin_definition)
        histogram_analysis = HistogramAnalysis(self.aminer_config, [(histogram_data.property_path, modulo_time_bin_definition)], 604800,
                                               [self.stream_printer_event_handler], True, 'Default')
        self.analysis_context.register_component(histogram_analysis, description)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)

        t = time.time()
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), '')

        # resetting the outputStream
        start_time = start_time + 3600
        end_time = end_time + 3600
        self.reset_output_stream()
        t = t + diff

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)

        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), '')

    def test10_histogram_analysis_receive_atom_report_expected(self):
        """This test case aims to test the functionality of the HistogramAnalysis's receive_atom method, when A report is expected."""
        description = "Test10HistogramAnalysis"
        start_time = 57600
        end_time = 662600
        diff = 605000

        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData(self.match_crontab, modulo_time_bin_definition)
        histogram_analysis = HistogramAnalysis(self.aminer_config, [(histogram_data.property_path, modulo_time_bin_definition)], 604800,
                                               [self.stream_printer_event_handler], True, 'Default')
        self.analysis_context.register_component(histogram_analysis, description)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)

        t = time.time()
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)

        self.assertEqual(self.output_stream.getvalue(), self.__expected_string_histogram_analysis % (
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string), histogram_analysis.__class__.__name__, description, 2,
            datetime.fromtimestamp(t).strftime(self.datetime_format_string),
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string),
            'Property "match/crontab" (2 elements):\n  * [16-17]: 2 (ratio = 1.00e+00, p = 1.74e-03)'))

        # resetting the outputStream
        start_time = start_time + 3600
        end_time = end_time + 3600
        self.reset_output_stream()
        t = t + diff

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, histogram_analysis)
        histogram_analysis.receive_atom(log_atom)

        self.assertEqual(self.output_stream.getvalue(), self.__expected_string_histogram_analysis % (
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string), histogram_analysis.__class__.__name__, description, 2,
            datetime.fromtimestamp(t).strftime(self.datetime_format_string),
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string),
            'Property "match/crontab" (2 elements):\n  * [16-17]: 1 (ratio = 5.00e-01, p = 8.16e-02)\n  * [17-18]: 1 '
            '(ratio = 5.00e-01, p = 8.16e-02)'))

    def test11_path_dependent_histogram_analysis_no_report(self):
        """
        This test case aims to test the functionality of the PathDependantHistogramAnalysis.receive_atom method.
        No report is expected.
        """
        description = "Test11HistogramAnalysis"
        start_time = 57600
        end_time = 662600
        diff = 30000

        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData(self.match_crontab, modulo_time_bin_definition)
        path_dependent_histogram_analysis = PathDependentHistogramAnalysis(
            self.aminer_config, histogram_data.property_path, modulo_time_bin_definition, 604800,
            [self.stream_printer_event_handler], True, 'Default')
        self.analysis_context.register_component(path_dependent_histogram_analysis, description)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)

        t = time.time()
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), '')

        # resetting the outputStream
        start_time = start_time + 3600
        end_time = end_time + 3600
        self.reset_output_stream()
        t = t + diff

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), '')

    def test12_path_dependent_histogram_analysis_report_expected(self):
        """
        This test case aims to test the functionality of the PathDependantHistogramAnalysis.receiveAtom method.
        A report is expected.
        """
        description = "Test12HistogramAnalysis"
        start_time = 57600
        end_time = 662600
        diff = 605000

        modulo_time_bin_definition = ModuloTimeBinDefinition(86400, 3600, 0, 1, 24, False)
        histogram_data = HistogramData(self.match_crontab, modulo_time_bin_definition)
        path_dependent_histogram_analysis = PathDependentHistogramAnalysis(
            self.aminer_config, histogram_data.property_path, modulo_time_bin_definition, 604800,
            [self.stream_printer_event_handler], True, 'Default')
        self.analysis_context.register_component(path_dependent_histogram_analysis, description)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)

        t = time.time()
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), self.__expected_string_path_dependent_histogram_analysis % (
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string), path_dependent_histogram_analysis.__class__.__name__,
            description, 2, datetime.fromtimestamp(t).strftime(self.datetime_format_string),
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string),
            'Path values "match/crontab":\nExample: 662600\n  Property "match/crontab" (2 elements):\n  * [16-17]: 2 '
            '(ratio = 1.00e+00, p = 1.74e-03)'))

        # resetting the outputStream
        start_time = start_time + 3600
        end_time = end_time + 3600
        self.output_stream.seek(0)
        self.output_stream.truncate(0)
        t = t + diff

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        match_element = MatchElement(self.match_crontab, str(start_time).encode(), start_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        histogram_data.add_value(start_time)
        histogram_data.add_value(end_time)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)

        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        match_element = MatchElement(self.match_crontab, str(end_time).encode(), end_time, None)
        log_atom = LogAtom(histogram_data.bin_data, ParserMatch(match_element), t + diff, path_dependent_histogram_analysis)
        path_dependent_histogram_analysis.receive_atom(log_atom)
        self.assertEqual(self.output_stream.getvalue(), self.__expected_string_path_dependent_histogram_analysis % (
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string), path_dependent_histogram_analysis.__class__.__name__,
            description, 3, datetime.fromtimestamp(t).strftime(self.datetime_format_string),
            datetime.fromtimestamp(t + diff).strftime(self.datetime_format_string),
            'Path values "match/crontab":\nExample: 666200\n  Property "match/crontab" (3 elements):\n  * [16-17]: 1 '
            '(ratio = 3.33e-01, p = 1.20e-01)\n  * [17-18]: 2 (ratio = 6.67e-01, p = 5.06e-03)'))


if __name__ == "__main__":
    unittest.main()
