#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam                                                #
# All rights reserved.                                                     #
#                                                                          #
# GNU Affero General Public License Usage                                  #
# This file may be used under the terms of the GNU Affero                  #
# Public License version 3.0 as published by the Free Software Foundation  #
# and appearing in the file LICENSE included in the packaging of this      #
# file. Please review the following information to ensure the GNU Affero   #
# Public License version 3.0 requirements will be met:                     #
# https://www.gnu.org/licenses/agpl-3.0.html.                              #
#                                                                          #
# Author: Alexander Jaeger, Stephan Herrnkind,                             #
#         Lukas Lehmann, Dirk Roessler#                                    #
# Email: herrnkind@gempa.de                                                #
############################################################################


# from time import strptime
import sys
import traceback
import seiscomp.client
import seiscomp.core
import seiscomp.datamodel
import seiscomp.io
import seiscomp.logging
import seiscomp.math


TimeFormats = ["%d-%b-%Y_%H:%M:%S.%f", "%d-%b-%Y_%H:%M:%S"]


# SC3 has more event types available in the datamodel
EventTypes = {
    "teleseismic quake": seiscomp.datamodel.EARTHQUAKE,
    "local quake": seiscomp.datamodel.EARTHQUAKE,
    "regional quake": seiscomp.datamodel.EARTHQUAKE,
    "quarry blast": seiscomp.datamodel.QUARRY_BLAST,
    "nuclear explosion": seiscomp.datamodel.NUCLEAR_EXPLOSION,
    "mining event": seiscomp.datamodel.MINING_EXPLOSION,
}


def wfs2Str(wfsID):
    return f"{wfsID.networkCode()}.{wfsID.stationCode()}.{wfsID.locationCode()}.{wfsID.channelCode()}"


###############################################################################
class SH2Proc(seiscomp.client.Application):
    ###########################################################################
    def __init__(self):
        seiscomp.client.Application.__init__(self, len(sys.argv), sys.argv)
        self.setMessagingEnabled(True)
        self.setDatabaseEnabled(True, True)
        self.setLoadInventoryEnabled(True)
        self.setLoadConfigModuleEnabled(True)
        self.setDaemonEnabled(False)

        self.inputFile = "-"
        self.streams = None

    ###########################################################################
    def initConfiguration(self):
        if not seiscomp.client.Application.initConfiguration(self):
            return False

        # If the database connection is passed via command line or configuration
        # file then messaging is disabled. Messaging is only used to get
        # the configured database connection URI.
        if self.databaseURI() != "":
            self.setMessagingEnabled(False)
        else:
            # A database connection is not required if the inventory is loaded
            # from file
            if not self.isInventoryDatabaseEnabled():
                self.setMessagingEnabled(False)
                self.setDatabaseEnabled(False, False)

        return True

    ##########################################################################
    def printUsage(self):
        print(
            """Usage:
  sh2proc [options]

Convert Seismic Handler event data to SeisComP XML format which is sent to stdout."""
        )

        seiscomp.client.Application.printUsage(self)

        print(
            """Examples:
Convert the Seismic Handler file shm.evt to SCML. Receive the database
connection to read inventory and configuration information from messaging
  sh2proc shm.evt > event.xml

Read Seismic Handler data from stdin. Provide inventory and configuration in XML
  cat shm.evt | sh2proc --inventory-db=inventory.xml --config-db=config.xml > event.xml
"""
        )

    ##########################################################################
    def validateParameters(self):
        if not seiscomp.client.Application.validateParameters(self):
            return False

        for opt in self.commandline().unrecognizedOptions():
            if len(opt) > 1 and opt.startswith("-"):
                continue

            self.inputFile = opt
            break

        return True

    ###########################################################################
    def loadStreams(self):
        now = seiscomp.core.Time.GMT()
        inv = seiscomp.client.Inventory.Instance()

        self.streams = {}

        # try to load streams by detecLocid and detecStream
        mod = self.configModule()
        if mod is not None and mod.configStationCount() > 0:
            seiscomp.logging.info("loading streams using detecLocid and detecStream")
            for i in range(mod.configStationCount()):
                cfg = mod.configStation(i)
                net = cfg.networkCode()
                sta = cfg.stationCode()
                if sta in self.streams:
                    seiscomp.logging.warning(
                        f"ambiguous stream id found for station {net}.{sta}"
                    )
                    continue

                setup = seiscomp.datamodel.findSetup(cfg, self.name(), True)
                if not setup:
                    seiscomp.logging.warning(
                        f"could not find station setup for {net}.{sta}"
                    )
                    continue

                params = seiscomp.datamodel.ParameterSet.Find(setup.parameterSetID())
                if not params:
                    seiscomp.logging.warning(
                        f"could not find station parameters for {net}.{sta}"
                    )
                    continue

                detecLocid = ""
                detecStream = None

                for j in range(params.parameterCount()):
                    param = params.parameter(j)
                    if param.name() == "detecStream":
                        detecStream = param.value()
                    elif param.name() == "detecLocid":
                        detecLocid = param.value()

                if detecStream is None:
                    seiscomp.logging.warning(
                        f"could not find detecStream for {net}.{sta}"
                    )
                    continue

                loc = inv.getSensorLocation(net, sta, detecLocid, now)
                if loc is None:
                    seiscomp.logging.warning(
                        f"could not find preferred location for {net}.{sta}"
                    )
                    continue

                components = {}
                tc = seiscomp.datamodel.ThreeComponents()
                seiscomp.datamodel.getThreeComponents(tc, loc, detecStream[:2], now)
                if tc.vertical():
                    cha = tc.vertical()
                    wfsID = seiscomp.datamodel.WaveformStreamID(
                        net, sta, loc.code(), cha.code(), ""
                    )
                    components[cha.code()[-1]] = wfsID
                    seiscomp.logging.debug(f"add stream {wfs2Str(wfsID)} (vertical)")
                if tc.firstHorizontal():
                    cha = tc.firstHorizontal()
                    wfsID = seiscomp.datamodel.WaveformStreamID(
                        net, sta, loc.code(), cha.code(), ""
                    )
                    components[cha.code()[-1]] = wfsID
                    seiscomp.logging.debug(
                        f"add stream {wfs2Str(wfsID)} (first horizontal)"
                    )
                if tc.secondHorizontal():
                    cha = tc.secondHorizontal()
                    wfsID = seiscomp.datamodel.WaveformStreamID(
                        net, sta, loc.code(), cha.code(), ""
                    )
                    components[cha.code()[-1]] = wfsID
                    seiscomp.logging.debug(
                        f"add stream {wfs2Str(wfsID)} (second horizontal)"
                    )
                if len(components) > 0:
                    self.streams[sta] = components

            return

        # fallback loading streams from inventory
        seiscomp.logging.warning(
            "no configuration module available, loading streams "
            "from inventory and selecting first available stream "
            "matching epoch"
        )
        for iNet in range(inv.inventory().networkCount()):
            net = inv.inventory().network(iNet)
            seiscomp.logging.debug(
                f"network {net.code()}: loaded {net.stationCount()} stations"
            )
            for iSta in range(net.stationCount()):
                sta = net.station(iSta)
                try:
                    start = sta.start()
                    if not start <= now:
                        continue
                except:
                    continue

                try:
                    end = sta.end()
                    if not now <= end:
                        continue
                except:
                    pass

                for iLoc in range(sta.sensorLocationCount()):
                    loc = sta.sensorLocation(iLoc)
                    for iCha in range(loc.streamCount()):
                        cha = loc.stream(iCha)

                        wfsID = seiscomp.datamodel.WaveformStreamID(
                            net.code(), sta.code(), loc.code(), cha.code(), ""
                        )
                        comp = cha.code()[2]
                        if sta.code() not in self.streams:
                            components = {}
                            components[comp] = wfsID
                            self.streams[sta.code()] = components
                        else:
                            # Seismic Handler does not support network,
                            # location and channel code: make sure network and
                            # location codes match first item in station
                            # specific steam list
                            oldWfsID = list(self.streams[sta.code()].values())[0]
                            if (
                                net.code() != oldWfsID.networkCode()
                                or loc.code() != oldWfsID.locationCode()
                                or cha.code()[:2] != oldWfsID.channelCode()[:2]
                            ):
                                seiscomp.logging.warning(
                                    f"ambiguous stream id found for station\
                                        {sta.code()}, ignoring {wfs2Str(wfsID)}"
                                )
                                continue

                            self.streams[sta.code()][comp] = wfsID

                        seiscomp.logging.debug(f"add stream {wfs2Str(wfsID)}")

    ###########################################################################
    def parseTime(self, timeStr):
        time = seiscomp.core.Time()
        for fmt in TimeFormats:
            if time.fromString(timeStr, fmt):
                break
        return time

    ###########################################################################
    def parseMagType(self, value):
        if value == "m":
            return "M"
        if value == "ml":
            return "ML"
        if value == "mb":
            return "mb"
        if value == "ms":
            return "Ms(BB)"
        if value == "mw":
            return "Mw"
        if value == "bb":
            return "mB"

        return ""

    ###########################################################################
    def sh2proc(self, file):
        ep = seiscomp.datamodel.EventParameters()
        origin = seiscomp.datamodel.Origin.Create()
        event = seiscomp.datamodel.Event.Create()

        origin.setCreationInfo(seiscomp.datamodel.CreationInfo())
        origin.creationInfo().setCreationTime(seiscomp.core.Time.GMT())

        originQuality = None
        originCE = None
        latFound = False
        lonFound = False
        depthError = None
        originComments = {}

        # variables, reset after 'end of phase'
        pick = None
        stationMag = None
        staCode = None
        compCode = None
        stationMagBB = None

        ampPeriod = None
        ampBBPeriod = None
        amplitudeDisp = None
        amplitudeVel = None
        amplitudeSNR = None
        amplitudeBB = None

        magnitudeMB = None
        magnitudeML = None
        magnitudeMS = None
        magnitudeBB = None

        # To avoid undefined warning
        arrival = None
        phase = None

        km2degFac = 1.0 / seiscomp.math.deg2km(1.0)

        # read file line by line, split key and value at colon
        iLine = 0
        for line in file:
            iLine += 1
            a = line.split(":", 1)
            key = a[0].strip()
            keyLower = key.lower()
            value = None

            # empty line
            if len(keyLower) == 0:
                continue

            # end of phase
            if keyLower == "--- end of phase ---":
                if pick is None:
                    seiscomp.logging.warning(f"Line {iLine}: found empty phase block")
                    continue

                if staCode is None or compCode is None:
                    seiscomp.logging.warning(
                        f"Line {iLine}: end of phase, stream code incomplete"
                    )
                    continue

                if not staCode in self.streams:
                    seiscomp.logging.warning(
                        f"Line {iLine}: end of phase, station code {staCode} not found in inventory"
                    )
                    continue

                if not compCode in self.streams[staCode]:
                    seiscomp.logging.warning(
                        f"Line {iLine}: end of phase, component\
                            {compCode} of station {staCode} not found in inventory"
                    )
                    continue

                streamID = self.streams[staCode][compCode]

                pick.setWaveformID(streamID)
                ep.add(pick)

                arrival.setPickID(pick.publicID())
                arrival.setPhase(phase)
                origin.add(arrival)

                if amplitudeSNR is not None:
                    amplitudeSNR.setPickID(pick.publicID())
                    amplitudeSNR.setWaveformID(streamID)
                    ep.add(amplitudeSNR)

                if amplitudeBB is not None:
                    amplitudeBB.setPickID(pick.publicID())
                    amplitudeBB.setWaveformID(streamID)
                    ep.add(amplitudeBB)

                if stationMagBB is not None:
                    stationMagBB.setWaveformID(streamID)
                    origin.add(stationMagBB)
                    stationMagContrib = (
                        seiscomp.datamodel.StationMagnitudeContribution()
                    )
                    stationMagContrib.setStationMagnitudeID(stationMagBB.publicID())
                    if magnitudeBB is None:
                        magnitudeBB = seiscomp.datamodel.Magnitude.Create()
                    magnitudeBB.add(stationMagContrib)

                if stationMag is not None:
                    if stationMag.type() in ["mb", "ML"] and amplitudeDisp is not None:
                        amplitudeDisp.setPickID(pick.publicID())
                        amplitudeDisp.setWaveformID(streamID)
                        amplitudeDisp.setPeriod(
                            seiscomp.datamodel.RealQuantity(ampPeriod)
                        )
                        amplitudeDisp.setType(stationMag.type())
                        ep.add(amplitudeDisp)

                    if stationMag.type() in ["Ms(BB)"] and amplitudeVel is not None:
                        amplitudeVel.setPickID(pick.publicID())
                        amplitudeVel.setWaveformID(streamID)
                        amplitudeVel.setPeriod(
                            seiscomp.datamodel.RealQuantity(ampPeriod)
                        )
                        amplitudeVel.setType(stationMag.type())
                        ep.add(amplitudeVel)

                    stationMag.setWaveformID(streamID)
                    origin.add(stationMag)

                    stationMagContrib = (
                        seiscomp.datamodel.StationMagnitudeContribution()
                    )
                    stationMagContrib.setStationMagnitudeID(stationMag.publicID())

                    magType = stationMag.type()
                    if magType == "ML":
                        if magnitudeML is None:
                            magnitudeML = seiscomp.datamodel.Magnitude.Create()
                        magnitudeML.add(stationMagContrib)

                    elif magType == "Ms(BB)":
                        if magnitudeMS is None:
                            magnitudeMS = seiscomp.datamodel.Magnitude.Create()
                        magnitudeMS.add(stationMagContrib)

                    elif magType == "mb":
                        if magnitudeMB is None:
                            magnitudeMB = seiscomp.datamodel.Magnitude.Create()
                        magnitudeMB.add(stationMagContrib)

                pick = None
                staCode = None
                compCode = None
                stationMag = None
                stationMagBB = None
                ampPeriod = None
                ampBBPeriod = None
                amplitudeDisp = None
                amplitudeVel = None
                amplitudeSNR = None
                amplitudeBB = None

                continue

            # empty key
            if len(a) == 1:
                seiscomp.logging.warning(f"Line {iLine}: key without value")
                continue

            value = a[1].strip()
            if pick is None:
                pick = seiscomp.datamodel.Pick.Create()
                arrival = seiscomp.datamodel.Arrival()

            try:
                ##############################################################
                # station parameters

                # station code
                if keyLower == "station code":
                    staCode = value

                # pick time
                elif keyLower == "onset time":
                    pick.setTime(seiscomp.datamodel.TimeQuantity(self.parseTime(value)))

                # pick onset type
                elif keyLower == "onset type":
                    found = False
                    for onset in [
                        seiscomp.datamodel.EMERGENT,
                        seiscomp.datamodel.IMPULSIVE,
                        seiscomp.datamodel.QUESTIONABLE,
                    ]:
                        if value == seiscomp.datamodel.EPickOnsetNames.name(onset):
                            pick.setOnset(onset)
                            found = True
                            break
                    if not found:
                        raise Exception("Unsupported onset value")

                # phase code
                elif keyLower == "phase name":
                    phase = seiscomp.datamodel.Phase()
                    phase.setCode(value)
                    pick.setPhaseHint(phase)

                # event type
                elif keyLower == "event type":
                    evttype = EventTypes[value]
                    event.setType(evttype)
                    originComments[key] = value

                # filter ID
                elif keyLower == "applied filter":
                    pick.setFilterID(value)

                # channel code, prepended by configured Channel prefix if only
                # one character is found
                elif keyLower == "component":
                    compCode = value

                # pick evaluation mode
                elif keyLower == "pick type":
                    found = False
                    for mode in [
                        seiscomp.datamodel.AUTOMATIC,
                        seiscomp.datamodel.MANUAL,
                    ]:
                        if value == seiscomp.datamodel.EEvaluationModeNames.name(mode):
                            pick.setEvaluationMode(mode)
                            found = True
                            break
                    if not found:
                        raise Exception("Unsupported evaluation mode value")

                # pick author
                elif keyLower == "analyst":
                    creationInfo = seiscomp.datamodel.CreationInfo()
                    creationInfo.setAuthor(value)
                    pick.setCreationInfo(creationInfo)

                # pick polarity
                # isn't tested
                elif keyLower == "sign":
                    if value == "positive":
                        sign = "0"  # positive
                    elif value == "negative":
                        sign = "1"  # negative
                    else:
                        sign = "2"  # unknown
                    pick.setPolarity(float(sign))

                # arrival weight
                elif keyLower == "weight":
                    arrival.setWeight(float(value))

                # arrival azimuth
                elif keyLower == "theo. azimuth (deg)":
                    arrival.setAzimuth(float(value))

                # pick theo backazimuth
                elif keyLower == "theo. backazimuth (deg)":
                    if pick.slownessMethodID() == "corrected":
                        seiscomp.logging.debug(
                            f"Line {iLine}: ignoring parameter: {key}"
                        )
                    else:
                        pick.setBackazimuth(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )
                        pick.setSlownessMethodID("theoretical")

                # pick beam slowness
                elif keyLower == "beam-slowness (sec/deg)":
                    if pick.slownessMethodID() == "corrected":
                        seiscomp.logging.debug(
                            f"Line {iLine}: ignoring parameter: {key}"
                        )
                    else:
                        pick.setHorizontalSlowness(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )
                        pick.setSlownessMethodID("Array Beam")

                # pick beam backazimuth
                elif keyLower == "beam-azimuth (deg)":
                    if pick.slownessMethodID() == "corrected":
                        seiscomp.logging.debug(
                            f"Line {iLine}: ignoring parameter: {key}"
                        )
                    else:
                        pick.setBackazimuth(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )

                # pick epi slowness
                elif keyLower == "epi-slowness (sec/deg)":
                    pick.setHorizontalSlowness(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    pick.setSlownessMethodID("corrected")

                # pick epi backazimuth
                elif keyLower == "epi-azimuth (deg)":
                    pick.setBackazimuth(seiscomp.datamodel.RealQuantity(float(value)))

                # arrival distance degree
                elif keyLower == "distance (deg)":
                    arrival.setDistance(float(value))

                # arrival distance km, recalculates for degree
                elif keyLower == "distance (km)":
                    if isinstance(arrival.distance(), float):
                        seiscomp.logging.debug(
                            f"Line {iLine - 1}: ignoring parameter: distance (deg)"
                        )
                    arrival.setDistance(float(value) * km2degFac)

                # arrival time residual
                elif keyLower == "residual time":
                    arrival.setTimeResidual(float(value))

                # amplitude snr
                elif keyLower == "signal/noise":
                    amplitudeSNR = seiscomp.datamodel.Amplitude.Create()
                    amplitudeSNR.setType("SNR")
                    amplitudeSNR.setAmplitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )

                # amplitude period
                elif keyLower.startswith("period"):
                    ampPeriod = float(value)

                # amplitude value for displacement
                elif keyLower == "amplitude (nm)":
                    amplitudeDisp = seiscomp.datamodel.Amplitude.Create()
                    amplitudeDisp.setAmplitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    amplitudeDisp.setUnit("nm")

                # amplitude value for velocity
                elif keyLower.startswith("vel. amplitude"):
                    amplitudeVel = seiscomp.datamodel.Amplitude.Create()
                    amplitudeVel.setAmplitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    amplitudeVel.setUnit("nm/s")

                elif keyLower == "bb amplitude (nm/sec)":
                    amplitudeBB = seiscomp.datamodel.Amplitude.Create()
                    amplitudeBB.setAmplitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    amplitudeBB.setType("mB")
                    amplitudeBB.setUnit("nm/s")
                    amplitudeBB.setPeriod(seiscomp.datamodel.RealQuantity(ampBBPeriod))

                elif keyLower == "bb period (sec)":
                    ampBBPeriod = float(value)

                elif keyLower == "broadband magnitude":
                    magType = self.parseMagType("bb")
                    stationMagBB = seiscomp.datamodel.StationMagnitude.Create()
                    stationMagBB.setMagnitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    stationMagBB.setType(magType)
                    stationMagBB.setAmplitudeID(amplitudeBB.publicID())

                # ignored
                elif keyLower == "quality number":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                # station magnitude value and type
                elif keyLower.startswith("magnitude "):
                    magType = self.parseMagType(key[10:])
                    stationMag = seiscomp.datamodel.StationMagnitude.Create()
                    stationMag.setMagnitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )

                    if len(magType) > 0:
                        stationMag.setType(magType)
                    if magType == "mb":
                        stationMag.setAmplitudeID(amplitudeDisp.publicID())

                    elif magType == "MS(BB)":
                        stationMag.setAmplitudeID(amplitudeVel.publicID())
                    else:
                        seiscomp.logging.debug(
                            f"Line {iLine}: Magnitude Type not known {magType}."
                        )

                ###############################################################
                # origin parameters

                # event ID, added as origin comment later on
                elif keyLower == "event id":
                    originComments[key] = value

                # magnitude value and type
                elif keyLower == "mean bb magnitude":
                    magType = self.parseMagType("bb")
                    if magnitudeBB is None:
                        magnitudeBB = seiscomp.datamodel.Magnitude.Create()
                    magnitudeBB.setMagnitude(
                        seiscomp.datamodel.RealQuantity(float(value))
                    )
                    magnitudeBB.setType(magType)

                elif keyLower.startswith("mean magnitude "):
                    magType = self.parseMagType(key[15:])

                    if magType == "ML":
                        if magnitudeML is None:
                            magnitudeML = seiscomp.datamodel.Magnitude.Create()
                        magnitudeML.setMagnitude(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )
                        magnitudeML.setType(magType)

                    elif magType == "Ms(BB)":
                        if magnitudeMS is None:
                            magnitudeMS = seiscomp.datamodel.Magnitude.Create()
                        magnitudeMS.setMagnitude(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )
                        magnitudeMS.setType(magType)

                    elif magType == "mb":
                        if magnitudeMB is None:
                            magnitudeMB = seiscomp.datamodel.Magnitude.Create()
                        magnitudeMB.setMagnitude(
                            seiscomp.datamodel.RealQuantity(float(value))
                        )
                        magnitudeMB.setType(magType)

                    else:
                        seiscomp.logging.warning(
                            f"Line {iLine}: Magnitude type {magType} not defined yet."
                        )

                # latitude
                elif keyLower == "latitude":
                    origin.latitude().setValue(float(value))
                    latFound = True
                elif keyLower == "error in latitude (km)":
                    origin.latitude().setUncertainty(float(value))

                # longitude
                elif keyLower == "longitude":
                    origin.longitude().setValue(float(value))
                    lonFound = True
                elif keyLower == "error in longitude (km)":
                    origin.longitude().setUncertainty(float(value))

                # depth
                elif keyLower == "depth (km)":
                    origin.setDepth(seiscomp.datamodel.RealQuantity(float(value)))
                    if depthError is not None:
                        origin.depth().setUncertainty(depthError)
                elif keyLower == "depth type":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
                elif keyLower == "error in depth (km)":
                    depthError = float(value)
                    try:
                        origin.depth().setUncertainty(depthError)
                    except seiscomp.core.ValueException:
                        pass

                # time
                elif keyLower == "origin time":
                    origin.time().setValue(self.parseTime(value))
                elif keyLower == "error in origin time":
                    origin.time().setUncertainty(float(value))

                # location method
                elif keyLower == "location method":
                    origin.setMethodID(str(value))

                # region table, added as origin comment later on
                elif keyLower == "region table":
                    originComments[key] = value

                # region table, added as origin comment later on
                elif keyLower == "region id":
                    originComments[key] = value

                # source region, added as origin comment later on
                elif keyLower == "source region":
                    originComments[key] = value

                # used station count
                elif keyLower == "no. of stations used":
                    if originQuality is None:
                        originQuality = seiscomp.datamodel.OriginQuality()
                    originQuality.setUsedStationCount(int(value))

                # ignored
                elif keyLower == "reference location name":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                # confidence ellipsoid major axis
                elif keyLower == "error ellipse major":
                    if originCE is None:
                        originCE = seiscomp.datamodel.ConfidenceEllipsoid()
                    originCE.setSemiMajorAxisLength(float(value))

                # confidence ellipsoid minor axis
                elif keyLower == "error ellipse minor":
                    if originCE is None:
                        originCE = seiscomp.datamodel.ConfidenceEllipsoid()
                    originCE.setSemiMinorAxisLength(float(value))

                # confidence ellipsoid rotation
                elif keyLower == "error ellipse strike":
                    if originCE is None:
                        originCE = seiscomp.datamodel.ConfidenceEllipsoid()
                    originCE.setMajorAxisRotation(float(value))

                # azimuthal gap
                elif keyLower == "max azimuthal gap (deg)":
                    if originQuality is None:
                        originQuality = seiscomp.datamodel.OriginQuality()
                    originQuality.setAzimuthalGap(float(value))

                # creation info author
                elif keyLower == "author":
                    origin.creationInfo().setAuthor(value)

                # creation info agency
                elif keyLower == "source of information":
                    origin.creationInfo().setAgencyID(value)

                # earth model id
                elif keyLower == "velocity model":
                    origin.setEarthModelID(value)

                # standard error
                elif keyLower == "rms of residuals (sec)":
                    if originQuality is None:
                        originQuality = seiscomp.datamodel.OriginQuality()
                    originQuality.setStandardError(float(value))

                # ignored
                elif keyLower == "phase flags":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                # ignored
                elif keyLower == "location input params":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                # missing keys
                elif keyLower == "ampl&period source":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                elif keyLower == "location quality":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                elif keyLower == "reference latitude":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                elif keyLower == "reference longitude":
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                elif keyLower.startswith("amplitude time"):
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")

                # unknown key
                else:
                    seiscomp.logging.warning(
                        "Line {iLine}: ignoring unknown parameter: {key}"
                    )

            except ValueError:
                seiscomp.logging.warning(f"Line {iLine}: can not parse {key} value")
            except Exception:
                seiscomp.logging.error("Line {iLine}: {str(traceback.format_exc())}")
                return None

        # check
        if not latFound:
            seiscomp.logging.warning("could not add origin, missing latitude parameter")
        elif not lonFound:
            seiscomp.logging.warning(
                "could not add origin, missing longitude parameter"
            )
        elif not origin.time().value().valid():
            seiscomp.logging.warning(
                "could not add origin, missing origin time parameter"
            )
        else:
            if magnitudeMB is not None:
                origin.add(magnitudeMB)
            if magnitudeML is not None:
                origin.add(magnitudeML)
            if magnitudeMS is not None:
                origin.add(magnitudeMS)
            if magnitudeBB is not None:
                origin.add(magnitudeBB)

            ep.add(event)
            ep.add(origin)

            if originQuality is not None:
                origin.setQuality(originQuality)

            if originCE is not None:
                uncertainty = seiscomp.datamodel.OriginUncertainty()
                uncertainty.setConfidenceEllipsoid(originCE)
                origin.setUncertainty(uncertainty)

            for k, v in originComments.items():
                comment = seiscomp.datamodel.Comment()
                comment.setId(k)
                comment.setText(v)
                origin.add(comment)

        return ep

    ###########################################################################
    def run(self):
        self.loadStreams()

        try:
            if self.inputFile == "-":
                f = sys.stdin
            else:
                f = open(self.inputFile)
        except IOError as e:
            seiscomp.logging.error(str(e))
            return False

        ep = self.sh2proc(f)
        if ep is None:
            return False

        ar = seiscomp.io.XMLArchive()
        ar.create("-")
        ar.setFormattedOutput(True)
        ar.writeObject(ep)
        ar.close()

        return True


###############################################################################
def main():
    try:
        app = SH2Proc()
        return app()
    except:
        sys.stderr.write(str(traceback.format_exc()))

    return 1


if __name__ == "__main__":
    sys.exit(main())


# vim: ts=4 et
