#pragma once
#include "buffer.h"
#include "randomaccessaudioreadable.h"
#include "rendercontext.h"
#include "ringbuffercache.h"
#include "statefuldsp.h"
#include "timing/warp.h"
#include <bungee/Bungee.h>
#include <emscripten/val.h>

// adapt static to dynamic polymorphism, to select between stretcher
// implementations
struct BungeeStretcherBase {
  BungeeStretcherBase(int preroll, int blockDelay)
      : preroll(preroll), blockDelay(blockDelay) {};
  virtual ~BungeeStretcherBase() {};

  virtual Bungee::InputChunk specifyGrain(const Bungee::Request &request) = 0;
  virtual void analyseGrain(const float *data, intptr_t channelStride) = 0;
  virtual void synthesiseGrain(Bungee::OutputChunk &outputChunk) = 0;

  const int preroll;
  const int blockDelay;
};

class TimestretchReader : public StatefulDSP {
  enum class State { Reset, ContinuousPlayback };
  const std::shared_ptr<RingBufferCache> underlyingBuffer;
  const int underlyingSampleRate;
  const int playbackSampleRate;
  const int synthesisHopSize;
  const int grainSize;
  State state;
  time_units::Beats<double> currentPosition;
  time_units::Beats<double> lastLocalPosition;
  time_units::Seconds<double> lastAnalysisPosition;
  double resamplingSynthesisHopSize;
  double pitchFreqCoef;
  BufferF32 excessOutput;
  int excessOutputOffset;
  int excessOutputCount;
  BufferF32 underlyingChunk;
  float transposition;

  std::shared_ptr<time_transform::WarpMap<double>> warpMap;
  std::unique_ptr<BungeeStretcherBase> stretcher;

  void reset(const RenderContext *renderContext,
             time_units::Beats<double> localPosition);
  void synthesize(const RenderContext *renderContext,
                  time_units::Beats<double> localPosition, bool reset);
  void calculatePitchCoefAndHopSize();

public:
  TimestretchReader(std::shared_ptr<RandomAccessAudioReadable> underlyingBuffer,
                    std::shared_ptr<time_transform::WarpMap<double>> warpMap,
                    int playbackSampleRate, bool useProAlgorithm);
  TimestretchReader(int channelCount, int underlyingSampleRate,
                    int playbackSampleRate, bool useProAlgorithm);
  ~TimestretchReader();

  void
  setUnderlying(std::shared_ptr<RandomAccessAudioReadable> underlyingBuffer,
                std::shared_ptr<time_transform::WarpMap<double>> warpMap);

  // returns true on reset
  bool readSegment(const RenderContext *renderContext,
                   time_units::Beats<double> localPosition,
                   float incomingTransposition, BufferF32 output);

  time_units::Seconds<double> getLastAnalysisPosition() const;
};