#include "track.h"
#include "remotebuffer.h"
#include "timing/time_units.h"
#include "timing/warp.h"
#include <algorithm>
#include <catch2/catch.hpp>
#include <emscripten/atomic.h>
#include <emscripten/bind.h>

Track::Track(int sampleRate, float vuAlpha, float gain, float pan, bool muted,
             const std::shared_ptr<Meter> &meter,
             const std::shared_ptr<FilterChain> &filterChain,
             const std::vector<std::shared_ptr<AudioClip>> &clips)
    : gain(gain), pan(pan), muted(muted ? 1 : 0), clips(clips),
      tempBuffer(2, 4096), silentBuffer(2, 4096), meter(meter),
      filterChain(filterChain) {
  assert(std::isfinite(gain));
  assert(std::isfinite(pan));
  silentBuffer.fill(0.f);
}

float Track::getGain() const { return emscripten_atomic_load_f32(&gain); }

void Track::setGain(float gain) {
  assert(std::isfinite(gain));
  emscripten_atomic_store_f32(&this->gain, gain);
}

float Track::getPan() const { return emscripten_atomic_load_f32(&pan); }

void Track::setPan(float pan) {
  assert(std::isfinite(pan));
  emscripten_atomic_store_f32(&this->pan, pan);
}

bool Track::isMuted() const { return emscripten_atomic_load_u32(&muted) != 0; }

void Track::setMuted(bool muted) {
  emscripten_atomic_store_u32(&this->muted, muted ? 1 : 0);
}
std::shared_ptr<AudioClip> Track::getClip(int index) const {
  if (index < 0 || index >= clips.size()) {
    return nullptr;
  }
  return clips[index];
}

void Track::readSegment(const RenderContext *renderContext, BufferF32 output) {
  output.fill(0);
  if (isMuted()) {
    if (meter) {
      meter->update(output);
    }
    return;
  }

  const auto gainHere = getGain();
  const auto panHere = getPan();

  auto workBuffer =
      tempBuffer.slice(0, output.getFrameCount(), output.getChannelCount());
  // pan: -1 to 1, where -1 is full left and 1 is full right
  // TODO: need panning rules and mixdown for mono output
  const float channelGains[2] = {gainHere * std::clamp(1.f - panHere, 0.f, 1.f),
                                 gainHere *
                                     std::clamp(1.f + panHere, 0.f, 1.f)};

  const auto renderStart = renderContext->position;
  const auto renderEnd = renderContext->position + renderContext->duration;

  for (const auto &clip : clips) {
    const auto clipTimelineStart =
        time_units::Beats(clip->getTimelineStartBeats());
    const auto clipTimelineEnd = time_units::Beats(clip->getTimelineEndBeats());

    if (renderEnd < clipTimelineStart || renderStart > clipTimelineEnd) {
      continue;
    }

    workBuffer.fill(0);
    clip->readSegment(renderContext, workBuffer);
    for (int channel = 0; channel < workBuffer.getChannelCount(); channel++) {
      auto channelData = workBuffer.getChannelData(channel);
      auto channelGain = channelGains[channel];
#pragma clang loop vectorize(enable)
      for (int i = 0; i < workBuffer.getFrameCount(); i++) {
        channelData[i] *= channelGain;
      }
    }
    output.sumWith(workBuffer);
  }
  if (filterChain) {
    filterChain->processBuffer(output);
  }
  if (meter) {
    meter->update(output);
  }
}

void Track::progressSegment(int channelCount, int frameCount) {
  if (meter) {
    meter->update(silentBuffer.slice(0, frameCount, channelCount));
  }
}

EMSCRIPTEN_BINDINGS(track) {
  using namespace emscripten;
  class_<Track>("Track")
      .smart_ptr<std::shared_ptr<Track>>("Track")
      .property("gain", &Track::getGain, &Track::setGain)
      .property("pan", &Track::getPan, &Track::setPan)
      .property("muted", &Track::isMuted, &Track::setMuted)
      .property("meter", &Track::meter)
      .property("filterChain", &Track::filterChain)
      .function("getClipCount", &Track::getClipCount)
      .function("getClip(index)", &Track::getClip);
};

TEST_CASE("track memory tests", "[track]") {
  using namespace time_units;
  auto warpMap = std::make_shared<time_transform::WarpMap<double>>(
      std::vector<time_transform::WarpMap<double>::WarpMarker>{
          {Beats(0.0), Seconds(0.0)}, {Beats(1.0), Seconds(1.0)}});
  auto buf = std::make_shared<BufferF32>(2, 44100);
  buf->noise();
  BufferF32 out(2, 1024);
  double clock = 0.0;
  auto rab = std::make_shared<BufferAsRemoteAudioBuffer>(44100, buf);
  auto clip = std::make_shared<AudioClip>(rab, warpMap, 44100, 1.f, 0.0, 1.0,
                                          0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                          0.f, 120.0, false, Uuid());
  auto meter = std::make_shared<Meter>(2, 44100, 10, 10, 0.99f);
  Track track(44100, 0.99f, 1.f, 0.f, false, meter, nullptr,
              std::vector<std::shared_ptr<AudioClip>>{clip});

  /*
  TODO
    track.setMuted(true);
    track.readSegment(0.0, 2.0, out);
    REQUIRE(out.peak() == 0.f);
    REQUIRE(track.meter->getPpmHoldIndicated(0) < -100.f);
    REQUIRE(track.meter->getPpmIndicated(0) < -100.f);
    REQUIRE(track.meter->getVuIndicated(0) < -100.f);

    track.setMuted(false);
    track.readSegment(0.0, 2.0, out);
    REQUIRE(out.peak() > 0.f);
    REQUIRE(track.meter->getPpmHoldIndicated(0) > -100.f);
    REQUIRE(track.meter->getPpmIndicated(0) > -100.f);
    REQUIRE(track.meter->getVuIndicated(0) > -100.f);

    track.setMuted(true);
    track.readSegment(0.0, 2.0, out);
    REQUIRE(out.peak() == 0.f);
    REQUIRE(track.meter->getPpmHoldIndicated(0) > -100.f);
    REQUIRE(track.meter->getPpmIndicated(0) < -100.f);
    */
}