#pragma once

#include "ffmpegbuffer.h"
#include <cstdint>
#include <deque>
#include <emscripten/val.h>
#include <emscripten/wasm_worker.h>
#include <memory>
#include <optional>
#include <unordered_map>

EMSCRIPTEN_DECLARE_VAL_TYPE(AsyncWaitResult);

EMSCRIPTEN_DECLARE_VAL_TYPE(MipMapOutputBuffers);

class AsyncWaitPolyfill {
  bool hasWaitAsync;
  emscripten::val atomics;
  emscripten::val getGrowableHeapI32;

public:
  AsyncWaitPolyfill();

  AsyncWaitResult waitAsync(uintptr_t address, uint32_t expectedValue,
                            double timeout) const;
};

class MipMapRequest {
  // arguments
  std::shared_ptr<FfmpegAudioBuffer> buffer;
  int mipMapLevel;
  int start;
  int end;

  // results
  int blocksProduced;
  std::optional<int> continueFrom;
  std::array<std::vector<int16_t>, 2> outputs;

  // synchronization
  uint32_t status; // 0: queued, 1: completed, 2: cancelled

  void finalize(int finalStatus);
  static std::vector<int16_t> makeSharedMinMaxArray(int blockCount);
  AsyncWaitPolyfill asyncWaitPolyfill;

public:
  MipMapRequest(std::shared_ptr<FfmpegAudioBuffer> &&buffer, int mipMapLevel,
                int start, int end);
  ~MipMapRequest();

  MipMapRequest(const MipMapRequest &) = delete;
  MipMapRequest(MipMapRequest &&) = delete;
  MipMapRequest &operator=(const MipMapRequest &) = delete;
  MipMapRequest &operator=(MipMapRequest &&) = delete;

  int getStatus() const;
  int getBlocksProduced() const;
  std::optional<int> getContinueFrom() const;
  MipMapOutputBuffers getOutputBuffers() const;

  void cancel();
  AsyncWaitResult waitAsync() const;

  void run();
};

class MipMapWorker {
  constexpr static const int QUEUE_LIMIT = 100;

  std::unordered_map<std::string, std::shared_ptr<FfmpegAudioBuffer>>
      bufferCache;

  emscripten_lock_t bufferCacheLock;
  emscripten_lock_t lock;
  emscripten_condvar_t condvar;
  std::deque<std::shared_ptr<MipMapRequest>> workQueue;

  static void mipMapWorkerStart(int payloadPtr);

  std::shared_ptr<FfmpegAudioBuffer> getBufferCached(const std::string &uuid);
  std::shared_ptr<MipMapRequest> waitForWork();
  void workerEntrypoint();

public:
  MipMapWorker();
  ~MipMapWorker();

  MipMapWorker(const MipMapWorker &) = delete;
  MipMapWorker(MipMapWorker &&) = delete;
  MipMapWorker &operator=(const MipMapWorker &) = delete;
  MipMapWorker &operator=(MipMapWorker &&) = delete;

  std::shared_ptr<MipMapRequest>
  createRequest(const std::string &uuid, int mipMapLevel, int start, int end);
};