// Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

#include <carb/Defines.h>
#include <carb/Framework.h>
#include <carb/livestream/StreamServer.h>

#include <algorithm>
#include <utility>


namespace carb
{
namespace livestream
{

/**
 * Defines the wrapper around stream server interface and its context.
 */
class StreamServerWrapper
{
public:
    /**
     *  Constructs an uninitilized wrapper object.
     */
    inline StreamServerWrapper(){};

    /**
     *  Deinitialize and destroys wrapper object.
     */
    virtual ~StreamServerWrapper()
    {
        done();
    };

    inline void swap(StreamServerWrapper& other)
    {
        std::swap(m_interface, other.m_interface);
        std::swap(m_context, other.m_context);
    }

public:
    /**
     * Initialize the wrapper instance.
     *
     * @param[in] framework Pointer to a framework interface to use.
     * @param[in] graphics Pointer to a graphics interface to use.
     * @param[in] graphicsMux Pointer to optional graphics multiplexing API for async CUDA command recording or nullptr.
     * @param[in] device Pointer to a graphics device instance.
     * @return true if successfull.
     */
    bool init(carb::Framework* framework,
              carb::graphics::Graphics* graphics,
              carb::graphicsmux::GraphicsMux* graphicsMux,
              carb::graphics::Device* device)
    {
        m_interface = framework->tryAcquireInterface<carb::livestream::Vision>();
        if (!m_interface)
        {
            return false;
        }

        m_context = m_interface->createContext(graphics, graphicsMux, device);
        if (!m_context)
        {
            return false;
        }

        return true;
    }

    /**
     * Deinitialize the wrapper instance.
     */
    void done()
    {
        if (m_interface && m_context)
        {
            m_interface->stopServer(m_context);
            m_interface->destroyContext(m_context);
            m_context = nullptr;
        }
    }

    /**
     *  Check if the wrapper instance is initialized properly.
     *
     *  @return true if the wrapper is initialized properly.
     */
    inline bool isValid() const
    {
        return (m_interface && m_context);
    }

    /**
     * Binds input interfaces to a server context.
     *
     * @param keyboard Pointer to the keyboard input interface.
     * @param mouse Pointer to the mouse input interface.
     * @param gamepad Pointer to the gamepad input interface.
     */
    void bindInput(carb::input::Keyboard* keyboard, carb::input::Mouse* mouse, carb::input::Gamepad* gamepad)
    {
        if (m_interface && m_context)
        {
            m_interface->bindInput(m_context, keyboard, mouse, gamepad);
        }
    }

    /**
     * Pump the remote client cached input events into carb::input.
     * This function must be called from main thread.
     */
    void updateInput()
    {
        if (m_interface && m_context)
        {
            m_interface->updateInput(m_context);
        }
    }

    /**
     * Starts streaming server.
     *
     * @return true if server started successfully.
     */
    bool start()
    {
        if (m_interface && m_context)
        {
            return m_interface->startServer(m_context);
        }
        return false;
    }

    /**
     * Stops streaming server.
     */
    void stop()
    {
        if (m_interface && m_context)
        {
            m_interface->stopServer(m_context);
        }
    }

    /**
     * Get remote client cached width and height.
     *
     * @param extent Pointer to an extent destination.
     * @return true if extent is filled with valid data.
     */
    bool getRemoteClientExtent(carb::Uint2* extent)
    {
        if (m_interface && m_context && extent)
        {
            return m_interface->getRemoteClientExtent(m_context, extent);
        }
        return false;
    }

    /**
     * Records a command to send a frame from a CUDA buffer memory.
     *
     * @param commandList Pointer to the command list to use.
     * @param cudaMemory Unified pointer to the CUDA buffer memory.
     * @param pitch The frame row pitch in bytes.
     * @param width The frame width in pixels.
     * @param height The frame height in rows.
     */
    void cmdSendCudaMemoryFrame(
        carb::graphicsmux::CommandList* commandList, void* cudaMemory, uint32_t pitch, uint32_t width, uint32_t height)
    {
        if (m_interface && m_context)
        {
            m_interface->cmdSendCudaMemoryFrame(m_context, commandList, cudaMemory, pitch, width, height);
        }
    }

    /**
     * Enqueue a frame from a CUDA buffer memory.
     *
     * @param cudaMemory Unified pointer to the CUDA buffer memory.
     * @param pitch The frame row pitch in bytes.
     * @param width The frame width in pixels.
     * @param height The frame height in rows.
     */
    void sendCudaMemoryFrame(void* cudaMemory, uint32_t pitch, uint32_t width, uint32_t height)
    {
        if (m_interface && m_context)
        {
            m_interface->sendCudaMemoryFrame(m_context, cudaMemory, pitch, width, height);
        }
    }

    /**
     * Enqueue a frame from a CUDA buffer external memory.
     *
     * @param cudaExternalMemory Pointer to the CUDA buffer external memory.
     * @param pitch The frame row pitch in bytes.
     * @param width The frame width in pixels
     * @param height The frame height in rows
     */
    void sendCudaExternalMemoryFrame(carb::cudainterop::ExternalMemory* cudaExternalMemory,
                                     uint32_t pitch,
                                     uint32_t width,
                                     uint32_t height)
    {
        if (m_interface && m_context)
        {
            m_interface->sendCudaExternalMemoryFrame(m_context, cudaExternalMemory, pitch, width, height);
        }
    }

    /**
     * Enqueue a frame from a shared texture.
     *
     * @param textureHandle Pointer to the shared texture handle description.
     * @param texture Pointer to the texture object.
     */
    void sendSharedTextureFrame(carb::graphics::SharedResourceHandle* textureHandle, carb::graphics::Texture* texture)
    {
        if (m_interface && m_context)
        {
            m_interface->sendSharedTextureFrame(m_context, textureHandle, texture);
        }
    }

    /**
     * Enqueue an audio samples.
     *
     * @param buf Pointer to the auduo samples buffer.
     * @param size Audio samples buffer size in bytes.
     * @param time Audio samples starting time in milleseconds.
     * @param desc Audio samples description.
     */
    void sendAudioSamples(const void* buf, size_t size, uint32_t time, const AudioSamplesDesc& desc)
    {
        if (m_interface && m_context)
        {
            m_interface->sendAudioSamples(m_context, buf, size, time, desc);
        }
    }

private:
    StreamServerWrapper(const StreamServerWrapper&) = delete;
    StreamServerWrapper& operator=(const StreamServerWrapper&) = delete;

private:
    carb::livestream::Vision* m_interface = nullptr;
    carb::livestream::Context* m_context = nullptr;
};

} // namespace livestream
} // namespace carb
