# Copyright (C) 2013 DNAnexus, Inc.
#
# This file is part of dx-toolkit (DNAnexus platform client libraries).
#
#   Licensed under the Apache License, Version 2.0 (the "License"); you may not
#   use this file except in compliance with the License. You may obtain a copy
#   of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#   License for the specific language governing permissions and limitations
#   under the License.

'''
Exceptions for the :mod:`dxpy` package.
'''

import os, sys, json, traceback, httplib
from .packages import requests

class DXError(Exception):
    '''Base class for exceptions in this package.'''
    pass

class DXAPIError(DXError):
    '''
    Exception for when the API server responds with a code that is
    not 200 (OK).

    '''
    def __init__(self, content, code):
        self.name = content["error"]["type"]
        self.msg = content["error"]["message"]
        if "details" in content["error"]:
            self.details = content["error"]["details"]
        else:
            self.details = None
        self.code = code

    def __str__(self):
        output = self.name + ": " + self.msg + ", code " + str(self.code)
        if self.details is not None:
            output += "\nDetails: " + json.dumps(self.details)
        return output

class DXFileError(DXError):
    '''Exception for :class:`dxpy.bindings.dxfile.DXFile`.'''
    pass

class DXGTableError(DXError):
    '''Exception for :class:`dxpy.bindings.dxgtable.DXGTable`.'''
    pass

class DXSearchError(DXError):
    '''Exception for :mod:`dxpy.bindings.search` methods.'''
    pass

class DXAppletError(DXError):
    '''Exception for :class:`dxpy.bindings.dxapplet.DXApplet`.'''
    pass

class DXJobFailureError(DXError):
    '''Exception produced by :class:`dxpy.bindings.dxjob.DXJob` when a job fails.'''
    pass

class ProgramError(DXError):
    '''Deprecated. Use :class:`AppError` instead.'''
    pass

class AppError(ProgramError):
    '''
    Base class for fatal exceptions to be raised while using :mod:`dxpy` inside
    DNAnexus execution containers.

    This exception is thrown for user errors, and the error message is
    presented to the user. Throwing this exception will cause the Python
    execution template to write exception information into the file
    *job_error.json* in the current working directory, allowing reporting of
    the error state through the DNAnexus API.
    '''
    pass

class AppInternalError(DXError):
    '''
    Base class for fatal exceptions to be raised while using :mod:`dxpy` inside
    DNAnexus execution containers.

    This exception is intended for internal App errors, whose message goes to
    the App developer. Throwing this exception will cause the Python execution
    template to write exception information into the file ``job_error.json`` in
    the current working directory, allowing reporting of the error state
    through the DNAnexus API.
    '''
    pass

def exit_with_exc_info(code=1, message='', print_tb=False):
    '''
    Exits the program, printing information about the last exception (if any) and an optional error message.
    :param code: Exit code.
    :type code: integer (valid exit code, 0-255)
    :param message: Message to be printed after the exception information.
    :type message: string
    :param print_tb: If set to True, prints the exception traceback; otherwise, suppresses it.
    :type print_tb: boolean
    '''
    exc_type, exc_value = sys.exc_info()[:2]
    if exc_type is not None:
        if print_tb:
            traceback.print_exc()
        else:
            for line in traceback.format_exception_only(exc_type, exc_value):
                sys.stderr.write(line)

    sys.stderr.write(message)
    if message != '' and not message.endswith('\n'):
        sys.stderr.write('\n')
    sys.exit(code)

default_expected_exceptions = (DXAPIError,
                               requests.ConnectionError,
                               requests.HTTPError,
                               requests.Timeout,
                               httplib.HTTPException)

def err_exit(message='', code=None, expected_exceptions=default_expected_exceptions, arg_parser=None):
    '''
    Exits the program, printing information about the last exception (if any) and an optional error message. Uses **expected_exceptions** to set the error code decide whether to suppress the error traceback.
    :param message: Message to be printed after the exception information.
    :type message: string
    :param code: Exit code.
    :type code: integer (valid exit code, 0-255)
    :param expected_exceptions: Exceptions for which to exit with error code 3 (expected error condition) and suppress the stack trace (unless the _DX_DEBUG environment variable is set).
    :type expected_exceptions: iterable
    :param arg_parser: argparse.ArgumentParser object used in the program (optional)
    '''
    if arg_parser is not None:
        message = arg_parser.prog + ": " + message
    exc = sys.exc_info()[1]
    if isinstance(exc, expected_exceptions):
        exit_with_exc_info(3, message, print_tb=True if '_DX_DEBUG' in os.environ else False)
    else:
        if code is None:
            code = 1
        exit_with_exc_info(code, message, print_tb=True)
