import os
import unittest

from obspy import UTCDateTime
from obspy.io.xseed import Parser
from obspy.io.xseed.utils import SEEDParserException

from pylot.core.util.dataprocessing import Metadata
from tests.utils import HidePrints


class TestMetadata(unittest.TestCase):

    def setUp(self):
        self.station_id = 'BW.WETR..HH'
        self.time = UTCDateTime('2012-08-01')
        metadata_folder = os.path.join('test_data', 'dless_multiple_files', 'metadata1')
        self.m = Metadata(metadata_folder)

    def test_get_coordinates_sucess(self):
        expected = {'Z': {u'elevation': 607.0, u'longitude': 12.87571, u'local_depth': 0.0, u'azimuth': 0.0,
                          u'latitude': 49.14502, u'dip': -90.0},
                    'E': {u'azimuth': 90.0, u'dip': 0.0, u'elevation': 607.0, u'latitude': 49.14502,
                          u'local_depth': 0.0, u'longitude': 12.87571},
                    'N': {u'azimuth': 0.0, u'dip': 0.0, u'elevation': 607.0, u'latitude': 49.14502, u'local_depth': 0.0,
                          u'longitude': 12.87571}
                    }
        result = {}
        for channel in ('Z', 'N', 'E'):
            with HidePrints():
                coords = self.m.get_coordinates(self.station_id + channel, time=self.time)
            result[channel] = coords
            self.assertDictEqual(result[channel], expected[channel])

    def test_get_coordinates_sucess_no_time(self):
        expected = {'Z': {u'elevation': 607.0, u'longitude': 12.87571, u'local_depth': 0.0, u'azimuth': 0.0,
                          u'latitude': 49.14502, u'dip': -90.0},
                    'E': {u'azimuth': 90.0, u'dip': 0.0, u'elevation': 607.0, u'latitude': 49.14502,
                          u'local_depth': 0.0, u'longitude': 12.87571},
                    'N': {u'azimuth': 0.0, u'dip': 0.0, u'elevation': 607.0, u'latitude': 49.14502, u'local_depth': 0.0,
                          u'longitude': 12.87571}
                    }
        result = {}
        for channel in ('Z', 'N', 'E'):
            with HidePrints():
                coords = self.m.get_coordinates(self.station_id + channel)
            result[channel] = coords
            self.assertDictEqual(result[channel], expected[channel])


class TestMetadataAdding(unittest.TestCase):
    """Tests if adding files and directories to a metadata object works."""

    def setUp(self):
        self.station_id = 'BW.WETR..HH'
        self.metadata_folders = (os.path.join('test_data', 'dless_multiple_files', 'metadata1'),
                                 os.path.join('test_data', 'dless_multiple_files', 'metadata2'))
        self.m = Metadata()

    def test_add_inventory_folder(self):
        """Test if add_inventory adds the folder to the list of inventories"""
        self.m.add_inventory(self.metadata_folders[0])
        # adding an inventory folder should append it to the list of inventories
        self.assertDictEqual({}, self.m.inventory_files)
        self.assertDictEqual({}, self.m.seed_ids)
        self.assertEqual([self.metadata_folders[0]], self.m.inventories)

    def test_add_inventory_file(self):
        """Test if add_inventory_file adds the folder containing the file to the list of inventories and
        if the files is added to inventory_files"""
        fpath = os.path.join(self.metadata_folders[0], 'DATALESS.BW.WETR..HHZ')
        self.m.add_inventory_file(fpath)
        # adding an inventory file should append its folder to the list of inventories and the file to the
        self.assertEqual([os.path.join(self.metadata_folders[0], 'DATALESS.BW.WETR..HHZ')],
                         self.m.inventory_files.keys())  # does the filename exist in inventory files?
        self.assertEqual(['data', 'invtype'], self.m.inventory_files[os.path.join(self.metadata_folders[0],
                                                                                  'DATALESS.BW.WETR..HHZ')].keys())  # is the required information attacht to the filename?
        self.assertDictEqual({}, self.m.seed_ids)
        self.assertEqual([self.metadata_folders[0]], self.m.inventories)

    def test_add_inventory_invalid_path(self):
        """Test if adding an inventory that is not an existing directory fails with an exception"""
        with self.assertRaises(Exception):
            self.m.add_inventory('InvalidDirName')
        self.assertEqual([], self.m.inventories)  # inventory list should still be empty

    def test_add_inventory_file_invalid_path(self):
        """Test if adding a inventory file with an invalid path fails with an exception"""
        with self.assertRaises(Exception):
            self.m.add_inventory_file('/invalid/file/name')
        self.assertEqual([], self.m.inventories)  # inventory list should still be empty


class TestMetadataRemoval(unittest.TestCase):
    """Tests if removing files and directories to a metadata object works."""

    def setUp(self):
        self.station_id = 'BW.WETR..HH'
        self.metadata_folders = (os.path.join('test_data', 'dless_multiple_files', 'metadata1'),
                                 os.path.join('test_data', 'dless_multiple_files', 'metadata2'))
        self.m = Metadata()

    def test_remove_all_inventories(self):
        """Test if function remove_inventory cleans the Metadata object """
        # add multiple inventories
        for folder in self.metadata_folders:
            self.m.add_inventory(folder)
        self.m.remove_all_inventories()
        self.isEmpty(self.m)

    def test_remove_inventory(self):
        """Test if remove_inventory removes single inventories"""
        # add multiple inventories
        for folder in self.metadata_folders:
            self.m.add_inventory(folder)
        self.m.remove_inventory(self.metadata_folders[0])
        self.assertNotIn(self.metadata_folders[0], self.m.inventories)
        self.m.remove_inventory(self.metadata_folders[1])
        self.assertNotIn(self.metadata_folders[1], self.m.inventories)
        self.isEmpty(self.m)

    def test_remove_inventory_not_in_inventory_list(self):
        """Test if remove_inventory does not modify the metadata instance if the given inventory to remove does not
        exist in the instance."""
        # add multiple inventories
        self.m.add_inventory(self.metadata_folders[0])
        with HidePrints():
            self.m.remove_inventory('metadata_not_existing')
        self.assertIn(self.metadata_folders[0], self.m.inventories)

    def isEmpty(self, metadata):
        """Asserts if the given metadata object is empty"""
        self.assertDictEqual({}, metadata.inventory_files)
        self.assertDictEqual({}, metadata.seed_ids)
        self.assertEqual([], metadata.inventories)


class TestMetadata_read_single_file(unittest.TestCase):

    def setUp(self):
        self.station_id = 'BW.WETR..HHZ'
        self.metadata_folders = (os.path.join('test_data', 'dless_multiple_files', 'metadata1'),
                                 os.path.join('test_data', 'dless_multiple_files', 'metadata2'))
        self.metadata_paths = []
        self.m = Metadata()

    def test_read_single_file(self):
        """Test if reading a single file works"""
        fname = os.path.join(self.metadata_folders[0], 'DATALESS.' + self.station_id)
        with HidePrints():
            res = self.m.read_single_file(fname)
        # method should return true if file is successfully read
        self.assertTrue(res)
        # list of inventories (folders) should be empty
        self.assertEqual([], self.m.inventories)
        # list of inventory files should contain the added file
        self.assertIn(fname, self.m.inventory_files.keys())
        self.assertEqual({}, self.m.seed_ids)

    def test_read_single_file_invalid_path(self):
        """Test if reading from a non existing file fails. The filename should not be
        added to the metadata object"""
        fname = os.path.join("this", "path", "doesnt", "exist")
        with HidePrints():
            res = self.m.read_single_file(fname)
        # method should return None if file reading fails
        self.assertIsNone(res)
        # list of inventories (folders) should be empty
        self.assertEqual([], self.m.inventories)
        # list of inventory files should not contain the added file
        self.assertNotIn(fname, self.m.inventory_files.keys())
        self.assertEqual({}, self.m.seed_ids)

    def test_read_single_file_multiple_times(self):
        """Test if reading a file twice doesnt add it twice to the metadata object"""
        fname = os.path.join(self.metadata_folders[0], 'DATALESS.' + self.station_id)
        with HidePrints():
            res1 = self.m.read_single_file(fname)
            res2 = self.m.read_single_file(fname)
        self.assertTrue(res1)
        self.assertIsNone(res2)
        self.assertItemsEqual([fname], self.m.inventory_files.keys())


class TestMetadataMultipleTime(unittest.TestCase):
    """Test if stations with multiple metadata entries in a single file are handled correctly.
    The user must specify the time where he wants to get metadata.

    The station ROTT changed has metadata available at multiple times
    LE.ROTT..HNE | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-01-08 - 2015-03-19 | Lat: 49.1, Lng: 8.1
	LE.ROTT..HNE | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-03-19 -  | Lat: 49.1, Lng: 8.1
	LE.ROTT..HNN | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-01-08 - 2015-03-19 | Lat: 49.1, Lng: 8.1
	LE.ROTT..HNN | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-03-19 -  | Lat: 49.1, Lng: 8.1
	LE.ROTT..HNZ | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-01-08 - 2015-03-19 | Lat: 49.1, Lng: 8.1
	LE.ROTT..HNZ | 200.00 Hz | Titan 4g-EDR-209, Very Low gain, 200 sps | 2015-03-19 -  | Lat: 49.1, Lng: 8.1
    """

    def setUp(self):
        self.seed_id = 'LE.ROTT..HN'
        path = os.path.dirname(__file__)  # gets path to currently running script
        metadata = os.path.join('test_data', 'dless_multiple_times',
                                'MAGS2_LE_ROTT.dless')  # specific subfolder of test data
        metadata_path = os.path.join(path, metadata)
        self.m = Metadata(metadata_path)
        self.p = Parser(metadata_path)

    def test_get_metadata_works_without_datetime(self):
        """Test if get_metadata works if multiple metadata entries are available but no time is
        specified."""
        for channel in ('Z', 'N', 'E'):
            with HidePrints():
                md = self.m.get_metadata(self.seed_id + channel)
            self.assertDictEqual(md['data'].get_inventory(), self.p.get_inventory())

    def test_get_metadata_works_with_first_datetime(self):
        """Test if get_metadata works if multiple metadata entries are available and the older time is specified."""
        t = UTCDateTime('2015-02-08')
        for channel in ('Z', 'N', 'E'):
            with HidePrints():
                md = self.m.get_metadata(self.seed_id + channel, t)
            self.assertDictEqual(md['data'].get_inventory(), self.p.get_inventory())

    def test_get_metadata_fails_when_time_before_starttime(self):
        """Tests if get_metadata returns None when given a data that is before the start date
        of the metadata"""
        with HidePrints():
            md = self.m.get_metadata(self.seed_id, UTCDateTime('1960-07-20'))
        self.assertIs(md, None)

    def test_get_metadata_invalid_seed_id(self):
        """Tes if get metadata returns none when asked for a seed id that does not exist"""
        with HidePrints():
            res = self.m.get_metadata("this.doesnt..exist")
        self.assertIsNone(res)


class TestMetadataMultipleEntries(unittest.TestCase):
    """
    The station KB.TMO07 has changed instruments multiple times.
    Networks:
	KB (KB network)
Stations:
	KB.TMO07 (Karlsruhe GPI)
Channels:
	KB.TMO07.00.BHE | 50.00 Hz | Streckeisen KABBA-STS-2  | 2004-12-06 - 2005-04-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Streckeisen KABBA-STS-2  | 2005-04-18 - 2006-07-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-10-10 - 2006-11-14 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-11-24 - 2007-01-12 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-01-18 - 2007-03-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-10-25 - 2007-11-21 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHE | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-11-21 - 2008-01-17 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Streckeisen KABBA-STS-2  | 2004-12-06 - 2005-04-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Streckeisen KABBA-STS-2  | 2005-04-18 - 2006-07-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-10-10 - 2006-11-14 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-11-24 - 2007-01-12 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-01-18 - 2007-03-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-10-25 - 2007-11-21 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHN | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-11-21 - 2008-01-17 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Streckeisen KABBA-STS-2  | 2004-12-06 - 2005-04-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Streckeisen KABBA-STS-2  | 2005-04-18 - 2006-07-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-10-10 - 2006-11-14 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2006-11-24 - 2007-01-12 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-01-18 - 2007-03-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-10-25 - 2007-11-21 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.BHZ | 50.00 Hz | Lennartz KABBA-LE-3D/5   | 2007-11-21 - 2008-01-17 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-01-12 - 2007-01-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-10-10 - 2007-10-25 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Streckeisen KABBA-STS-2 | 2008-07-11 - 2008-12-05 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Streckeisen KABBA-STS-2 | 2009-05-12 - 2010-02-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-02-15 - 2010-04-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Lennartz KABBA-LE-3D/1  | 2010-04-07 - 2010-08-03 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-08-05 - 2010-12-20 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-20 - 2010-12-22 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-22 - 2011-04-02 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 200.00 Hz | Streckeisen KABBA-STS-2 | 2011-04-15 - 2012-05-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHE | 200.00 Hz | Streckeisen KABBA-STS-2 | 2012-05-07 -            | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-01-12 - 2007-01-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-10-10 - 2007-10-25 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Streckeisen KABBA-STS-2 | 2008-07-11 - 2008-12-05 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Streckeisen KABBA-STS-2 | 2009-05-12 - 2010-02-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-02-15 - 2010-04-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Lennartz KABBA-LE-3D/1  | 2010-04-07 - 2010-08-03 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-08-05 - 2010-12-20 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-20 - 2010-12-22 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-22 - 2011-04-02 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 200.00 Hz | Streckeisen KABBA-STS-2 | 2011-04-15 - 2012-05-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHN | 200.00 Hz | Streckeisen KABBA-STS-2 | 2012-05-07 -            | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-01-12 - 2007-01-18 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Lennartz KABBA-LE-3D/5  | 2007-10-10 - 2007-10-25 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Streckeisen KABBA-STS-2 | 2008-07-11 - 2008-12-05 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Streckeisen KABBA-STS-2 | 2009-05-12 - 2010-02-15 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-02-15 - 2010-04-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Lennartz KABBA-LE-3D/1  | 2010-04-07 - 2010-08-03 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-08-05 - 2010-12-20 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 100.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-20 - 2010-12-22 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 200.00 Hz | Streckeisen KABBA-STS-2 | 2010-12-22 - 2011-04-02 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 200.00 Hz | Streckeisen KABBA-STS-2 | 2011-04-15 - 2012-05-07 | Lat: 49.0, Lng: 8.4
	KB.TMO07.00.HHZ | 200.00 Hz | Streckeisen KABBA-STS-2 | 2012-05-07 -            | Lat: 49.0, Lng: 8.4
    """

    def setUp(self):
        self.seed_id = 'KB.TMO07.00.HHZ'
        path = os.path.dirname(__file__)  # gets path to currently running script
        metadata = os.path.join('test_data', 'dless_multiple_instruments',
                                'MAGS2_KB_TMO07.dless')  # specific subfolder of test data
        metadata_path = os.path.join(path, metadata)
        self.m = Metadata(metadata_path)
        self.p = Parser(metadata_path)

    def test_get_paz_current_time(self):
        """Test if getting the paz from the metadata object with the current time works"""
        t = UTCDateTime()
        with HidePrints():
            pazm = self.m.get_paz(self.seed_id, t)
        pazp = self.p.get_paz(self.seed_id, t)
        self.assertEqual(pazm, pazp)

    def test_get_paz_past(self):
        """Test if getting paz from metadata object with a time in the past works"""
        t = UTCDateTime('2007-01-13')
        with HidePrints():
            pazm = self.m.get_paz(self.seed_id, t)
        pazp = self.p.get_paz(self.seed_id, t)
        self.assertEqual(pazm, pazp)

    def test_get_paz_time_not_exisiting(self):
        """Test if getting paz from metadata at a time where there is no metadata
        available fails correctly"""
        with self.assertRaises(SEEDParserException):
            with HidePrints():
                self.m.get_paz(self.seed_id, UTCDateTime('1990-1-1'))

    def test_get_paz_seed_id_not_existing(self):
        """Test if getting paz from a non existing seed id returns None as expected."""
        with HidePrints():
            res = self.m.get_paz('This.doesnt..exist', UTCDateTime)
        self.assertIsNone(res)