/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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.
 */

#ifndef ACSDKNOTIFICATIONS_NOTIFICATIONRENDERER_H_
#define ACSDKNOTIFICATIONS_NOTIFICATIONRENDERER_H_

#include <mutex>
#include <unordered_set>

#include <acsdkApplicationAudioPipelineFactoryInterfaces/ApplicationAudioPipelineFactoryInterface.h>
#include <acsdkManufactory/Annotated.h>
#include <acsdkNotificationsInterfaces/NotificationRendererInterface.h>
#include <acsdkShutdownManagerInterfaces/ShutdownNotifierInterface.h>
#include <AVSCommon/AVS/MixingBehavior.h>
#include <AVSCommon/SDKInterfaces/AudioFocusAnnotation.h>
#include <AVSCommon/SDKInterfaces/FocusManagerInterface.h>
#include <AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h>
#include <AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h>
#include <AVSCommon/Utils/MediaType.h>
#include <AVSCommon/Utils/RequiresShutdown.h>
#include <AVSCommon/Utils/Threading/Executor.h>

namespace alexaClientSDK {
namespace acsdkNotifications {

/// String to identify the notifications media player to render audio.
static const constexpr char* NOTIFICATIONS_MEDIA_PLAYER_NAME = "NotificationsMediaPlayer";

/**
 * Implementation of NotificationRendererInterface using the @c
 * MediaPlayerInterface
 */
class NotificationRenderer
        : public acsdkNotificationsInterfaces::NotificationRendererInterface
        , public avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface
        , public avsCommon::utils::RequiresShutdown
        , public avsCommon::sdkInterfaces::ChannelObserverInterface
        , public std::enable_shared_from_this<NotificationRenderer> {
public:
    /// A type that identifies which source is currently being operated on.
    using SourceId = avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId;

    /**
     * Create a NotificationRenderer. The new NotificationRenderer starts life in
     * the IDLE state, awaiting request to render notifications.
     *
     * @param audioPipelineFactory The @c ApplicationAudioPlayerInterface instance to use to create the notifications
     * media player for rendering audio.
     * @param audioFocusManager The annotated @c FocusManager instance to use to request audio
     * focus.
     * @param shutdownNotifier The object to register with to receive notification when it is time to shutdown.
     * @return The new NotificationRenderer, or null if the operation fails.
     */
    static std::shared_ptr<NotificationRendererInterface> createNotificationRendererInterface(
        const std::shared_ptr<acsdkApplicationAudioPipelineFactoryInterfaces::ApplicationAudioPipelineFactoryInterface>&
            audioPipelineFactory,
        acsdkManufactory::Annotated<
            avsCommon::sdkInterfaces::AudioFocusAnnotation,
            avsCommon::sdkInterfaces::FocusManagerInterface> audioFocusManager,
        const std::shared_ptr<acsdkShutdownManagerInterfaces::ShutdownNotifierInterface>& shutdownNotifier);

    /**
     * Create a NotificationRenderer.  The new NotificationRenderer starts life in
     * the IDLE state, awaiting request to render notifications.
     *
     * @param mediaPlayer The @c MediaPlayer instance to use to render audio.
     * @param focusManager The @c FocusManager instance to use to request audio
     * focus.
     * @return The new NotificationRenderer, or null if the operation fails.
     */
    static std::shared_ptr<NotificationRenderer> create(
        std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerInterface> mediaPlayer,
        std::shared_ptr<avsCommon::sdkInterfaces::FocusManagerInterface> focusManager);

    /// @name NotificationRendererInterface methods
    /// @{
    void addObserver(
        std::shared_ptr<acsdkNotificationsInterfaces::NotificationRendererObserverInterface> observer) override;
    void removeObserver(
        std::shared_ptr<acsdkNotificationsInterfaces::NotificationRendererObserverInterface> observer) override;
    bool renderNotification(
        std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> audioFactory,
        const std::string& url) override;
    bool cancelNotificationRendering() override;
    /// @}

    /// @name MediaPlayerObserverInterface methods
    /// @{
    void onFirstByteRead(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override;
    void onPlaybackStarted(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override;
    void onPlaybackStopped(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override;
    void onPlaybackFinished(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override;
    void onPlaybackError(
        SourceId sourceId,
        const avsCommon::utils::mediaPlayer::ErrorType& type,
        std::string error,
        const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override;
    /// @}

    /// @name RequiresShutdown methods
    /// @{
    void doShutdown() override;
    /// @}

    /// @name ChannelObserverInterface methods
    /// @{
    void onFocusChanged(alexaClientSDK::avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior)
        override;
    /// @}

private:
    /**
     * The different states that a NotificationRenderer may be in.  The normal
     * flow of states is: IDLE -> RENDERING_PREFERRED -> (if failed,
     * RENDERING_DEFAULT) -> NOTIFYING -> IDLE. If @c
     * cancelNotificationRendering() is called during rendering, the state can
     * also transition from {RENDERING_PREFERRED|RENDERING_DEFAULT} -> CANCELLING
     * -> NOTIFYING -> IDLE.
     */
    enum class State {
        /**
         * Awaiting a request to render a notification. Transitions to:
         * - RENDERING_PREFERRED when a new rendering request is received.
         */
        IDLE,
        /**
         * Rendering the preferred audio asset. Requests to render while already
         * rendering are refused, not queued). Transitions to:
         * - RENDERING_DEFAULT if rendering the preferred asset fails.
         * - CANCELLING if a request is made to cancel rendering.
         * - NOTIFYING if rendering the preferred asset completes.
         */
        RENDERING_PREFERRED,
        /**
         * Rendering the default audio asset. Requests to render while already
         * rendering are refused, not queued). Transitions to:
         * - CANCELLING if a request is made to cancel rendering.
         * - NOTIFYING if rendering the default asset completes.
         */
        RENDERING_DEFAULT,
        /**
         * Canceling a request to render a notification. Transitions to:
         * - NOTIFYING once cancellation has completed.
         */
        CANCELLING,
        /**
         * Notifying that rendering finished (even if rendering failed or was
         * cancelled). Transitions to:
         * - IDLE once callbacks to observers have returned.
         */
        NOTIFYING
    };

    /**
     * Constructor.
     *
     * @param mediaPlayer The @c MediaPlayer instance to use to render audio.
     * @param focusManager The @c FocusManager instance to use to request audio
     * focus.
     */
    NotificationRenderer(
        std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerInterface> mediaPlayer,
        std::shared_ptr<avsCommon::sdkInterfaces::FocusManagerInterface> focusManager);

    /**
     * Handle the completion of rendering an audio asset, whether successful or
     * not.
     *
     * @param sourceId The @c SourceId of the audio that is expected to be
     * playing.
     */
    void onRenderingFinished(SourceId sourceId);

    /**
     * Set a new state - rejecting invalid state changes.
     *
     * @param newState The new state to enter.
     * @return Whether the new state was accepted.
     */
    bool setState(State newState);

    /**
     * Set a new state - rejecting invalid state changes. @note Caller must be
     * holdling m_mutex.
     *
     * @param newState The new state to enter.
     * @return Whether the new state was accepted.
     */
    bool setStateLocked(State newState);

    /**
     * Verify that audio rendering is in progress.
     *
     * @param caller The name of the calling method (for logging a more useful
     * error)
     * @param sourceId The @c SourceId to verify.
     * @return Whether the specified @c SourceId matches @c m_sourceId.
     */
    bool verifyRenderingLocked(const std::string& caller, SourceId sourceId);

    /// The mediaPlayer with which to render the notification.
    std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerInterface> m_mediaPlayer;

    /// The Focus Manager used to request focus when a notification sound is
    /// played.
    std::shared_ptr<avsCommon::sdkInterfaces::FocusManagerInterface> m_focusManager;

    /// The current focus state of NotificationRenderer, which is used to
    /// determine when a notification can be rendered.
    avsCommon::avs::FocusState m_focusState;

    /// The observers to notify when rendering is finished.  Access serialized by
    /// @c m_mutex.
    std::unordered_set<std::shared_ptr<acsdkNotificationsInterfaces::NotificationRendererObserverInterface>>
        m_observers;

    /// Mutex to serialize access to various data members.
    std::mutex m_mutex;

    /// Used to wake a thread waiting for a state change.
    std::condition_variable m_wakeTrigger;

    /// Current state. Access serialized by @c m_mutex.
    State m_state;

    /// Factory for creating pair of @c istream and media type char instances
    /// containing the default audio asset.
    std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> m_audioFactory;

    /// The id associated with the media that our MediaPlayer is currently
    /// handling.
    SourceId m_sourceId;

    /// Future used to capture result from std::async() call used if
    /// PLAYBACK_PREFERRED fails.
    std::future<void> m_renderFallbackFuture;

    /**
     * The @c Executor which queues up operations from asynchronous API calls.
     *
     * @note This declaration needs to come *after* the Executor Thread Variables
     * above so that the thread shuts down before the Executor Thread Variables
     * are destroyed.
     */
    avsCommon::utils::threading::Executor m_executor;

    /// Friend relationship to allow accessing State to convert it to a string for
    /// logging.
    friend std::ostream& operator<<(std::ostream& stream, const NotificationRenderer::State state);
};

}  // namespace acsdkNotifications
}  // namespace alexaClientSDK

#endif  // ACSDKNOTIFICATIONS_NOTIFICATIONRENDERER_H_
