###############################################################################
# interface for translating GAMMA errors messages into Python error types

# Copyright (c) 2015-2026, the pyroSAR Developers.

# This file is part of the pyroSAR Project. It is subject to the
# license terms in the LICENSE.txt file found in the top-level
# directory of this distribution and at
# https://github.com/johntruckenbrodt/pyroSAR/blob/master/LICENSE.txt.
# No part of the pyroSAR project, including this file, may be
# copied, modified, propagated, or distributed except according
# to the terms contained in the LICENSE.txt file.
###############################################################################

import re
import signal


def gammaErrorHandler(returncode: int, out: str, err: str) -> None:
    """
    Function to raise errors in Python. This function is not intended
    for direct use but as part of function :func:`pyroSAR.gamma.auxil.process`.
    
    Parameters
    ----------
    returncode:
        the subprocess return code
    out:
        the stdout message returned by a subprocess call of a gamma command
    err:
        the stderr message returned by a subprocess call of a gamma command

    Raises: IOError | ValueError | RuntimeError

    """
    
    # scan stdout and stdin messages for lines starting with 'ERROR'
    messages = out.split('\n') if out else []
    messages.extend(err.strip().split('\n'))
    errormessages = [x for x in messages if x.startswith('ERROR')]
    
    # registry of known gamma error messages and corresponding Python error types
    # do not change the Python error types of specific messages! This will change the behavior of several functions
    # in case no error is to be thrown define None as error type
    knownErrors = {'image data formats differ': IOError,
                   'cannot open': IOError,
                   r'no coverage of SAR image by DEM(?: \(in (?:latitude/northing|longitude/easting)\)|)': RuntimeError,
                   'libgdal.so.1: no version information available': None,
                   'line outside of image': ValueError,
                   'no offsets found above SNR threshold': ValueError,
                   'window size < 4': ValueError,
                   'MLI oversampling factor must be 1, 2, 4, 8': ValueError,
                   'no points available for determining average intensity': ValueError,
                   'p_interp(): time outside of range': RuntimeError,
                   'no overlap with lookup table': RuntimeError,
                   'insufficient offset points to determine offset model parameters': RuntimeError,
                   'insufficient offset points left after culling to determine offset model parameters': RuntimeError,
                   'calloc_1d: number of elements <= 0': ValueError,
                   'multi-look output line:': RuntimeError,
                   'no OPOD state vector found with the required start time!': RuntimeError,
                   'gc_map operates only with slant range geometry, image geometry in SLC_par: GROUND_RANGE': RuntimeError,
                   'OPOD state vector data ends before start of the state vector time window': RuntimeError,
                   'non-zero exit status': RuntimeError,
                   'unsupported DEM projection': RuntimeError,
                   'tiffWriteProc:No space left on device': RuntimeError,
                   'in subroutine julday: there is no year zero!': RuntimeError,
                   'cannot create ISP image parameter file': OSError}
    
    # check if the error message is known and throw the mapped error from knownErrors accordingly.
    # Otherwise raise a RuntimeError if killed by a signal and a GammaUnknownError in all other cases.
    # The actual message is passed to the error and thus visible for backtracing.
    if returncode != 0:
        if len(errormessages) > 0:
            errormessage = errormessages[-1]
            err_out = '\n\n'.join([re.sub('ERROR[: ]*', '', x) for x in errormessages])
            for error in knownErrors:
                if re.search(error, errormessage):
                    errortype = knownErrors[error]
                    if errortype:
                        raise errortype(err_out)
                    else:
                        return
        else:
            err_out = f'failed with return code {returncode}'
            if returncode < 0:
                # handle signal kills like SIGSEGV (segmentation fault)
                sig = signal.Signals(-returncode)
                raise RuntimeError(err_out + f' ({sig.name})')
        raise GammaUnknownError(err_out)


class GammaUnknownError(Exception):
    """
    This is a general error, which is raised if the error message is not yet integrated
    into the known errors of function :func:`gammaErrorHandler`.
    If this error occurs, the message should be included in this function.
    """
    
    def __init__(self, errormessage):
        Exception.__init__(self, errormessage)
