# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Interactive feedback layer abstraction."""

from autotest_lib.client.common_lib import error


# All known queries.
#
# Audio playback and recording testing.
QUERY_AUDIO_PLAYBACK_SILENT = 0
QUERY_AUDIO_PLAYBACK_AUDIBLE = 1
QUERY_AUDIO_RECORDING = 2
# Motion sensor testing.
QUERY_MOTION_RESTING = 10
QUERY_MOTION_MOVING = 11
# USB keyboard plugging and typing.
QUERY_KEYBOARD_PLUG = 20
QUERY_KEYBOARD_TYPE = 21
# GPIO write/read testing.
QUERY_GPIO_WRITE = 30
QUERY_GPIO_READ = 31
# On-board light testing.
QUERY_LIGHT_ON = 40
# TODO(garnold) Camera controls testing.
#QUERY_CAMERA_???
# Power management testing.
QUERY_POWER_WAKEUP = 60

INPUT_QUERIES = set((
        QUERY_AUDIO_RECORDING,
        QUERY_MOTION_RESTING,
        QUERY_MOTION_MOVING,
        QUERY_KEYBOARD_PLUG,
        QUERY_KEYBOARD_TYPE,
        QUERY_GPIO_READ,
        QUERY_POWER_WAKEUP,
))

OUTPUT_QUERIES = set((
        QUERY_AUDIO_PLAYBACK_SILENT,
        QUERY_AUDIO_PLAYBACK_AUDIBLE,
        QUERY_GPIO_WRITE,
        QUERY_LIGHT_ON,
))

ALL_QUERIES = INPUT_QUERIES.union(OUTPUT_QUERIES)


# Feedback client definition.
#
class Client(object):
    """Interface for an interactive feedback layer."""

    def __init__(self):
        self._initialized = False
        self._finalized = False


    def _check_active(self):
        """Ensure that the client was initialized and not finalized."""
        if not self._initialized:
            raise error.TestError('Client was not initialized')
        if self._finalized:
            raise error.TestError('Client was already finalized')


    def __enter__(self):
        self._check_active()
        return self


    def __exit__(self, ex_type, ex_val, ex_tb):
        self.finalize()


    def initialize(self, test, host=None):
        """Initializes the feedback object.

        This method should be called once prior to any other call.

        @param test: An object representing the test case.
        @param host: An object representing the DUT; required for server-side
                     tests.

        @raise TestError: There was an error during initialization.
        """
        if self._initialized:
            raise error.TestError('Client was already initialized')
        if self._finalized:
            raise error.TestError('Client was already finalized')
        self._initialize_impl(test, host)
        self._initialized = True
        return self


    def _initialize_impl(self, test, host):
        """Implementation of feedback client initialization.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError


    def new_query(self, query_id):
        """Instantiates a new query.

        @param query_id: A query identifier (see QUERY_ constants above).

        @return A query object.

        @raise TestError: Query is invalid or not supported.
        """
        self._check_active()
        return self._new_query_impl(query_id)


    def _new_query_impl(self, query_id):
        """Implementation of new query instantiation.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError


    def finalize(self):
        """Finalizes the feedback object.

        This method should be called once when done using the client.

        @raise TestError: There was an error while finalizing the client.
        """
        self._check_active()
        self._finalize_impl()
        self._finalized = True


    def _finalize_impl(self):
        """Implementation of feedback client finalization.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError


# Feedback query definitions.
#
class _Query(object):
    """Interactive feedback query base class.

    This class is further derived and should not be inherited directly.
    """

    def __init__(self):
        self._prepare_called = False
        self._validate_called = False


    def prepare(self, **kwargs):
        """Prepares the tester for providing or capturing feedback.

        @raise TestError: Query preparation failed.
        """
        if self._prepare_called:
            raise error.TestError('Prepare was already called')
        self._prepare_impl(**kwargs)
        self._prepare_called = True


    def _prepare_impl(self, **kwargs):
        """Implementation of query preparation logic.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError


    def validate(self, **kwargs):
        """Validates the interactive input/output result.

        This enforces that the method is called at most once, then delegates
        to an underlying implementation method.

        @raise TestError: An error occurred during validation.
        @raise TestFail: Query validation failed.
        """
        if self._validate_called:
            raise error.TestError('Validate was already called')
        self._validate_impl(**kwargs)
        self._validate_called = True


    def _validate_impl(self, **kwargs):
        """Implementation of query validation logic.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError


class OutputQuery(_Query):
    """Interface for an output interactive feedback query.

    This class mandates that prepare() is called prior to validate().
    Subclasses should override implementations of _prepare_impl() and
    _validate_impl().
    """

    def __init__(self):
        super(OutputQuery, self).__init__()


    def validate(self, **kwargs):
        """Validates the interactive input/output result.

        This enforces the precondition and delegates to the base method.

        @raise TestError: An error occurred during validation.
        @raise TestFail: Query validation failed.
        """
        if not self._prepare_called:
            raise error.TestError('Prepare was not called')
        super(OutputQuery, self).validate(**kwargs)


class InputQuery(_Query):
    """Interface for an input interactive feedback query.

    This class mandates that prepare() is called first, then emit(), and
    finally validate(). Subclasses should override implementations of
    _prepare_impl(), _emit_impl() and _validate_impl().
    """

    def __init__(self):
        super(InputQuery, self).__init__()
        self._emit_called = False


    def validate(self, **kwargs):
        """Validates the interactive input/output result.

        This enforces the precondition and delegates to the base method.

        @raise TestError: An error occurred during validation.
        @raise TestFail: Query validation failed.
        """
        if not self._emit_called:
            raise error.TestError('Emit was not called')
        super(InputQuery, self).validate(**kwargs)


    def emit(self):
        """Instructs the tester to emit a feedback to be captured by the test.

        This enforces the precondition and ensures the method is called at most
        once, then delegates to an underlying implementation method.

        @raise TestError: An error occurred during emission.
        """
        if not self._prepare_called:
            raise error.TestError('Prepare was not called')
        if self._emit_called:
            raise error.TestError('Emit was already called')
        self._emit_impl()
        self._emit_called = True


    def _emit_impl(self):
        """Implementation of query emission logic.

        This should be implemented in concrete subclasses.
        """
        raise NotImplementedError