//
// Copyright 2012 Advanced Micro Devices, Inc. All rights reserved.
//

/*! \file commandqueue.hpp
 *  \brief  Declarations CommandQueue object.
 *
 * \author Laurent Morichetti (laurent.morichetti@amd.com)
 * \date   October 2008
 */

#ifndef COMMAND_QUEUE_HPP_
#define COMMAND_QUEUE_HPP_

#include "thread/thread.hpp"
#include "platform/object.hpp"
#include "platform/command.hpp"
/*! \brief Holds commands that will be executed on a specific device.
 *
 *  \details A command queue is created on a specific device in
 *  a Context. A new virtual device will be instantiated from the given
 *  device and an execution environment (a thread) will be created to run
 *  the CommandQueue::loop() function.
 */

namespace amd {

class HostQueue;
class DeviceQueue;

class CommandQueue : public RuntimeObject {
 public:
  static const uint RealTimeDisabled = 0xffffffff;
  enum class Priority : uint { Normal = 0, Medium, High };

  struct Properties {
    typedef cl_command_queue_properties value_type;
    const value_type mask_;
    value_type value_;

    Properties(value_type mask, value_type value) : mask_(mask), value_(value & mask) {}

    bool set(value_type bits) {
      if ((mask_ & bits) != bits) {
        return false;
      }
      value_ |= bits;
      return true;
    }

    bool clear(value_type bits) {
      if ((mask_ & bits) != bits) {
        return false;
      }
      value_ &= ~bits;
      return true;
    }

    bool test(value_type bits) const { return (value_ & bits) != 0; }
  };

  //! Return the context this command queue is part of.
  Context& context() const { return context_(); }

  //! Return the device for this command queue.
  Device& device() const { return device_; }

  //! Return the command queue properties.
  Properties properties() const { return properties_; }
  Properties& properties() { return properties_; }

  //! Returns the base class object
  CommandQueue* asCommandQueue() { return this; }

  virtual ~CommandQueue() {}

  //! Returns TRUE if the object was successfully created
  virtual bool create() = 0;

  //! RTTI internal implementation
  virtual ObjectType objectType() const { return ObjectTypeQueue; }

  //! Rturns HostQueue object
  virtual HostQueue* asHostQueue() { return NULL; }

  //! Returns DeviceQueue object
  virtual DeviceQueue* asDeviceQueue() { return NULL; }

  //! Returns the number or requested real time CUs
  uint rtCUs() const { return rtCUs_; }

  //! Returns the queue priority
  Priority priority() const { return priority_; }

 protected:
  //! CommandQueue constructor is protected
  //! to keep the CommandQueue class as a virtual interface
  CommandQueue(Context& context,                        //!< Context object
               Device& device,                          //!< Device object
               cl_command_queue_properties properties,  //!< Queue properties
               cl_command_queue_properties propMask,    //!< Queue properties mask
               uint rtCUs = RealTimeDisabled,           //!< Avaialble real time compute units
               Priority priority = Priority::Normal     //!< Queue priority
               )
      : properties_(propMask, properties),
        rtCUs_(rtCUs),
        priority_(priority),
        queueLock_("CommandQueue::queueLock"),
        device_(device),
        context_(context) {}

  Properties properties_;             //!< Queue properties
  uint rtCUs_;                        //!< The number of used RT compute units
  Priority priority_;                 //!< Queue priority
  Monitor queueLock_;                 //!< Lock protecting the queue
  Device& device_;                    //!< The device
  SharedReference<Context> context_;  //!< The context of this command queue

 private:
  //! Disable copy constructor
  CommandQueue(const CommandQueue&);

  //! Disable assignment
  CommandQueue& operator=(const CommandQueue&);
};


class HostQueue : public CommandQueue {
  class Thread : public amd::Thread {
   public:
    //! True if this command queue thread is accepting commands.
    volatile bool acceptingCommands_;

    //! Create a new thread
    Thread()
        : amd::Thread("Command Queue Thread", CQ_THREAD_STACK_SIZE),
          acceptingCommands_(false),
          virtualDevice_(NULL) {}

    //! The command queue thread entry point.
    void run(void* data) {
      HostQueue* queue = static_cast<HostQueue*>(data);
      virtualDevice_ = queue->device().createVirtualDevice(queue);
      if (virtualDevice_ != NULL) {
        queue->loop(virtualDevice_);
        if (virtualDevice_->terminate()) {
          delete virtualDevice_;
        }
      } else {
        acceptingCommands_ = false;
        queue->flush();
      }
    }

    //! Get virtual device for the current thread
    device::VirtualDevice* vdev() const { return virtualDevice_; }

   private:
    device::VirtualDevice* virtualDevice_;  //!< Virtual device for this thread

  } thread_;  //!< The command queue thread instance.

 private:
  ConcurrentLinkedQueue<Command*> queue_;  //!< The queue.

  Command* lastEnqueueCommand_;  //!< The last submitted command

  //! Await commands and execute them as they become ready.
  void loop(device::VirtualDevice* virtualDevice);

 protected:
  virtual bool terminate();

 public:
  /*! \brief Construct a new host queue.
   *
   * \note A new virtual device instance will be created from the
   * given device.
   */
  HostQueue(Context& context, Device& device, cl_command_queue_properties properties,
            uint queueRTCUs = 0, Priority priority = Priority::Normal);

  //! Returns TRUE if this command queue can accept commands.
  virtual bool create() { return thread_.acceptingCommands_; }

  //! Append the given command to the queue.
  void append(Command& command);

  //! Return the thread object running the command loop.
  const Thread& thread() const { return thread_; }

  //! Signal to start processing the commands in the queue.
  void flush() {
    ScopedLock sl(queueLock_);
    queueLock_.notify();
  }

  //! Finish all queued commands
  void finish();

  //! Check if hostQueue empty snapshot
  bool isEmpty();

  //! Get virtual device for the current command queue
  device::VirtualDevice* vdev() const { return thread_.vdev(); }

  //! Return the current queue as the HostQueue
  virtual HostQueue* asHostQueue() { return this; }

  //! Get last enqueued command
  Command* getLastQueuedCommand(bool retain);

  //! Set last enqueued command
  void setLastQueuedCommand(Command* lastCommand);
};


class DeviceQueue : public CommandQueue {
 public:
  DeviceQueue(Context& context,                        //!< Context object
              Device& device,                          //!< Device object
              cl_command_queue_properties properties,  //!< Queue properties
              uint size                                //!< Device queue size
              )
      : CommandQueue(context, device, properties, device.info().queueOnDeviceProperties_ |
                         CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT),
        size_(size),
        virtualDevice_(NULL) {}

  virtual ~DeviceQueue();

  //! Returns TRUE if device queue was successfully created
  virtual bool create();

  //! Return the current queue as the DeviceQueue
  virtual DeviceQueue* asDeviceQueue() { return this; }

  //! Returns the size of device queue
  uint size() const { return size_; }

  //! Returns virtual device for this device queue
  device::VirtualDevice* vDev() const { return virtualDevice_; }

  //! Returns the queue lock
  Monitor& lock() { return queueLock_; }

 private:
  uint size_;                             //!< Device queue size
  device::VirtualDevice* virtualDevice_;  //!< Virtual device for this queue
};
}  // namespace amd

#endif  // COMMAND_QUEUE_HPP_
