# -*- coding: utf-8 -*-
import importlib
import io
import sys
import warnings

from lxml import etree

from obspy.io.xseed.blockette import (
    Blockette041, Blockette050, Blockette054, Blockette060)
from obspy.io.xseed.blockette.blockette import BlocketteLengthException
from obspy.io.xseed.fields import SEEDTypeException
import pytest


class TestBlockette():
    """
    Test cases for all blockettes.
    """
    def test_invalid_blockette_length(self):
        """
        A wrong blockette length should raise an exception.
        """
        # create a blockette 054 which is way to long
        b054 = b"0540240A0400300300000020" + (b"+1.58748E-03" * 40)
        blockette = Blockette054(strict=True)
        with pytest.raises(BlocketteLengthException):
            blockette.parse_seed(b054)

    def parse_file(self, blkt_file):
        """
        Parses the test definition file and creates a list of it.
        """
        # Create a new empty list to store all information of the test in it.
        test_examples = []
        # Now read the corresponding file and parse it.
        with io.open(blkt_file, 'rt') as fh:
            # Helper variable to parse the file. Might be a little bit slow but
            # its just for tests.
            cur_stat = None
            for line in fh:
                # Skip unnecessary content.
                if not cur_stat:
                    if line[0:2] != '--':
                        continue
                    else:
                        # If new example number append new list.
                        if len(test_examples) != int(line[2:4]):
                            test_examples.append([])
                        # Append new list to the current example of the list.
                        # The list contains the type of the example.
                        test_examples[-1].append([line[5:].replace('\n', '')])
                        cur_stat = 1
                        continue
                # Filter out any empty/commentary lines still remaining and
                # also set cur_stat to None.
                if line.strip() == '':
                    cur_stat = None
                    continue
                elif line.strip()[0] == '#':
                    cur_stat = None
                    continue
                test_examples[-1][-1].append(line)
        # Simplify and Validate the list.
        self.simplify_and_validate_and_create_dictionary(test_examples)
        # Convert everything to bytes - a lot of the logic in this method
        # here assumes strings to its easier to work with strings and
        # convert to bytes after everything is done.
        for _i in test_examples:
            for key, value in _i.items():
                _i[key] = value.encode()
        return test_examples

    def simplify_and_validate_and_create_dictionary(self, examples):
        """
        Takes an examples list and combines the XSEED strings and validates the
        list.
        Afterwards in creates a list containing a dictionary for each example
        set.
        """
        # Loop over each example:
        for example in examples:
            if len(example) < 2:
                msg = 'At least one SEED and XSEED string for each examples.'
                raise Exception(msg)
            # Exactly one SEED string needed.
            if [item[0] for item in example].count('SEED') != 1:
                msg = 'Only one SEED string per example!'
                raise Exception(msg)
            # Some other stuff is not tested! Please be careful and adhere to
            # the format rules for writing blockette tests.
            for _i in range(len(example)):
                ex_type = example[_i]
                # Loop over each type in the example and differentiate between
                # SEED and not SEED types.
                if ex_type[0] == 'SEED':
                    # Nothing to do here except to remove the line ending.
                    ex_type[1] = ex_type[1][:-1]
                    continue
                # Remove spaces and line endings.
                for _j in range(len(ex_type)):
                    temp_string = ex_type[_j].strip()
                    if temp_string.endswith('\n'):
                        temp_string = temp_string[:-2]
                    ex_type[_j] = temp_string
                # Combine all XSEED strings to one large string. The weird
                # PLACEHOLDER syntax assures that flat fields are parsed
                # correctly because you need to have a space between them.
                ex_type.insert(1, 'PLACEHOLDER'.join(ex_type[1:]))
                ex_type[1] = ex_type[1].replace('>PLACEHOLDER<', '><')
                ex_type[1] = ex_type[1].replace('>PLACEHOLDER', '>')
                ex_type[1] = ex_type[1].replace('PLACEHOLDER<', '<')
                ex_type[1] = ex_type[1].replace('PLACEHOLDER', ' ')
                example[_i] = ex_type[0:2]
        # Now create a dictionary for each example.
        for _i in range(len(examples)):
            ex_dict = {}
            for part in examples[_i]:
                ex_dict[part[0]] = part[1]
            examples[_i] = ex_dict

    def seed_and_xseed_conversion(self, test_examples, blkt_number):
        """
        Takes everything in the prepared list and tests the SEED/XSEED
        conversion for all given formats.
        """
        # Another loop over all examples.
        for example in test_examples:
            # Create several blockette instances
            # One to read from SEED and one for each XSEED version.
            blkt_module = 'obspy.io.xseed.blockette.blockette' + blkt_number
            blkt_class_name = 'Blockette' + blkt_number
            blkt = sys.modules[blkt_module].__dict__[blkt_class_name]

            versions = {}
            # prepare SEED
            versions['SEED'] = {}
            versions['SEED']['Blkt'] = blkt()
            versions['SEED']['data'] = example['SEED']
            versions['SEED']['Blkt'].parse_seed(example['SEED'])

            # prepare XSEED
            for key, data in example.items():
                if 'XSEED' not in key:
                    continue
                if key == 'XSEED':
                    key = ''
                versions[key] = {}
                versions[key]['version'] = key[6:]
                versions[key]['Blkt'] = blkt(xseed_version=key[6:])
                versions[key]['Blkt'].parse_xml(etree.fromstring(data))
                versions[key]['data'] = data
            # loop over all combinations
            errmsg = 'Blockette %s - Getting %s from %s\n%s\n!=\n%s'
            for key1, blkt1 in versions.items():
                # conversion to SEED
                seed = blkt1['Blkt'].get_seed()
                assert seed == versions['SEED']['data'], \
                    errmsg % (blkt_number, 'SEED', key1,
                              seed, versions['SEED']['data'])
                for key2, blkt2 in versions.items():
                    if key2 == 'SEED':
                        continue
                    xseed = etree.tostring(blkt1['Blkt'].get_xml(
                        xseed_version=blkt2['version']))
                    assert xseed == versions[key2]['data'], \
                        errmsg % (blkt_number, 'XSEED', key2,
                                  xseed, blkt2['data'])

    def test_all_blockettes(self, datapath):
        """
        Tests all Blockettes.
        """
        # Loop over all files in the blockette-tests directory.
        test_example_count = 0
        for blkt_file in (datapath / 'blockette-tests').glob('blockette*.txt'):
            # Get blockette number.
            blkt_number = str(blkt_file)[-7:-4]
            # Check whether the blockette class can be loaded.
            try:
                __import__('obspy.io.xseed.blockette.blockette' + blkt_number)
            except Exception:
                msg = 'Failed to import blockette', blkt_number
                raise ImportError(msg)
            # Parse the file.
            test_examples = self.parse_file(blkt_file)
            # Check how many examples were actually tested.
            test_example_count += len(test_examples)
            # The last step is to actually test the conversions to and from
            # SEED/XSEED for every example in every direction.
            self.seed_and_xseed_conversion(test_examples, blkt_number)
        assert test_example_count > 0

    def test_all_blockettes_get_resp(self, datapath):
        """
        Tests get_resp() for all blockettes that have that method.
        """
        tested_blockettes = 0
        # Loop over all files in the blockette-tests directory.
        for blkt_file in (datapath / 'blockette-tests').glob('blockette*.txt'):

            # Import the corresponding blockette.
            blkt_number = str(blkt_file)[-7:-4]
            blkt = importlib.import_module(
                'obspy.io.xseed.blockette.blockette%s' % blkt_number)
            blkt = getattr(blkt, "Blockette%s" % blkt_number)

            # Skip if it has no get_resp() method.
            if not hasattr(blkt, "get_resp"):
                continue
            tested_blockettes += 1

            # Some of the blockettes requires access to other blockettes.
            # They don't have it for this simplistic test and thus raise
            # warnings which therefore have to be caught.
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                test_examples = self.parse_file(blkt_file)
                for ex in test_examples:
                    b = blkt()
                    b.parse_seed(ex["SEED"])
                    if blkt_number != "060":
                        r = b.get_resp("AA", "BB", [])
                    else:
                        # Blockette 60 is special as it also needs access to
                        # other blockettes. Create some dummy data here.
                        b.stages = [[0]]
                        blkt41 = Blockette041()
                        blkt41.response_lookup_key = 0
                        blkt41.symmetry_code = 0
                        blkt41.signal_in_units = "m/s"
                        blkt41.signal_out_units = "m/s"
                        blkt41.number_of_factors = 0
                        r = b.get_resp("AA", "BB", [blkt41])
                    # For now only check that it contains something and that it
                    # returns bytes.
                    assert len(r) > 0
                    assert isinstance(r, bytes)

        assert tested_blockettes > 0

    def test_blockette60_has_blockette_id(self):
        """
        Blockette 60 overwrites the init method. Check that the parent class is
        called.
        """
        blkt = Blockette060()
        assert blkt.blockette_id == "060"
        assert blkt.id == 60

    def test_issue701(self):
        """
        Testing an oversized site name.
        """

        b050_orig = "0500168ANTF +43.564000  +7.123000  +54.0   6  0" + \
            "Antibes - 06004 - Alpes-Maritimes - Provence-Alpes-Côte d'" + \
            "Azur - France~ 363210102003,211,11:18:00~2004,146,08:52:00~NFR"
        b050_cut = "0500166ANTF +43.564000  +7.123000  +54.00006000" + \
            "Antibes - 06004 - Alpes-Maritimes - Provence-Alpes-Côte d'A~" + \
            "0363210102003,211,11:18:00.0000~2004,146,08:52:00.0000~NFR"
        # reading should work but without issues
        blockette = Blockette050()
        blockette.parse_seed(b050_orig.encode('utf-8'))
        assert len(blockette.site_name.encode('utf-8')) == 72
        with warnings.catch_warnings(record=True):
            warnings.simplefilter('error', UserWarning)
            with pytest.raises(UserWarning):
                blockette.get_seed()
            # Now ignore the warnings and test the default values.
            warnings.simplefilter('ignore', UserWarning)
            # writing should cut to 60 chars
            out = blockette.get_seed()
            # utf-8 only needed for PY2
            assert out.decode('utf-8') == b050_cut
            # reading it again should have cut length
            blockette = Blockette050()
            blockette.parse_seed(out)
            assert len(blockette.site_name.encode('utf-8')) == 60
        # writing with strict=True will raise
        blockette = Blockette050(strict=True)
        blockette.parse_seed(b050_orig.encode('utf-8'))
        with pytest.raises(SEEDTypeException):
            blockette.get_seed()

    def test_equality_and_unequality(self):
        """
        Tests the rich (un)equality dunder methods.
        """
        a = Blockette050()
        b = Blockette050()
        c = Blockette050()
        d = Blockette060()

        a.test = 1
        b.test = 1
        c.test = 2
        d.test = 1

        assert a == a
        assert a == b
        assert a != c
        assert a != d
        # Revert order
        assert a == a
        assert b == a
        assert c != a
        assert d != a
        # Negations
        assert not (a != a)
        assert not (a != b)
        assert not (a == c)
        assert not (a == d)
