#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
seiscomp.event test suite.

:author:
    EOST (École et Observatoire des Sciences de la Terre)
:copyright:
    The ObsPy Development Team (devs@obspy.org)
:license:
    GNU Lesser General Public License, Version 3
    (https://www.gnu.org/copyleft/lesser.html)
"""
import filecmp
import os
import re

from lxml import etree

from obspy import read_events
from obspy.core.util.base import NamedTemporaryFile
from obspy.io.quakeml.core import _read_quakeml
from obspy.io.quakeml.core import _validate as _validate_quakeml
from obspy.io.seiscomp.core import validate as validate_scml
from obspy.io.seiscomp.event import (
    SCHEMA_VERSION, NEW_SCHEMA_VERSION, _read_scml
)
import pytest


class TestEvent():
    """
    Test suite for obspy.io.seiscomp.event
    """
    @pytest.fixture(autouse=True, scope="function")
    def setup(self, root, datapath):
        # directory where the test files are located
        self.io_directory = root / 'io'
        self.quakeml_path = self.io_directory / 'quakeml' / 'tests' / 'data'
        self.path = datapath
        self.write_xslt_filename = (
            self.io_directory / 'seiscomp' / 'data' /
            'quakeml_1.2__sc3ml_0.10.xsl')
        self.schema_pattern = re.compile(
            r'http://geofon\.gfz(?:-potsdam)?\.de/ns/seiscomp3?-schema/[\d\.]+'
        )
        self.version_pattern = re.compile(r'version="[\d\.]+"')

    def change_reading_version(self, filename, version):
        """
        Change the namespace and version number of a SCML
        file-like object and return an etree document.
        """
        with open(filename, 'r') as f:
            data = f.read()

            # Remove encoding declaration otherwise lxml will not be
            # able to read the file.
            data = data.replace('<?xml version="1.0" encoding="UTF-8"?>\n', '')

            new_ns = "http://geofon.gfz.de/ns/seiscomp-schema/"
            old_ns = "http://geofon.gfz-potsdam.de/ns/seiscomp3-schema/"

            if version in NEW_SCHEMA_VERSION:
                sch_ver = f"{new_ns}{version}"
                data = data.replace(old_ns, new_ns)
            else:
                sch_ver = f"{old_ns}{version}"
                data = data.replace(new_ns, old_ns)

            data = self.schema_pattern.sub(sch_ver, data)
            data = self.version_pattern.sub(
                f'version="{version}"', data)

        return etree.fromstring(data)

    def change_writing_version(self, data, version):
        """
        Change the namespace and version number of a
        string and return the new string.
        """
        # Remove the XML header else the version will be changed
        data = data.replace('<?xml version="1.0" encoding="UTF-8"?>\n', '')

        new_ns = "http://geofon.gfz.de/ns/seiscomp-schema/"
        old_ns = "http://geofon.gfz-potsdam.de/ns/seiscomp3-schema/"

        if version in NEW_SCHEMA_VERSION:
            sch_ver = new_ns + version
        else:
            sch_ver = old_ns + version

        data = self.schema_pattern.sub(sch_ver, data)
        data = self.version_pattern.sub(
            f'version="{version}"', data)

        # Put back the XML header
        return '<?xml version="1.0" encoding="UTF-8"?>\n%s' % data

    def cmp_read_xslt_file(self, scml_file, quakeml_file, validate=True):
        """
        Check if the QuakeML file generated with the XSLT file is the
        same than the one in the data folder. Every available SCML
        versions are tested except those for which the file is not
        valid. Test file namespaces are briefly hacked to match
        variable incoming SCML.
        """
        for version in SCHEMA_VERSION:
            # versions 0.9 and below still have incorrect km - m
            # ellipsoid conversion but are kept for legacy reasons.
            # accordingly they no longer pass _origin tests
            if version in ['0.7', '0.8', '0.9']:
                continue

            if version in NEW_SCHEMA_VERSION:
                xslfile = 'scml_%s__quakeml_1.2.xsl' % version
            else:
                xslfile = 'sc3ml_%s__quakeml_1.2.xsl' % version

            read_xslt_filename = os.path.join(
                self.io_directory, 'seiscomp', 'data',
                xslfile,
            )

            transform = etree.XSLT(etree.parse(read_xslt_filename))
            filename = self.path / scml_file
            scml_doc = self.change_reading_version(filename, version)

            if not validate_scml(scml_doc):
                continue

            quakeml_doc = transform(scml_doc)

            with NamedTemporaryFile() as tf:
                tf.write(quakeml_doc)
                if validate:
                    assert _validate_quakeml(tf.name)

                # Read the expected file and force matching namespace
                filepath_cmp = self.path / quakeml_file
                with open(filepath_cmp, 'r') as f:
                    expected = f.read()

                new_ns = "gfz.de"
                old_ns = "gfz-potsdam.de"

                if version in NEW_SCHEMA_VERSION:
                    expected = expected.replace(old_ns, new_ns)
                else:
                    expected = expected.replace(new_ns, old_ns)

                # Read generated content
                tf.seek(0)
                generated = tf.read().decode('utf-8')
                print("DEBUG:: ", version)

                assert generated == expected

    def cmp_write_xslt_file(self, quakeml_file, scml_file, target_version,
                            validate=True, path=None):
        """
        Check if the SCML file generated with the XSLT file is the
        same than the one in the data folder.

        The `target_version` parameter allows to change the version of
        the generated file to match the version of the reference file.
        """
        if path is None:
            path = self.path

        transform = etree.XSLT(etree.parse(self.write_xslt_filename))
        filename = os.path.join(path, quakeml_file)
        quakeml_doc = etree.parse(filename)
        scml_doc = transform(quakeml_doc)

        scml_doc_string = self.change_writing_version(
            bytes(scml_doc).decode(encoding=quakeml_doc.docinfo.encoding),
            target_version,
        )

        scml_doc_bytes = scml_doc_string.encode(
            encoding=quakeml_doc.docinfo.encoding
        )

        with NamedTemporaryFile() as tf:
            tf.write(scml_doc_bytes)

            if validate:
                assert validate_scml(tf.name, target_version)
            filepath_cmp = self.path / scml_file
            assert filecmp.cmp(filepath_cmp, tf.name)

    def test_scml_versions(self, testdata):
        """
        Test multiple schema versions
        """
        for version in ['0.9', '0.10', '0.11',
                        '0.12', '0.13', '0.14']:
            filename = testdata['version%s' % version]
            read_events(filename)

        filename = testdata['version0.6']
        expected_message = re.escape(
            "Can't read SCML version 0.6, ObsPy can handle "
            "versions [0.7, 0.8, 0.9, 0.10, "
            "0.11, 0.12, 0.13, 0.14].")
        with pytest.raises(ValueError, match=expected_message):
            read_events(filename)

        filename = testdata['version0.15']
        expected_message = re.escape(
            "Can't read SCML version 0.15, ObsPy can handle "
            "versions [0.7, 0.8, 0.9, 0.10, "
            "0.11, 0.12, 0.13, 0.14].")
        with pytest.raises(ValueError, match=expected_message):
            read_events(filename)

    def test_read_xslt_event(self):
        self.cmp_read_xslt_file('quakeml_1.2_event.sc3ml',
                                'quakeml_1.2_event_res.xml')

    def test_read_xslt_origin(self):
        self.cmp_read_xslt_file('quakeml_1.2_origin.sc3ml',
                                'quakeml_1.2_origin_res.xml')

    def test_read_xslt_magnitude(self):
        self.cmp_read_xslt_file('quakeml_1.2_magnitude.sc3ml',
                                'quakeml_1.2_magnitude.xml')

    def test_read_xslt_station_magnitude_contribution(self):
        self.cmp_read_xslt_file(
            'quakeml_1.2_stationmagnitudecontributions.sc3ml',
            'quakeml_1.2_stationmagnitudecontributions.xml')

    def test_read_xslt_station_magnitude(self):
        self.cmp_read_xslt_file('quakeml_1.2_stationmagnitude.sc3ml',
                                'quakeml_1.2_stationmagnitude.xml')

    def test_read_xslt_data_used_in_moment_tensor(self):
        self.cmp_read_xslt_file('quakeml_1.2_data_used.sc3ml',
                                'quakeml_1.2_data_used.xml')

    def test_read_xslt_arrival(self):
        self.cmp_read_xslt_file('quakeml_1.2_arrival.sc3ml',
                                'quakeml_1.2_arrival.xml')

    def test_read_xslt_pick(self):
        self.cmp_read_xslt_file('quakeml_1.2_pick.sc3ml',
                                'quakeml_1.2_pick.xml')

    def test_read_xslt_focalmechanism(self):
        self.cmp_read_xslt_file('quakeml_1.2_focalmechanism.sc3ml',
                                'quakeml_1.2_focalmechanism_res.xml')

    def test_read_xslt_amplitude(self):
        """ See issue #2273 """
        self.cmp_read_xslt_file('quakeml_1.2_amplitude.sc3ml',
                                'quakeml_1.2_amplitude_res.xml')

    def test_read_xslt_iris_events(self):
        # Magnitude lost during conversion
        self.cmp_read_xslt_file('iris_events.sc3ml', 'iris_events_res.xml')

    def test_read_xslt_example(self):
        self.cmp_read_xslt_file('qml-example-1.2-RC3.sc3ml',
                                'qml-example-1.2-RC3.xml')

    def test_read_example(self, testdata):
        filename = testdata['qml-example-1.2-RC3.sc3ml']
        catalog = _read_scml(filename)

        assert len(catalog.events) == 1
        assert len(catalog.events[0].origins) == 1

    def test_read_id_prefix(self, testdata):
        filename = \
            testdata['qml-example-1.2-RC3_wrong_id.sc3ml']
        catalog = _read_scml(filename, id_prefix='quakeml:obspy.org/')

        assert len(catalog.events) == 1
        event = catalog.events[0]
        assert event.resource_id == 'quakeml:obspy.org/test_event_id'

        assert len(event.origins) == 1
        origin = event.origins[0]
        assert origin.resource_id == 'quakeml:obspy.org/test_origin_id'
        assert origin.reference_system_id == \
            'quakeml:obspy.org/test_reference_system_id'

        assert len(event.amplitudes) == 1
        amplitude = event.amplitudes[0]
        assert amplitude.resource_id == 'quakeml:obspy.org/test_amplitude_id'

        assert len(event.magnitudes) == 1
        magnitude = event.magnitudes[0]
        assert magnitude.resource_id == 'quakeml:obspy.org/test_magnitude_id'

        assert len(event.station_magnitudes) == 1
        station_magnitude = event.station_magnitudes[0]
        assert station_magnitude.resource_id == \
            'quakeml:obspy.org/test_station_magnitude_id'

    def test_read_string(self, testdata):
        """
        Test reading a SCML string/unicode object via read_events.
        """
        filename = testdata['qml-example-1.2-RC3.sc3ml']
        with open(filename, 'rb') as fp:
            data = fp.read()

            catalog = read_events(data)
            assert len(catalog) == 1

    def test_read_quakeml(self, testdata):
        """
        Test reading a QuakeML file via read_events.
        """
        filename = testdata['qml-example-1.2-RC3.xml']
        expected_message = "Not a SCML compatible file or string."
        with pytest.raises(ValueError, match=expected_message):
            read_events(filename, format='SCML')

    def test_read_scml_fields(self, testdata):
        """
        Test SCML fields which are not in the QuakeML 1.2.
        """
        filename = testdata['sc3ml_fields.sc3ml']
        catalog = read_events(filename, format='SCML')

        # No longer present
        # assert 'reading' in catalog.extra

        origin = catalog[0].origins[0]
        assert 'pdf' in origin.time_errors.extra
        assert 'modificationTime' in origin.creation_info.extra

        comment = origin.comments[0]
        assert 'start' in comment.extra
        assert 'end' in comment.extra

        station_magnitude = catalog[0].station_magnitudes[0]
        assert 'passedQC' in station_magnitude.extra

        tensor = catalog[0].focal_mechanisms[0].moment_tensor
        assert 'status' in tensor.extra
        assert 'cmtName' in tensor.extra
        assert 'cmtVersion' in tensor.extra
        assert 'phaseSetting' in tensor.extra
        assert 'stationMomentTensorContribution' in tensor.extra

    def test_write_xslt_event(self):
        self.cmp_write_xslt_file('quakeml_1.2_event.xml',
                                 'quakeml_1.2_event.sc3ml',
                                 '0.10',
                                 path=self.quakeml_path)

    def test_write_xslt_origin(self):
        self.cmp_write_xslt_file('quakeml_1.2_origin.xml',
                                 'quakeml_1.2_origin.sc3ml',
                                 '0.12',
                                 path=self.quakeml_path)

    def test_write_xslt_quarryblast(self):
        self.cmp_write_xslt_file('quakeml_1.2_quarryblast.xml',
                                 'quakeml_1.2_quarryblast.scml',
                                 '0.14')

    def test_write_xslt_magnitude(self):
        # Missing origin in original QuakeML test case.
        self.cmp_write_xslt_file('quakeml_1.2_magnitude.xml',
                                 'quakeml_1.2_magnitude.sc3ml',
                                 '0.12')

    def test_write_xslt_station_magnitude_contribution(self):
        # Missing origin in original QuakeML test case.
        self.cmp_write_xslt_file(
            'quakeml_1.2_stationmagnitudecontributions.xml',
            'quakeml_1.2_stationmagnitudecontributions.sc3ml',
            '0.12',
        )

    def test_write_xslt_station_magnitude(self):
        # Missing origin in original QuakeML test case.
        self.cmp_write_xslt_file('quakeml_1.2_stationmagnitude.xml',
                                 'quakeml_1.2_stationmagnitude.sc3ml',
                                 '0.10')

    def test_write_xslt_data_used_in_moment_tensor(self):
        self.cmp_write_xslt_file('quakeml_1.2_data_used.xml',
                                 'quakeml_1.2_data_used.scml',
                                 '0.14')

    def test_write_xslt_arrival(self):
        self.cmp_write_xslt_file('quakeml_1.2_arrival.xml',
                                 'quakeml_1.2_arrival_res.sc3ml',
                                 '0.13')

    def test_write_xslt_pick(self):
        self.cmp_write_xslt_file('quakeml_1.2_pick.xml',
                                 'quakeml_1.2_pick.sc3ml',
                                 '0.10')

    def test_write_xslt_012_events(self):
        self.cmp_write_xslt_file('westaus_events.xml',
                                 'westaus_events_0.12',
                                 '0.12')

    def test_write_xslt_014_events(self):
        self.cmp_write_xslt_file('westaus_events.xml',
                                 'westaus_events_0.14',
                                 '0.14')

    def test_write_xslt_focalmechanism(self):
        self.cmp_write_xslt_file('quakeml_1.2_focalmechanism.xml',
                                 'quakeml_1.2_focalmechanism.sc3ml',
                                 '0.11',
                                 path=self.quakeml_path)

    def test_write_xslt_iris_events(self):
        self.cmp_write_xslt_file('iris_events.xml',
                                 'iris_events.sc3ml',
                                 '0.10',
                                 path=self.quakeml_path)

    def test_write_xslt_neries_events(self):
        # Some ID are generated automatically. File comparison can't be done.
        filename = os.path.join(self.quakeml_path, 'neries_events.xml')
        catalog = _read_quakeml(filename)

        with NamedTemporaryFile() as tf:
            tmpfile = tf.name
            try:
                catalog.write(tmpfile, format='SCML', validate=True,
                              verbose=True)
            except AssertionError as e:
                self.fail(e)

    def test_write_xslt_usgs_events(self):
        # Can't validate due to wrong event types (QuakeML was already
        # unvalid)
        self.cmp_write_xslt_file('usgs_event.xml',
                                 'usgs_event.sc3ml',
                                 '0.11',
                                 path=self.quakeml_path,
                                 validate=False)

    def test_write_xslt_example(self):
        self.cmp_write_xslt_file('qml-example-1.2-RC3.xml',
                                 'qml-example-1.2-RC3.sc3ml',
                                 '0.11')

    def test_write_example(self, testdata):
        filename = testdata['qml-example-1.2-RC3.xml']
        catalog = _read_quakeml(filename)

        with NamedTemporaryFile() as tf:
            catalog.write(tf, format='SCML', validate=True,
                          version='0.13')
            filepath_cmp = \
                testdata['qml-example-1.2-RC3_write.sc3ml']
            assert filecmp.cmp(filepath_cmp, tf.name)

    def test_write_remove_events(self, testdata):
        filename = testdata['qml-example-1.2-RC3.xml']
        catalog = _read_quakeml(filename)

        with NamedTemporaryFile() as tf:
            catalog.write(tf, format='SCML', validate=True,
                          event_removal=True, version='0.13')
            filepath_cmp = \
                testdata['qml-example-1.2-RC3_no_events.sc3ml']
            assert filecmp.cmp(filepath_cmp, tf.name)

    def test_read_and_write(self, testdata):
        filename = testdata['qml-example-1.2-RC3_write.sc3ml']
        catalog = read_events(filename)

        with NamedTemporaryFile() as tf:
            catalog.write(tf, format='SCML', validate=True,
                          version='0.13')
            assert filecmp.cmp(filename, tf.name)

    def test_read_and_write_scml_fields(self, testdata):
        """
        Test that the non-QuakeML nodes are correctly rewritten.
        """
        filename = testdata['scml_fields.scml']
        catalog = read_events(filename)

        with NamedTemporaryFile() as tf:
            catalog.write(tf, format='SCML', validate=True,
                          version='0.14')
            assert filecmp.cmp(filename, tf.name)
