import unittest
import sys
for path in ['../', './']:
    sys.path.insert(1, path)

# python -m unittest discover
from processing.clustering.clusterer import Clusterer

class TestClusterer(unittest.TestCase):
    clusterer:Clusterer = None

    def setUp(self):
        self.clusterer = Clusterer(epsilon=10, min_points=2)

    def test_create_labels_noneInput_noneOutput(self):
        labels = self.clusterer.create_labels(None)
        self.assertEqual(None, labels)

    def test_create_labels_emptyInput_emptyOutput(self):
        labels = self.clusterer.create_labels([])
        self.assertEqual([], labels)

    def test_create_labels_singleInput_singleCluster(self):
        features = self.clusterer.extract_location_features([self.location(1,2)])
        labels = self.clusterer.create_labels(features)
        self.assertEqual(1, len(labels))

    def test_create_labels_nearInputs_singleCluster(self):
        locations = [self.location(1,2), self.location(2,2)]

        features = self.clusterer.extract_location_features(locations)
        labels = self.clusterer.create_labels(features)

        self.assertEqual(2, len(labels))
        self.assertEqual(labels[0], labels[1])

    def test_create_labels_nearInputs_twoClusters(self):
        locations = [self.location(1,2), self.location(2,2), self.location(20,20)]

        features = self.clusterer.extract_location_features(locations)
        labels = self.clusterer.create_labels(features)

        self.assertEqual(3, len(labels))
        self.assertEqual(labels[0], labels[1])
        self.assertNotEqual(labels[0], labels[2])

    def test_label_locations_NoneLocations_NoException(self):
        self.clusterer.label_dataset(None, [])

    def test_label_locations_NoneLabels_NoException(self):
        self.clusterer.label_dataset([], None)

    def test_label_locations_emptyInput_emptyOutput(self):
        locations = []
        self.clusterer.label_dataset(locations, [])
        self.assertEqual(0, len(locations))

    def test_label_locations_diffInputLengths_ValueError_1(self):
        with self.assertRaises(ValueError):
            self.clusterer.label_dataset([], [1])

    def test_label_locations_diffInputLengths_ValueError_2(self):
        with self.assertRaises(ValueError):
            self.clusterer.label_dataset([self.location(1,2)], [])

    def test_label_locations_multInput_correctlyLabeled(self):
        locations = [self.location(1,2), self.location(2,2), self.location(20,20)]
        labels = [17,2,20]

        self.clusterer.label_dataset(locations, labels)

        self.assertEqual(3, len(locations))
        self.assertHaveLabelsAsNewKey(locations, labels)

    def test_cluster_locations_multInput_correctlyLabeled(self):
        locations = [self.location(1,2), self.location(2,2), self.location(20,20)]
        labels = [0,0,-1]

        res = self.clusterer.cluster_locations(locations)
        
        self.assertHaveLabelsAsNewKey(locations, labels)
        self.assertDictEqual(res, {0: [{'latitude': 1, 'longitude': 2, 'cluster_label': 0}, {'latitude': 2, 'longitude': 2, 'cluster_label': 0}], -1: [{'latitude': 20, 'longitude': 20, 'cluster_label': -1}]})

    def test_cluster_times_multInput_correctlyLabeled(self):
        times = [self.time(123), self.time(128), self.time(223)]
        labels = [0,0,-1]

        res = self.clusterer.cluster_times(times)
        
        self.assertHaveLabelsAsNewKey(times, labels)
        self.assertDictEqual(res, {0: [{'timestamp': 123, 'cluster_label': 0}, {'timestamp': 128, 'cluster_label': 0}], -1: [{'timestamp': 223, 'cluster_label': -1}]})

    def test_cluster_dataset_locationsMultInput_correctlyLabeled(self):
        locations = [self.location(1,2), self.location(2,2), self.location(20,20)]
        labels = [0,0,-1]

        res = self.clusterer.cluster_dataset(locations, ['latitude', 'longitude'])
        
        self.assertHaveLabelsAsNewKey(locations, labels)
        self.assertDictEqual(res, {0: [{'latitude': 1, 'longitude': 2, 'cluster_label': 0}, {'latitude': 2, 'longitude': 2, 'cluster_label': 0}], -1: [{'latitude': 20, 'longitude': 20, 'cluster_label': -1}]})

    def test_cluster_dataset_timesMultInput_correctlyLabeled(self):
        times = [self.time(123), self.time(128), self.time(223)]
        labels = [0,0,-1]

        res = self.clusterer.cluster_dataset(times, ['timestamp'])
        
        self.assertHaveLabelsAsNewKey(times, labels)
        self.assertDictEqual(res, {0: [{'timestamp': 123, 'cluster_label': 0}, {'timestamp': 128, 'cluster_label': 0}], -1: [{'timestamp': 223, 'cluster_label': -1}]})


    # helper methods:
    def location(self, lat, long_) -> dict:
        return {'latitude': lat, 'longitude':long_}

    def time(self, ts) -> dict:
        return {'timestamp': ts}

    def assertHaveLabelsAsNewKey(self, locations, labels):
        for i in range(len(locations)):
            self.assertEqual(labels[i], locations[i]['cluster_label'])

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