# Copyright (C) 2017 The Meme Factory, Inc.  http://www.meme.com/
#
# 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.

# Karl O. Pinc <kop@meme.com>

'''Exception classes'''

import sys


class Warning(Exception):
    def __init__(self, condition, descr=None, detail=None, hint=None):
        super(Warning, self).__init__()
        self.condition = condition
        self.descr = descr
        self.detail = detail
        self.hint = hint

    def __str__(self):
        '''It may well be better to delete this and use the standard
        python exception representation.'''
        out = 'warning: {0}'.format(self.condition)
        return self._format(out)

    def _format(self, out):
        if self.descr is not None:
            out = '{0}\ndescr: {1}'.format(out, self.descr)
        if self.detail is not None:
            out = '{0}\ndetail: {1}'.format(out, self.detail)
        if self.hint is not None:
            out = '{0}\nhint: {1}'.format(out, self.hint)
        return out


class UnusedDeclarationWarning(Warning):
    def __init__(self, key):
        super(UnusedDeclarationWarning, self).__init__(
            'Unused declaration in the configuration file',
            'A recognized declaration is present but ignored',
            'The "{0}" declaration is not needed'.format(key))


class PoorRuleWarning(Warning):
    def __init__(self, descr=None, detail=None, hint=None):
        super(PoorRuleWarning, self).__init__(
            'Poor file name convention rules',
            descr, detail)


class DuplicateRuleWarning(PoorRuleWarning):
    def __init__(self, detail=None):
        super(DuplicateRuleWarning, self).__init__(
            'Duplicate rules found',
            detail)


class ExtraRuleWarning(PoorRuleWarning):
    def __init__(self, detail=None):
        super(ExtraRuleWarning, self).__init__(
            'Multiple rules where 1 will do',
            detail=detail,
            hint=('Combine 2 rules using [] to designate '
                  'optional components'))


class Unrecognized3339TimeWarning(Warning):
    def __init__(self, descr=None):
        super(Unrecognized3339TimeWarning, self).__init__(
            'Unrecognized RFC3339 time value',
            'Unrecognized modifiedTime value from Google',
            descr)


class Error(Warning):
    def __init__(self, err, descr=None, detail=None, hint=None):
        super(Error, self).__init__(err, descr, detail, hint)

    def __str__(self):
        out = 'error: {0}'.format(self.condition)
        return self._format(out)


class NoFileError(Error):
    def __init__(self, descr=None, detail=None):
        super(NoFileError, self).__init__(
            'File not found', descr, detail,
            'Missing file or directory')


class NoConfigFileError(NoFileError):
    def __init__(self, detail=None):
        super(NoConfigFileError, self).__init__(
            'No configuration file found',
            detail)


class NoRulesFileError(NoFileError):
    def __init__(self, detail=None):
        super(NoRulesFileError, self).__init__(
            'No rule convention file found',
            detail)


class InsufficientPermissionsError(Error):
    def __init__(self, descr=None, detail=None):
        super(InsufficientPermissionsError, self).__init__(
            'Insufficent file system permissions',
            descr, detail,
            "Insufficent permissions to file or directory in the file's path")


class NoGoogleCredentialsFileError(NoFileError):
    def __init__(self, detail=None):
        super(NoGoogleCredentialsFileError, self).__init__(
            'No Google credentials file found',
            detail)


class BadGoogleCredentialsFileError(Error):
    def __init__(self, descr=None, detail=None, hint=None):
        super(BadGoogleCredentialsFileError, self).__init__(
            'Bad Google credentials JSON file', descr, detail, hint)


class NotAServiceAccountError(BadGoogleCredentialsFileError):
    def __init__(self, detail=None, hint=None):
        super(NotAServiceAccountError, self).__init__(
            'File is not a service account credentials file',
            detail, hint)


class InvalidGoogleCredentialsError(BadGoogleCredentialsFileError):
    def __init__(self, detail=None, hint=None):
        super(InvalidGoogleCredentialsError, self).__init__(
            'Service account credentials file is invalid',
            detail, hint)


class HttpLib2Error(Error):
    def __init__(self, lib_error=None, hint=None):
        super(HttpLib2Error, self).__init__(
            'Network failure',
            'HTTP network connection failure',
            ('The httplib2 network library reports: {0}'
             .format(repr(lib_error))),
            hint)


class GoogleAPIError(Error):
    def __init__(self, err, descr, google_error=None, hint=None):
        super(GoogleAPIError, self).__init__(
            err, descr,
            'The Google API reports: {0}'.format(repr(google_error)),
            hint)


class GoogleAuthorizationError(GoogleAPIError):
    def __init__(self, google_error=None, hint=None):
        super(GoogleAuthorizationError, self).__init__(
            'Google authorization failed',
            'Unable to establish an authorized connection with Google',
            google_error, hint)


class GoogleDriveError(GoogleAPIError):
    def __init__(self, descr=None, google_error=None, hint=None):
        super(GoogleDriveError, self).__init__(
            'Unable to connect with Google Drive',
            descr, google_error, hint)


class GoogleDriveConnectionError(GoogleDriveError):
    def __init__(self, google_error=None, hint=None):
        super(GoogleDriveConnectionError, self).__init__(
            'The Google API cannot establish a connection with Google Drive',
            google_error, hint)


class GoogleListError(GoogleDriveError):
    def __init__(self, google_error=None, hint=None):
        super(GoogleListError, self).__init__(
            'The Google API cannot get information on the spreadsheet',
            google_error, hint)


class InvalidGoogleListCallError(GoogleListError):
    def __init__(self, google_error=None, hint=None):
        super(InvalidGoogleListCallError, self).__init__(
            google_error,
            ('Are there quotes or other invalid characters '
             'in the spreadsheet name?  Do not quote the '
             'spreadsheet name in the configuration file.'))


class GoogleDownloadError(GoogleDriveError):
    def __init__(self, google_error=None, hint=None):
        super(GoogleDownloadError, self).__init__(
            'The Google API cannot download the spreadsheet',
            google_error, hint)


class DownloadedSheetError(Error):
    def __init__(self, descr=None, detail=None, hint=None):
        super(DownloadedSheetError, self).__init__(
            'Unable to download sheet from Google',
            descr, detail, hint)


class NoSheetDownloadedError(DownloadedSheetError):
    def __init__(self, detail=None, hint=None):
        super(NoSheetDownloadedError, self).__init__(
            'The download did not contain a sheet',
            detail, hint)


class NoSuchSpreadsheetError(DownloadedSheetError):
    def __init__(self, detail=None, hint=None):
        super(NoSuchSpreadsheetError, self).__init__(
            'The named spreadsheet cannot be found',
            detail, hint)


class ConfigParserError(Error):
    def __init__(self, detail, err):
        super(ConfigParserError, self).__init__(
            'Bad configuration file',
            detail,
            'The configparser library reports: {0}'.format(err))


class NoEnforcerSectionError(Error):
    def __init__(self, detail=None):
        super(NoEnforcerSectionError, self).__init__(
            'Missing configuration section',
            'The required "enforcer" section is missing',
            detail)


class UnknownDeclarationError(Error):
    def __init__(self, key):
        super(UnknownDeclarationError, self).__init__(
            'Unknown configuration declaration',
            'The configuration file contains an unknown declaration',
            'The "{0}" declaration is unrecognized'.format(key))


class MissingConfigDeclarationError(Error):
    def __init__(self, detail=None, hint=None):
        super(MissingConfigDeclarationError, self).__init__(
            'Missing configuration declaration',
            'A required configuration entry is missing',
            detail, hint)


class BadMaxDepthError(Error):
    def __init__(self, detail=None):
        super(BadMaxDepthError, self).__init__(
            'Not a positive integer',
            'Bad max_depth configuration value',
            detail)


class BadRulesSourceError(Error):
    def __init__(self, err, detail=None):
        super(BadRulesSourceError, self).__init__(
            err,
            'Bad rules_source configuration value',
            detail)


class BadRowError(Error):
    def __init__(self, descr=None, detail=None):
        super(BadRowError, self).__init__('Bad row', descr, detail)


class EmptyRowError(BadRowError):
    def __init__(self, detail=None):
        super(EmptyRowError, self).__init__(
            'No data in row, except possibly a comment',
            detail)


class SkippedSubDirError(BadRowError):
    def __init__(self, detail=None):
        super(SkippedSubDirError, self).__init__(
            'No parent folder',
            detail)


class ParentWithChildError(BadRowError):
    def __init__(self, detail=None):
        super(ParentWithChildError, self).__init__(
            'One row describes both a folder and folder content',
            detail)


class MissingRulesError(Error):
    def __init__(self, descr=None, detail=None):
        super(MissingRulesError, self).__init__(
            'No storage conventions found',
            descr,
            detail)


class NoMarkerError(MissingRulesError):
    def __init__(self, detail=None):
        super(NoMarkerError, self).__init__(
            'Beginning of rule marker not found in file',
            detail)


class NoRulesError(MissingRulesError):
    def __init__(self, detail=None):
        super(NoRulesError, self).__init__(
            'No naming convention rules found after beginning of rule marker',
            detail)


class RuleError(Error):
    def __init__(self, descr, detail=None, hint=None):
        super(RuleError, self).__init__(
            'Error in storage convention rule',
            descr, detail, hint)


class PatternError(RuleError):
    def __init__(self, descr, rownum, grammererror, hint=None):
        super(PatternError, self).__init__(
            descr=descr,
            detail=(('Row number {0}:\n'
                     '   [{1}]\n'
                     '    {2}^').format(rownum,
                                        grammererror.lexdata,
                                        ' ' * grammererror.lexpos)),
            hint=hint)


class ScanError(PatternError):
    def __init__(self, rownum, grammererror):
        super(ScanError, self).__init__(
            ('Unable to recognize the pattern component(s) in ("{0}")'
             .format(grammererror.badval)),
            rownum, grammererror,
            hint=('There may be invalid character(s), '
                  'missing required character(s), '
                  'or out-of-order character(s) '
                  'at or after the indicated location'))


class ParseError(PatternError):
    def __init__(self, rownum, grammererror):
        # Point to the beginning of the bad token, instead of the start
        # of the next token.
        grammererror.lexpos -= len(grammererror.badval)
        super(ParseError, self).__init__(
            ('Unexpected pattern component ("{0}")'
             .format(grammererror.badval)),
            rownum, grammererror,
            hint=('The component may be in the wrong place '
                  ' or a prior required component may be missing'))


class ParseEOFError(RuleError):
    def __init__(self, rownum, pattern):
        super(ParseEOFError, self).__init__(
            descr='End-of-pattern reached unexpectedly',
            detail=(('Row number {0}:\n'
                     '   [{1}]').format(rownum, pattern)))


#
# Exceptions from the parser
#

class PLYError(Exception):
    pass


class GrammerError(PLYError):
    def __init__(self, lexpos, lexdata, badval):
        super(GrammerError, self).__init__()
        self.lexpos = lexpos
        self.lexdata = lexdata
        self.badval = badval


class LexError(GrammerError):
    def __init__(self, lexpos, lexdata, badval):
        super(LexError, self).__init__(lexpos, lexdata, badval)


class YaccError(GrammerError):
    def __init__(self, lexpos, lexdata, badval):
        super(YaccError, self).__init__(lexpos, lexdata, badval)


class YaccEOFError(PLYError):
    def __init__(self):
        super(YaccEOFError, self).__init__()


#
# Functions
#

def express(exception):
    '''Report an exception (without traceback)'''
    sys.stderr.write(
        '-------------------------------------\n{0}\n'.format(exception))
