// SPDX-License-Identifier: Unlicense

#include "api.h"

#ifdef SHR3D_SFX_CORE_EXTENSION_V2

#include "../../nuklear.h"
#include "../../settings.h"

#include <stdio.h>
#include <string.h>

struct LooperInstance final : SfxCoreExtensionV2Instance
{
  LooperInstance ()
  {
  }

  enum struct LooperState
  {
    idle,
    record,
    playback
  } looperState;
  std::vector<f32> recordedAudio;
  u64 playbackPos = 0;

  ProcessBlockResult processBlock(f32** inBlock, f32** /*outBlock*/, const i32 blockSize) override
  {
    switch (looperState)
    {
    case LooperState::idle:
      break;
    case LooperState::record:
      for (i32 i = 0; i < blockSize; ++i)
      {
        recordedAudio.push_back(inBlock[0][i]);
      }
      break;
    case LooperState::playback:
      {
        const size_t dataSize = recordedAudio.size();
        if (dataSize > 0)
        {
          for (i32 i = 0; i < blockSize; ++i)
          {
            inBlock[0][i] += recordedAudio[playbackPos];
            inBlock[1][i] += recordedAudio[playbackPos];
            playbackPos = (playbackPos + 1) % dataSize;
          }
        }
      }
      break;
    }
    return ProcessBlockResult::ProcessedInInBlock;
  }

  f32 getParameter(const i32 /*index*/) const override
  {
    unreachable();
  }

  void setParameter(const i32 /*index*/, const f32 /*value*/) override
  {
    unreachable();
  }

  void getParameterDisplay(const i32 /*index*/, char* /*text*/) override
  {
    unreachable();
  }

  void resetParameter(const i32 /*index*/) override
  {
    unreachable();
  }
};

static void Looper_getChunkDataCallback(SfxCoreExtensionV2Base* extension, i32 instance, const u8*& data, u64& dataSize)
{
  LooperInstance* looperInstance = static_cast<LooperInstance*>(extension->getInstance(instance));
  data = reinterpret_cast<const u8*>(looperInstance->recordedAudio.data());
  dataSize = sizeof(f32) * looperInstance->recordedAudio.size();
}

static void Looper_setChunkDataCallback(SfxCoreExtensionV2Base* extension, i32 instance, const u8* data, u64 dataSize)
{
  LooperInstance* looperInstance = static_cast<LooperInstance*>(extension->getInstance(instance));
  ASSERT(dataSize % sizeof(f32) == 0);
  looperInstance->recordedAudio.resize(dataSize / sizeof(f32));
  memcpy(looperInstance->recordedAudio.data(), data, dataSize);
}

static bool Looper_uiCallback(nk_context* ctx, SfxCoreExtensionV2Base* extension, i32 instance)
{
  char name[32];
  sprintf(name, "Looper_%d", instance);

  const bool showWindow = nk_begin_titled(ctx, name, "Looper", { 20, 20, 120, 110 }, NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE | NK_WINDOW_SCALABLE);
  if (showWindow)
  {
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_end(ctx);

    LooperInstance* looperInstance = static_cast<LooperInstance*>(extension->getInstance(instance));

    if (nk_button_symbol(ctx, NK_SYMBOL_CIRCLE_SOLID))
    {
      looperInstance->recordedAudio.clear();
      looperInstance->playbackPos = 0;
      looperInstance->looperState = LooperInstance::LooperState::record;
    }
    if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT))
    {
      looperInstance->playbackPos = 0;
      looperInstance->looperState = LooperInstance::LooperState::playback;
    }
    if (nk_button_symbol(ctx, NK_SYMBOL_RECT_SOLID))
    {
      looperInstance->playbackPos = 0;
      looperInstance->looperState = LooperInstance::LooperState::idle;
    }

    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);

#ifndef PLATFORM_EMSCRIPTEN
    nk_progress(ctx, &looperInstance->playbackPos, looperInstance->recordedAudio.size(), false);
#else // PLATFORM_EMSCRIPTEN
    nk_size playbackPos = looperInstance->playbackPos;
    nk_progress(ctx, &playbackPos, looperInstance->recordedAudio.size(), false);
    looperInstance->playbackPos = playbackPos;
#endif // PLATFORM_EMSCRIPTEN
  }
  nk_end(ctx);

  return showWindow;
}

static struct Looper final : SfxCoreExtensionV2Base
{
  Looper()
  {
    getChunkDataCallback = Looper_getChunkDataCallback;
    setChunkDataCallback = Looper_setChunkDataCallback;
    uiCallback = Looper_uiCallback;
    RegisterSfxCoreExtensionV2("Looper", this);
  }

  i32 getParameterCount() const override
  {
    return 0;
  }

  const char* getParameterLabel(const i32 index) override
  {
    unreachable();
  }

  const char* getParameterName(const i32 index) override
  {
    unreachable();
  }

  SfxCoreExtensionV2Instance* getInstance(const i32 instance) override
  {
    static SfxCoreExtensionV2Instance* instances[ExtensionV2MaxInstanceCount];
    assert(instance < ARRAY_SIZE(instances));

    if (instances[instance] == nullptr)
      instances[instance] = new LooperInstance;

    return instances[instance];
  }
} looper;

#endif // SHR3D_SFX_CORE_EXTENSION_V2
