#pragma once
#include "analytics.h"
#include "audioclipbackend.h"
#include "randomaccessaudioreadable.h"
#include "rendercontext.h"
#include "timestretch.h"
#include "timing/warp.h"
#include <memory>

class AudioClip {
private:
  std::shared_ptr<RandomAccessAudioReadable> originalUnderlyingReadable;
  std::shared_ptr<time_transform::WarpMap<double>> warpMap;
  int playbackSampleRate;
  int previousUnderlyingReadOffset;
  double previousRequestedPosition;

  double warpedContentBps;
  bool warpEnabled;

  float gain;
  float transposition;
  double timelineStartBeats;
  double timelineEndBeats;
  double loopStartBeats;
  double loopEndBeats;
  double readStartBeats;
  double fadeInBeats;
  double fadeInExponent;
  double fadeOutBeats;
  double fadeOutExponent;

  BufferF32 microfadeMemory;
  int microfadeOffset;

  Uuid arrangementId;

  int unwarpedExpectedReadOffset;

  std::shared_ptr<AudioClipBackend> backend;

  struct AnalyticsResult {
    bool didLoop;
    bool didPlayContent;
    time_units::Seconds<double> lastPosition;
  };

  void
  readSegmentWarped(const std::shared_ptr<TimestretchReader> &timestretchReader,
                    const RenderContext *renderContext, BufferF32 output,
                    float transposition, double adjustedReadStartBeats,
                    double adjustedTimelineStartBeats,
                    AnalyticsResult &analyticsResult);
  void readSegmentUnwarped(
      const std::shared_ptr<RandomAccessAudioReadable> &underlyingReadable,
      const RenderContext *renderContext, BufferF32 output,
      double adjustedReadStartBeats, double adjustedTimelineStartBeats,
      AnalyticsResult &analyticsResult);
  void applyFadesAndGain(double position, double bps, BufferF32 output);

public:
  AudioClip(
      std::shared_ptr<RandomAccessAudioReadable> originalUnderlyingReadable,
      std::shared_ptr<time_transform::WarpMap<double>> warpMap,
      int playbackSampleRate, float gain, double timelineStartBeats,
      double timelineEndBeats, double loopStartBeats, double loopEndBeats,
      double readStartBeats, double fadeInBeats, double fadeInExponent,
      double fadeOutBeats, double fadeOutExponent, float transposition,
      double warpedContentBps, bool warpEnabled, const Uuid &arrangementId);

  static constexpr const float TRANSPOSE_MINIMUM_SEMITONES = -24.f;
  static constexpr const float TRANSPOSE_MAXIMUM_SEMITONES = 24.f;

  // immutable
  double getTimelineStartBeats() const { return timelineStartBeats; }
  double getTimelineEndBeats() const { return timelineEndBeats; }

  // mutable from any thread
  float getGain() const;
  void setGain(float newGain);

  double getLoopStartBeats() const;
  void setLoopStartBeats(double newStart);

  double getLoopEndBeats() const;
  void setLoopEndBeats(double newEnd);

  double getReadStartBeats() const;
  void setReadStartBeats(double newStart);

  double getFadeInBeats() const;
  void setFadeInBeats(double newFade);

  double getFadeInExponent() const;
  void setFadeInExponent(double newExp);

  double getFadeOutBeats() const;
  void setFadeOutBeats(double newFade);

  double getFadeOutExponent() const;
  void setFadeOutExponent(double newExp);

  float getTransposition() const;
  void setTransposition(float newTrans);

  double getWarpedContentBps() const;
  void setWarpedContentBps(double newBps);

  bool getWarpEnabled() const;
  void setWarpEnabled(bool enabled);

  // called from realtime only
  std::shared_ptr<RandomAccessAudioReadable>
  getOriginalUnderlyingReadable() const {
    return originalUnderlyingReadable;
  }
  void setBackend(std::shared_ptr<AudioClipBackend> newBackend) {
    assert(backend == nullptr);
    assert(newBackend != nullptr);
    backend = std::move(newBackend);
  }

  void readSegment(const RenderContext *renderContext, BufferF32 output);
};