#pragma once

#include <cstring>
#include <fcntl.h>
#include <semaphore.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

namespace ipc {

constexpr size_t MAX_PATH_LEN = 256;
constexpr size_t QUEUE_CAPACITY = 128;

struct shared_queue {
  sem_t mutex;
  sem_t slots;
  sem_t items;
  size_t head;
  size_t tail;
  char data[QUEUE_CAPACITY][MAX_PATH_LEN];
  bool initialized;
};

class queue {
public:
  queue(const char *shm_name) {
    shm_fd_ = ::shm_open(shm_name, O_CREAT | O_RDWR, 0666);
    if (shm_fd_ < 0)
      throw std::runtime_error("shm_open failed");

    if (::ftruncate(shm_fd_, sizeof(shared_queue)) < 0)
      throw std::runtime_error("ftruncate failed");

    ptr_ = static_cast<shared_queue *>(::mmap(nullptr, sizeof(shared_queue),
                                              PROT_READ | PROT_WRITE,
                                              MAP_SHARED, shm_fd_, 0));
    if (ptr_ == MAP_FAILED)
      throw std::runtime_error("mmap failed");

    if (!ptr_->initialized) {
      initialize();
      ptr_->initialized = true;
    }
  }

  ~queue() {
    ::munmap(ptr_, sizeof(shared_queue));
    ::close(shm_fd_);
  }

  void push(const std::string &s) {
    if (s.size() >= MAX_PATH_LEN)
      throw std::length_error("path too long");
    ::sem_wait(&ptr_->slots);
    ::sem_wait(&ptr_->mutex);

    std::strncpy(ptr_->data[ptr_->tail], s.c_str(), MAX_PATH_LEN);
    ptr_->tail = (ptr_->tail + 1) % QUEUE_CAPACITY;

    ::sem_post(&ptr_->mutex);
    ::sem_post(&ptr_->items);
  }

  bool pop(std::string &out) {
    ::sem_wait(&ptr_->items);
    ::sem_wait(&ptr_->mutex);

    char buf[MAX_PATH_LEN];
    std::strncpy(buf, ptr_->data[ptr_->head], MAX_PATH_LEN);
    ptr_->head = (ptr_->head + 1) % QUEUE_CAPACITY;

    ::sem_post(&ptr_->mutex);
    ::sem_post(&ptr_->slots);

    out = buf;
    return true;
  }

private:
  int shm_fd_;
  shared_queue *ptr_;

  void initialize() {
    ::sem_init(&ptr_->mutex, 1, 1);
    ::sem_init(&ptr_->slots, 1, QUEUE_CAPACITY);
    ::sem_init(&ptr_->items, 1, 0);
    ptr_->head = ptr_->tail = 0;
  }
};
} // namespace ipc
