# Copyright (C) 2012-2013 Science and Technology Facilities Council.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

class CrabError(Exception):
    """Base class for exceptions raised internally by crab.

    Library functions should re-raise expected exceptions as a
    CrabError to allow them to be trapped conveniently without
    accidentally trapping other errors."""
    pass

class CrabStatus:
    """Helper class for status codes.

    The crab libraries should refer to codes by the symbolic
    names given in this class.

    VALUES is the set of status codes which should be accepted from
    a client.  Other codes are for internal use, such as those
    generated by the monitor."""

    SUCCESS = 0
    FAIL = 1
    UNKNOWN = 2
    COULDNOTSTART = 3
    ALREADYRUNNING = 4

    VALUES = set([SUCCESS, FAIL, UNKNOWN, COULDNOTSTART, ALREADYRUNNING])

    # Additional internal status values (it is not valid for
    # a client to send these).  Also some of these are less bad
    # than the client statuses.  For example, if something has a 
    # status of FAIL, you don't want to change it to just LATE.
    LATE = -1
    MISSED = -2
    TIMEOUT = -3
    CLEARED = -4

    INTERNAL_VALUES = set([LATE, MISSED, TIMEOUT, CLEARED])

    _error_names = ['Succeeded', 'Failed', 'Unknown', 'Could not start',
                    'Already running']
    _alarm_names = ['Late', 'Missed', 'Timed out', 'Cleared']

    @staticmethod
    def get_name(status):
        """Returns a readable name for the given status code."""
        try:
            if status is None:
                return 'Undefined'
            elif status >= 0:
                # TODO: find out if this can be referred to without class name?
                return CrabStatus._error_names[status]
            else:
                return CrabStatus._alarm_names[(-1) - status]
        except IndexError:
            return 'Status ' + int(status)

    @staticmethod
    def is_trivial(status):
        """Determines whether a status code is trivial and should
        mostly be ignored."""
        return status in (CrabStatus.LATE,
                          CrabStatus.CLEARED,
                          CrabStatus.ALREADYRUNNING)

    @staticmethod
    def is_ok(status):
        """Returns true if the code does not indicate any kind of problem."""
        return status == CrabStatus.SUCCESS or CrabStatus.is_trivial(status)

    @staticmethod
    def is_warning(status):
        """True if the given status is some kind of warning."""
        return status == CrabStatus.UNKNOWN or status == CrabStatus.MISSED

    @staticmethod
    def is_error(status):
        """True if the given status is an error, i.e. not OK and not a
        warning."""
        return not (CrabStatus.is_ok(status) or CrabStatus.is_warning(status))

class CrabEvent:
    """Helper class for crab events.

    Currently just provides symbolic names for the event types."""
    START = 1
    ALARM = 2
    FINISH = 3

    _event_names = ['Started', 'Alarm', 'Finished']

    @staticmethod
    def get_name(event):
        """Returns a readable name for the given event type code."""

        try:
            return CrabEvent._event_names[event - 1]

        except IndexError:
            return 'Event ' + int(event)
