#include "remotebuffer.h"
#include <catch2/catch.hpp>
#include <emscripten/bind.h>

struct RemoteAudioBufferWrapper
    : public emscripten::wrapper<RemoteAudioBuffer> {
  EMSCRIPTEN_WRAPPER(RemoteAudioBufferWrapper);
  void read(int frameOffset, BufferF32 output) override {
    return call<void>("read", frameOffset, output);
  }
};

EMSCRIPTEN_BINDINGS(remotebuffer) {
  using namespace emscripten;
  class_<RemoteAudioBuffer, base<RandomAccessAudioReadable>>(
      "RemoteAudioBuffer")
      .smart_ptr<std::shared_ptr<RemoteAudioBuffer>>("RemoteAudioBuffer")
      .function("read",
                select_overload<void(int, BufferF32)>(&RemoteAudioBuffer::read),
                pure_virtual())
      .function("readZeroPadded", &RemoteAudioBuffer::readZeroPadded)
      .property("sampleRate", &RemoteAudioBuffer::getSampleRate)
      .property("channelCount", &RemoteAudioBuffer::getChannelCount)
      .property("frameCount", &RemoteAudioBuffer::getFrameCount)
      .allow_subclass<RemoteAudioBufferWrapper,
                      std::shared_ptr<RemoteAudioBufferWrapper>>(
          "RemoteAudioBufferWrapper", "RemoteAudioBufferWrapperSharedPtr",
          constructor<int &&, int &&, int &&>());

  class_<BufferAsRemoteAudioBuffer, base<RemoteAudioBuffer>>(
      "BufferAsRemoteAudioBuffer")
      .smart_ptr_constructor<std::shared_ptr<BufferAsRemoteAudioBuffer>, int &&,
                             std::shared_ptr<BufferF32> &&>(
          "BufferAsRemoteAudioBuffer",
          &std::make_shared<BufferAsRemoteAudioBuffer>);
};

TEST_CASE("RAAR zero padded read", "[randomaccessaudioreadable]") {
  auto buf = std::make_shared<BufferF32>(2, 100);
  buf->noise();
  auto raa = std::make_shared<BufferAsRemoteAudioBuffer>(44100, buf);

  REQUIRE(raa->getSampleRate() == 44100);
  REQUIRE(raa->getChannelCount() == 2);
  REQUIRE(raa->getFrameCount() == 100);

  BufferF32 out(2, 10), bigout(2, 500);

  out.fill(1.f);
  raa->readZeroPadded(0, out);
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(out[channel], out[channel] + out.getFrameCount(),
                       buf->getChannelData(channel)));
  }

  out.fill(1.f);
  raa->readZeroPadded(-1, out);
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(out[channel] + 1, out[channel] + out.getFrameCount(),
                       buf->getChannelData(channel)));
    REQUIRE(out[channel][0] == 0.f);
  }

  out.fill(1.f);
  raa->readZeroPadded(95, out);
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(out[channel], out[channel] + 5,
                       buf->getChannelData(channel) + 95));
    REQUIRE(std::all_of(out[channel] + 5, out[channel] + out.getFrameCount(),
                        [](float v) { return v == 0.f; }));
  }

  bigout.fill(1.f);
  raa->readZeroPadded(-5, bigout);
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(bigout[channel] + 5, bigout[channel] + 105,
                       buf->getChannelData(channel)));
    REQUIRE(std::all_of(bigout[channel], bigout[channel] + 5,
                        [](float v) { return v == 0.f; }));
    REQUIRE(std::all_of(bigout[channel] + 105, bigout[channel] + 500,
                        [](float v) { return v == 0.f; }));
  }
}

TEST_CASE("RAAR stereo muxing", "[randomaccessaudioreadable]") {
  auto buf = std::make_shared<BufferF32>(2, 100);
  buf->noise();
  auto bufMono = std::make_shared<BufferF32>(1, 100);
  for (int i = 0; i < buf->getFrameCount(); i++) {
    (*bufMono)[0][i] = ((*buf)[0][i] + (*buf)[1][i]) / 2.f;
  }
  auto raa = std::make_shared<BufferAsRemoteAudioBuffer>(44100, buf);
  auto raaMono = std::make_shared<BufferAsRemoteAudioBuffer>(44100, bufMono);

  REQUIRE(raa->getChannelCount() == 2);
  REQUIRE(raaMono->getChannelCount() == 1);

  BufferF32 out(2, 10), outMono(1, 10);

  out.fill(0.f);
  raa->readWithMuxing(10, out); // no muxing
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(out[channel], out[channel] + 10,
                       buf->getChannelData(channel) + 10));
  }

  outMono.fill(0.f);
  raaMono->readWithMuxing(10, outMono); // no muxing
  REQUIRE(
      std::equal(outMono[0], outMono[0] + 10, bufMono->getChannelData(0) + 10));

  outMono.fill(0.f);
  raa->readWithMuxing(10, outMono); // down
  REQUIRE(
      std::equal(outMono[0], outMono[0] + 10, bufMono->getChannelData(0) + 10));

  out.fill(0.f);
  raaMono->readWithMuxing(10, out); // up
  for (int channel = 0; channel < 2; channel++) {
    REQUIRE(std::equal(out[channel], out[channel] + 10,
                       bufMono->getChannelData(0) + 10));
  }
}