#  Copyright (C) 2017 The authors of Py-ChemShell
#
#  This file is part of Py-ChemShell.
#
#  Py-ChemShell is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  Py-ChemShell is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with Py-ChemShell.  If not, see
#  <http://www.gnu.org/licenses/>.

# super(ChemShKeyError, self).__init__(message)

from sys import modules


# YL 20/02/2021: fake a class to use @property
class __Wrapper(object):
    '''A wrapper class that will be disguised as a module'''

    @property
    def all_builtin_exceptions(self):
        '''Return a tuple of all Python built-in exceptions (subclasses of <class BaseExceptions)'''

        try:
            return self._all_builtin_exceptions
        except:
            from inspect import isclass
            # __builtins__ may vary due to the Python implementation
            if type(__builtins__) is dict:
                self._all_builtin_exceptions = tuple([ i for i in __builtins__.values() \
                                                     if isclass(i) and issubclass(i, BaseException) ])
            else:
                self._all_builtin_exceptions = tuple([ getattr(__builtins__, i) \
                                                     for i in __builtins__.__dir__() \
                                                     if isclass(getattr(__builtins__, i)) \
                                                     and issubclass(getattr(__builtins__, i), BaseException) ])
            return self._all_builtin_exceptions


    @property
    def PASSED(self):
        return 0


    @property
    def UNKNOWN(self):
        return 1


    # 127 is the shell file not found error code
    @property
    def UNSUPPORTED(self):
        return 127


    @property
    def FAILED(self):
        return 112


    @property
    def FATAL(self):
        return 999


    @property
    def CHEMSH_ERRCODES(self):
        return [ self.PASSED, self.UNKNOWN, self.UNSUPPORTED, self.FAILED, self.FATAL ]


    # YL: we don't often use the following error classes as they are generally less convenient than print and raise/exit


    class ChemShError(Exception):
        '''Base ChemShell error type'''

        # default parameters
        message = ''
        extra_message = ''
        indent = 0
        initial_indent = 1
        subsequent_indent = 12
        drop_whitespace = True
        # YL 16/03/2024: there may be strange output if replace_whitespace=False (see: https://docs.python.org/3/library/textwrap.html)
        replace_whitespace = True
        prepend_blanc_line = True
        extra_indent = 1
        extra_initial_indent = 0
        extra_subsequent_indent = 0
        extra_drop_whitespace = True
        extra_replace_whitespace = True
        extra_prepend_blanc_line = False
        function = lambda self,x:x
        extra_function = lambda self,x:x
        exit_code = 999

        def __init__(self, *args, **kwargs):
            '''Initialise'''

            from sys  import stdout
            from ..io import format

            # YL 16/03/2024: added an initialiser
            for k, v in kwargs.items():
                setattr(self, k, v)

    #        limit = sys.tracebacklimit
            strbuff = self.function(self.message.format(*args, **kwargs))
            format.printwrap(strbuff,
                             indent             = ' '*self.indent,
                             initial_indent     = ' '*self.initial_indent,
                             subsequent_indent  = ' '*self.subsequent_indent,
                             drop_whitespace    = self.drop_whitespace,
                             replace_whitespace = self.replace_whitespace,
                             prepend_blanc_line = self.prepend_blanc_line)
            if self.extra_message:
                strbuff = self.extra_function(self.extra_message.format(*args, **kwargs))
                format.printwrap(strbuff,
                                 indent             = ' '*self.extra_indent,
                                 initial_indent     = ' '*self.extra_initial_indent,
                                 subsequent_indent  = ' '*self.extra_subsequent_indent,
                                 drop_whitespace    = self.extra_drop_whitespace,
                                 replace_whitespace = self.extra_replace_whitespace,
                                 prepend_blanc_line = self.extra_prepend_blanc_line)
            stdout.flush()
#            sys.tracebacklimit = 1
            exit(self.exit_code)


    class ChemShKeyError(ChemShError):
        '''ChemShell keyword error type
    
           args[0]: user's keyword
           args[1]: object assigning for'''

        from sys  import stdout
        from ..io import format

        # YL 08/11/2021: we want to use format.printwrap() but it's not convenient here
        message = '''>>> ERROR: Keyword '{0}' unrecognised for class '{1.__class__.__name__}'. Legal keywords are:\n'''
        # YL 05/07/2023: takes 3 arguments now and the 3rd passes in a prepared string containing the list of legal keywords
        extra_message = '{2}'
        #extra_function = lambda self,x: x.strip('[]').replace('\'','').replace(',','')


    class ChemShValError(ChemShError):
        '''ChemShell value error type'''

        message = '''>>> ERROR: value for keyword '{0}' must be {1}'''


    class ChemShCellError(ChemShError):
        '''ChemShell numerical error type'''

        message = '''>>> ERROR: '{0} {1}'\n'''


    class ChemShNoneError(ChemShError):
        '''ChemShell None error type'''

        message = ''''''


    class ChemShNoAtomError(ChemShError):
        '''ChemShell no-atom error type'''

        message = '''>>> ERROR: fragment {0} does not contain any atom/centre\n'''


    class ChemShTypeError(ChemShError):
        '''ChemShell type error'''

        message = '''>>> ERROR: keyword argument '{0}' must be given a value of type {1}'''

    class ChemShNAError(ChemShError):
        '''ChemShell not-available error'''

        message = '''>>> ERROR: method '{0}()' is not applicable for <class {1.__class__.__name__}> instances'''


    class UnsupportedError(ChemShError):
        '''ChemShell not-available error'''

        def __str__(self):
            '''Error string'''

            return repr('Unsupported')

        message = '''>>> ERROR: Module {0} is not available'''


    class ChemShModuleError(ChemShError):
        '''ChemShell module failure error'''

        message = '''>>> ERROR: Module {0} has failed'''


    class ChemShGeneralError(ChemShError):
        '''ChemShell module failure error'''

        message = '''>>> ERROR: {0}'''


modules[__name__] = __Wrapper()
