
import unittest

import quartzbio
from quartzbio import Query, Filter, GenomicFilter


class FilterTest(unittest.TestCase):
    def test_filter_basic(self):
        f = Filter()
        self.assertEqual(repr(f), "<Filter []>", "empty filter")
        self.assertEqual(repr(~f), "<Filter []>", '"not" of empty filter')

        # Because the order in listing keys is arbitrary, we only
        # test with one entry.
        f1 = Filter(price="Free")
        self.assertEqual(repr(f1), "<Filter [('price', 'Free')]>")
        self.assertEqual(
            repr(~~f1), "<Filter [('price', 'Free')]>", '"not" of empty filter'
        )

        a = quartzbio.query.Filter(chr1="3")
        b = quartzbio.query.Filter(chr2="4")
        self.assertEqual(
            repr(a | b),
            "<Filter [{'or': [('chr1', '3'), ('chr2', '4')]}]>",
            '"or" filter',
        )

        f |= a
        self.assertEqual(repr(f), "<Filter [('chr1', '3')]>", "'or' with empty filter")
        self.assertEqual(
            repr(a), "<Filter [('chr1', '3')]>", "prior 'or' doesn't mung filter"
        )

        filters3 = (
            Filter(omim_id=144650) | Filter(omim_id=144600) | Filter(omim_id=145300)
        )
        self.assertEqual(
            repr(filters3),
            "<Filter [{'or': [('omim_id', 144650),"
            + " ('omim_id', 144600), ('omim_id', 145300)]}]>",
        )

    def test_raw_filters(self):
        # Simple filter
        raw_filter = '[["field_a", "value_a"]]'
        f = Filter(raw_filter)
        self.assertEqual(f.filters, [["field_a", "value_a"]])
        f = Query._process_filters([f])
        self.assertEqual(f, [["field_a", "value_a"]])

        # Compounded filter
        raw_filter = '[["field_a", "value_a"], {"not": {"or": ["field_x", "value_x"]}}]'  # noqa
        f = Filter(raw_filter)
        self.assertEqual(
            f.filters,
            [
                {
                    "and": [
                        ["field_a", "value_a"],
                        {"not": {"or": ["field_x", "value_x"]}},
                    ]
                }
            ],
        )
        f = Query._process_filters([f])
        self.assertEqual(
            f,
            [
                {
                    "and": [
                        ["field_a", "value_a"],
                        {"not": {"or": ["field_x", "value_x"]}},
                    ]
                }
            ],
        )

        # Dict-only as list, and not as a list
        raw_filter = '[{"or": [["field_x", "value_x"]]}]'
        f = Filter(raw_filter)
        self.assertEqual(f.filters, [{"or": [["field_x", "value_x"]]}])
        f = Query._process_filters([f])
        self.assertEqual(f, [{"or": [["field_x", "value_x"]]}])

        raw_filter = '{"or": [["field_x", "value_x"]]}'
        f = Filter(raw_filter)
        self.assertEqual(f.filters, [{"or": [["field_x", "value_x"]]}])
        f = Query._process_filters([f])
        self.assertEqual(f, [{"or": [["field_x", "value_x"]]}])

    def test_combined_raw_filters(self):
        # Combined JSON and regular filter
        raw_filter = '[["field_a", "value_a"]]'
        f = Filter(raw_filter, field_x="value_x")
        self.assertEqual(
            f.filters, [{"and": [("field_x", "value_x"), ["field_a", "value_a"]]}]
        )
        f = Query._process_filters([f])
        self.assertEqual(f, [{"and": [("field_x", "value_x"), ["field_a", "value_a"]]}])

        # Combined, separate raw filters
        raw_filter = '[["field_a", "value_a"]]'
        f = Filter(raw_filter) | Filter(raw_filter)
        self.assertEqual(
            f.filters, [{"or": [["field_a", "value_a"], ["field_a", "value_a"]]}]
        )
        f = Query._process_filters([f])
        self.assertEqual(f, [{"or": [["field_a", "value_a"], ["field_a", "value_a"]]}])

        # Combined list of raw filters
        raw_filter = '[["field_a", "value_a"]]'
        f = Filter(raw_filter, raw_filter)
        self.assertEqual(
            f.filters, [{"and": [["field_a", "value_a"], ["field_a", "value_a"]]}]
        )
        f = Query._process_filters([f])
        self.assertEqual(f, [{"and": [["field_a", "value_a"], ["field_a", "value_a"]]}])

    def test_process_filters(self):
        filters = [("omim_id", None)]
        self.assertEqual(repr(Query._process_filters(filters)), repr(filters))


class GenomicFilterTest(unittest.TestCase):
    def test_single_position(self):
        f = GenomicFilter("chr1", 100)
        expected = [
            "<GenomicFilter [{'and': [('genomic_coordinates.start__lte', 100), ('genomic_coordinates.stop__gte', 100), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
            "<GenomicFilter [{'and': [('genomic_coordinates.stop__gte', 100), ('genomic_coordinates.start__lte', 100), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
        ]
        self.assertTrue(repr(f) in expected)

        f = GenomicFilter("chr1", 100, exact=True)
        expected = [
            "<GenomicFilter [{'and': [('genomic_coordinates.stop', 100), ('genomic_coordinates.start', 100), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
            "<GenomicFilter [{'and': [('genomic_coordinates.start', 100), ('genomic_coordinates.stop', 100), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
        ]
        self.assertTrue(repr(f) in expected)  # noqa

    def test_range(self):
        f = GenomicFilter("chr1", 100, 200)
        expected = [
            "<GenomicFilter [{'and': [{'or': [{'and': [('genomic_coordinates.start__lte', 100), ('genomic_coordinates.stop__gte', 200)]}, ('genomic_coordinates.start__range', [100, 200]), ('genomic_coordinates.stop__range', [100, 200])]}, ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
            "<GenomicFilter [{'and': [{'or': [{'and': [('genomic_coordinates.stop__gte', 200), ('genomic_coordinates.start__lte', 100)]}, ('genomic_coordinates.start__range', [100, 200]), ('genomic_coordinates.stop__range', [100, 200])]}, ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
        ]
        if repr(f) not in expected:
            print(repr(f))
        self.assertTrue(repr(f) in expected)

        f = GenomicFilter("chr1", 100, 200, exact=True)
        expected = [
            "<GenomicFilter [{'and': [('genomic_coordinates.stop', 200), ('genomic_coordinates.start', 100), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
            "<GenomicFilter [{'and': [('genomic_coordinates.start', 100), ('genomic_coordinates.stop', 200), ('genomic_coordinates.chromosome', '1')]}]>",  # noqa
        ]
        if repr(f) not in expected:
            print(repr(f))
        self.assertTrue(repr(f) in expected)


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