#ifndef NEURAL_AMP_MODELER_H
#define NEURAL_AMP_MODELER_H

#include "../../configuration.h"

#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER

#include "nam/dsp.h"
#include "dsp/ImpulseResponse.h"
#include "dsp/NoiseGate.h"
#include "dsp/RecursiveLinearFilter.h"
#include "dsp/dsp1.h"
#include "dsp/wav.h"

#include <atomic>
#include <memory>

#include <cstdio>

#define API_EXT2
#ifdef VST2_API


#elif defined APP_API
//#include "IPlugAPP.h"
#define PLUGIN_API_BASE IPlugAPP
#define API_EXT "app"
#endif

//namespace iplug {
//using Plugin = PLUGIN_API_BASE;
//}

#define EXPORT __declspec(dllexport)
#define BUNDLE_ID ""


#define STRINGISE_IMPL(x) #x
#define STRINGISE(x) STRINGISE_IMPL(x)

// Use: #pragma message WARN("My message")
#if _MSC_VER
#   define FILE_LINE_LINK __FILE__ "(" STRINGISE(__LINE__) ") : "
#   define WARN(exp) (FILE_LINE_LINK "WARNING: " exp)
#else//__GNUC__ - may need other defines for different compilers
#   define WARN(exp) ("WARNING: " exp)
#endif


const int kNumPresets = 1;
// The plugin is mono inside
constexpr size_t kNumChannelsInternal = 1;

//class NAMSender : public iplug::IPeakAvgSender<>
//{
//public:
//  NAMSender()
//  : iplug::IPeakAvgSender<>(-90.0, true, 5.0f, 1.0f, 300.0f, 500.0f)
//  {
//  }
//};

enum EParams
{
  // These need to be the first ones because I use their indices to place
  // their rects in the GUI.
  kInputLevel_ = 0,
  kNoiseGateThreshold_,
  kToneBass_,
  kToneMid_,
  kToneTreble_,
  kOutputLevel_,
  // The rest is fine though.
  kNoiseGateActive,
  kEQActive,
  kOutNorm,
  kIRToggle,
  kNumParams
};

const int numKnobs = 6;

enum ECtrlTags
{
  kCtrlTagModelFileBrowser = 0,
  kCtrlTagIRFileBrowser,
  kCtrlTagInputMeter,
  kCtrlTagOutputMeter,
  kCtrlTagAboutBox,
  kCtrlTagOutNorm,
  kCtrlTagSampleRateWarning,
  kNumCtrlTags
};

enum EMsgTags
{
  // These tags are used from UI -> DSP
  kMsgTagClearModel = 0,
  kMsgTagClearIR,
  kMsgTagHighlightColor,
  // The following tags are from DSP -> UI
  kMsgTagLoadFailed,
  kMsgTagLoadedModel,
  kMsgTagLoadedIR,
  kNumMsgTags
};

class NeuralAmpModeler final
{
public:
  double dSampleRate = 0.0;

  NeuralAmpModeler();
  ~NeuralAmpModeler();

  void ProcessBlock(const float* const* inputs, float** outputs, int nFrames, int sampleRate, int input, int gate, int bass, int middle, int treble, int output, bool gateEnabled, bool eqEnabled, bool normalizeEnabled, bool irEnabled, bool namEnabled);

  // Allocates mInputPointers and mOutputPointers
  void _AllocateIOPointers(const size_t nChans);
  // Moves DSP modules from staging area to the main area.
  // Also deletes DSP modules that are flagged for removal.
  // Exists so that we don't try to use a DSP module that's only
  // partially-instantiated.
  void _ApplyDSPStaging();
  // Deallocates mInputPointers and mOutputPointers
  // Check whether the sample rate is correct for the NAM model.
  // Adjust the warning control accordingly.
  void _CheckSampleRateWarning();
  void _DeallocateIOPointers();
  // Fallback that just copies inputs to outputs if mDSP doesn't hold a model.
  void _FallbackDSP(float** inputs, float** outputs, const size_t numChannels, const size_t numFrames);
  // Sizes based on mInputArray
  size_t _GetBufferNumChannels() const;
  size_t _GetBufferNumFrames() const;
  // Apply the normalization for the model output (if possible)
  void _NormalizeModelOutput(float** buffer, const size_t numChannels, const size_t numFrames);
  // Loads a NAM model and stores it to mStagedNAM
  // Returns an empty string on success, or an error message on failure.
  void _StageModel(const std::string& dspFile);
  void _StageModel(const unsigned char* configData, size_t configSize);
  // Loads an IR and stores it to mStagedIR.
  // Return status code so that error messages can be relayed if
  // it wasn't successful.
  dsp::wav::LoadReturnCode _StageIR(const std::string& irPath);

  bool _HaveModel() const { return this->mModel != nullptr; };
  // Prepare the input & output buffers
  void _PrepareBuffers(const size_t numChannels, const size_t numFrames);
  // Manage pointers
  void _PrepareIOPointers(const size_t nChans);
  // Copy the input buffer to the object, applying input level.
  // :param nChansIn: In from external
  // :param nChansOut: Out to the internal of the DSP routine
  void _ProcessInput(const float* const* inputs, const size_t nFrames, const size_t nChansIn, const size_t nChansOut, const int input);
  // Copy the output to the output buffer, applying output level.
  // :param nChansIn: In from internal
  // :param nChansOut: Out to external
  void _ProcessOutput(float** inputs, float** outputs, const size_t nFrames, const size_t nChansIn,
                      const size_t nChansOut, const int output);
  // Checks the loaded model and IR against the current sample rate and resamples them if needed
  void _ResampleModelAndIR();


  // Member data

  // Input arrays to NAM
  std::vector<std::vector<float>> mInputArray;
  // Output from NAM
  std::vector<std::vector<float>> mOutputArray;
  // Pointer versions
  float** mInputPointers = nullptr;
  float** mOutputPointers = nullptr;

  // Noise gates
  dsp::noise_gate::Trigger mNoiseGateTrigger;
  dsp::noise_gate::Gain mNoiseGateGain;
  // The model actually being used:
  std::unique_ptr<DSP> mModel;
  // And the IR
  std::unique_ptr<dsp::ImpulseResponse> mIR;
  // Manages switching what DSP is being used.
  std::unique_ptr<DSP> mStagedModel;
  std::unique_ptr<dsp::ImpulseResponse> mStagedIR;
  // Flags to take away the modules at a safe time.
  std::atomic<bool> mShouldRemoveModel = false;
  std::atomic<bool> mShouldRemoveIR = false;

  std::atomic<bool> mNewModelLoadedInDSP = false;
  // Flag to check whether the playback sample rate is correct for the model being used.
  std::atomic<bool> mCheckSampleRateWarning = true;

  // Tone stack modules
  recursive_linear_filter::LowShelf mToneBass;
  recursive_linear_filter::Peaking mToneMid;
  recursive_linear_filter::HighShelf mToneTreble;

  // Post-IR filters
  recursive_linear_filter::HighPass mHighPass;
  //  recursive_linear_filter::LowPass mLowPass;

  // Path to model's config.json or model.nam
  std::string mNAMPath;
  // Path to IR (.wav file)
  std::string mIRPath;

  //WDL_String mHighLightColor{PluginColors::NAM_THEMECOLOR.ToColorCode()};

  std::unordered_map<std::string, double> mNAMParams = {{"Input", 0.0}, {"Output", 0.0}};
};

#endif // SHR3D_SFX_CORE_NEURALAMPMODELER

#endif // NEURAL_AMP_MODELER_H
