// Context.h (Oclgrind)
// Copyright (c) 2013-2019, James Price and Simon McIntosh-Smith,
// University of Bristol. All rights reserved.
//
// This program is provided under a three-clause BSD license. For full
// license terms please see the LICENSE file distributed with this
// source code.

#include "common.h"

namespace llvm
{
class LLVMContext;
}

namespace oclgrind
{
class KernelInvocation;
class Memory;
class Plugin;
class WorkGroup;
class WorkItem;

typedef std::pair<Plugin*, bool> PluginEntry;
typedef std::list<PluginEntry> PluginList;

class Context
{
public:
  Context();
  virtual ~Context();

  Memory* getGlobalMemory() const;
  llvm::LLVMContext* getLLVMContext() const;
  bool isThreadSafe() const;
  void logError(const char* error) const;

  // Simulation callbacks
  void notifyInstructionExecuted(const WorkItem* workItem,
                                 const llvm::Instruction* instruction,
                                 const TypedValue& result) const;
  void notifyKernelBegin(const KernelInvocation* kernelInvocation) const;
  void notifyKernelEnd(const KernelInvocation* kernelInvocation) const;
  void notifyMemoryAllocated(const Memory* memory, size_t address, size_t size,
                             cl_mem_flags flags, const uint8_t* initData) const;
  void notifyMemoryAtomicLoad(const Memory* memory, AtomicOp op, size_t address,
                              size_t size) const;
  void notifyMemoryAtomicStore(const Memory* memory, AtomicOp op,
                               size_t address, size_t size) const;
  void notifyMemoryDeallocated(const Memory* memory, size_t address) const;
  void notifyMemoryLoad(const Memory* memory, size_t address,
                        size_t size) const;
  void notifyMemoryMap(const Memory* memory, size_t address, size_t offset,
                       size_t size, cl_map_flags flags) const;
  void notifyMemoryStore(const Memory* memory, size_t address, size_t size,
                         const uint8_t* storeData) const;
  void notifyMessage(MessageType type, const char* message) const;
  void notifyMemoryUnmap(const Memory* memory, size_t address,
                         const void* ptr) const;
  void notifyWorkGroupBarrier(const WorkGroup* workGroup, uint32_t flags) const;
  void notifyWorkGroupBegin(const WorkGroup* workGroup) const;
  void notifyWorkGroupComplete(const WorkGroup* workGroup) const;
  void notifyWorkItemBegin(const WorkItem* workItem) const;
  void notifyWorkItemComplete(const WorkItem* workItem) const;

  // Plugins
  void registerPlugin(Plugin* plugin);
  void unregisterPlugin(Plugin* plugin);

private:
  mutable const KernelInvocation* m_kernelInvocation;
  Memory* m_globalMemory;

  PluginList m_plugins;
  std::list<void*> m_pluginLibraries;
  void loadPlugins();
  void unloadPlugins();

  llvm::LLVMContext* m_llvmContext;

public:
  class Message
  {
  public:
    enum special
    {
      INDENT,
      UNINDENT,
      CURRENT_KERNEL,
      CURRENT_WORK_ITEM_GLOBAL,
      CURRENT_WORK_ITEM_LOCAL,
      CURRENT_WORK_GROUP,
      CURRENT_ENTITY,
      CURRENT_LOCATION,
    };

    Message(MessageType type, const Context* context);

    Message& operator<<(const special& id);
    Message& operator<<(const llvm::Instruction* instruction);

    template <typename T> Message& operator<<(const T& t);
    Message& operator<<(std::ostream& (*t)(std::ostream&));
    Message& operator<<(std::ios& (*t)(std::ios&));
    Message& operator<<(std::ios_base& (*t)(std::ios_base&));

    void send() const;

  private:
    MessageType m_type;
    const Context* m_context;
    const KernelInvocation* m_kernelInvocation;
    mutable std::stringstream m_stream;
    std::list<int> m_indentModifiers;
  };
};

template <typename T> Context::Message& Context::Message::operator<<(const T& t)
{
  m_stream << t;
  return *this;
}
} // namespace oclgrind
