/*
 * Copyright (C) 2012 Hermann Meyer, Andreas Degert, Pete Shorthose, Steve Poskitt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 * --------------------------------------------------------------------------
 */

// DEBUG stop program at Floating point exception
//#include <fenv.h>

#include <zita-resampler/resampler.h>
#include <assert.h>

////////////////////////////// LOCAL INCLUDES //////////////////////////
//#include "gx_resampler.h"
#include "gx_common.h"      // faust support and denormal protection (SSE)
#include "gx_fuzz.h"        // define struct PortIndex
#include "gx_pluginlv2.h"   // define struct PluginLV2
#include "bmfp.cc"    // dsp class generated by faust -> dsp2cc
#include "lowpass_up.cc"    // dsp class generated by faust -> dsp2cc
#include "lowpass_down.cc"    // dsp class generated by faust -> dsp2cc
#ifndef __SSE__
#include "noiser.cc"
#endif

#define MAX_UPSAMPLE 8

////////////////////////////// PLUG-IN CLASS ///////////////////////////

class Gx_fuzz_
{
private:
  // pointer to buffer
  float*      output;
  float*      input;
  // pointer to dsp class
  PluginLV2*  fuzz;
  PluginLV2*  pass;
  PluginLV2*  passd;
#ifndef __SSE__
  PluginLV2*  wn;
#endif
  // resampler
  Resampler r_up, r_down;
  int32_t m_fact;
  int32_t ratio_a;
  int32_t ratio_b;
  void setup(int32_t sampleRate, int32_t fact);
  int32_t up(int32_t count, float *input, float *output);
  void down(int32_t count, float *input, float *output);
  int32_t get_max_out_size(int32_t i_size)
  {
    return (i_size * ratio_b) / ratio_a + 1;
  }
  // private functions
  inline void run_dsp_(uint32_t n_samples);
  inline void connect_(uint32_t port,void* data);
  inline void init_dsp_(uint32_t rate);
  inline void connect_all__ports(uint32_t port, void* data);
  inline void activate_f();
  inline void clean_up();
  inline void deactivate_f();
  
public:
  // LV2 Descriptor
  static const LV2_Descriptor descriptor;
  // static wrapper to private functions
  static void deactivate(LV2_Handle instance);
  static void cleanup(LV2_Handle instance);
  static void run(LV2_Handle instance, uint32_t n_samples);
  static void activate(LV2_Handle instance);
  static void connect_port(LV2_Handle instance, uint32_t port, void* data);
  static LV2_Handle instantiate(const LV2_Descriptor* descriptor,
                                double rate, const char* bundle_path,
                                const LV2_Feature* const* features);
  Gx_fuzz_();
  ~Gx_fuzz_();
};

// constructor
Gx_fuzz_::Gx_fuzz_() :
  output(NULL),
  input(NULL),
  fuzz(bmfp::plugin()),
  pass(lowpass_up::plugin()),
  passd(lowpass_down::plugin()),
  r_up(),
  r_down(),
  m_fact() {};

// destructor
Gx_fuzz_::~Gx_fuzz_()
{
  // just to be sure the plug have given free the allocated mem
  // it didn't hurd if the mem is already given free by clean_up()
  if (fuzz->activate_plugin !=0)
    fuzz->activate_plugin(false, fuzz);
  // delete DSP class
  fuzz->delete_instance(fuzz);
  if (pass->activate_plugin !=0)
    pass->activate_plugin(false, pass);
  // delete DSP class
  pass->delete_instance(pass);
  if (passd->activate_plugin !=0)
    passd->activate_plugin(false, passd);
  // delete DSP class
  passd->delete_instance(passd);
};


static int32_t gcd (int32_t a, int32_t b)
{
  if (a == 0) return b;
  if (b == 0) return a;
  while (1)
    {
      if (a > b)
        {
          a = a % b;
          if (a == 0) return b;
          if (a == 1) return 1;
        }
      else
        {
          b = b % a;
          if (b == 0) return a;
          if (b == 1) return 1;
        }
    }
  return 1;
}

void Gx_fuzz_::setup(int32_t sampleRate, int32_t fact)
{
  int32_t d = gcd(sampleRate, sampleRate*fact);
  ratio_a = sampleRate / d;
  ratio_b = (sampleRate*fact) / d;

  assert(fact <= MAX_UPSAMPLE);
  m_fact = fact;
  const int32_t qual = 16; // resulting in a total delay of 2*qual (0.7ms @44100)
  // upsampler
  r_up.setup(sampleRate, sampleRate*fact, 1, qual);
  r_up.inp_count = r_up.inpsize() - 1;
  r_up.out_count = 1;
  r_up.inp_data = r_up.out_data = 0;
  r_up.process();
  // downsampler
  r_down.setup(sampleRate*fact, sampleRate, 1, qual);
  r_down.inp_count = r_down.inpsize() - 1;
  r_down.out_count = 1;
  r_down.inp_data = r_down.out_data = 0;
  r_down.process();
}

int32_t Gx_fuzz_::up(int32_t count, float *input, float *output)
{
  r_up.inp_count = count;
  r_up.inp_data = input;
  int m = get_max_out_size(count);
  r_up.out_count = m;
  r_up.out_data = output;
  r_up.process();
  assert(r_up.inp_count == 0);
  assert(r_up.out_count <= 1);
  r_down.inp_count = m - r_up.out_count;
  return r_down.inp_count;
}

void Gx_fuzz_::down(int32_t count, float *input, float *output)
{
  //r_down.inp_count = count * m_fact;
  r_down.inp_data = input;
  r_down.out_count = count+1; // +1 == trick to drain input
  r_down.out_data = output;
  r_down.process();
  assert(r_down.inp_count == 0);
  assert(r_down.out_count == 1);
}


///////////////////////// PRIVATE CLASS  FUNCTIONS /////////////////////

void Gx_fuzz_::init_dsp_(uint32_t rate)
{
  // DEBUG stop program at Floating point exception
  //feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
  AVOIDDENORMALS(); // init the SSE denormal protection
  setup(rate, 4);
  fuzz->set_samplerate(4*rate, fuzz); // init the DSP class
  pass->set_samplerate(rate, pass); // init the DSP class
  passd->set_samplerate(rate, passd); // init the DSP class
#ifndef __SSE__
  wn = noiser::plugin();
  wn->set_samplerate(rate, wn);
#endif
}

// connect the Ports used by the plug-in class
void Gx_fuzz_::connect_(uint32_t port,void* data)
{
  switch ((PortIndex)port)
    {
    case EFFECTS_OUTPUT:
      output = static_cast<float*>(data);
      break;
    case EFFECTS_INPUT:
      input = static_cast<float*>(data);
      break;
    default:
      break;
    }
}

void Gx_fuzz_::activate_f()
{
  // allocate the internal DSP mem
  if (fuzz->activate_plugin !=0)
    fuzz->activate_plugin(true, fuzz);
  if (pass->activate_plugin !=0)
    pass->activate_plugin(true, pass);
  if (passd->activate_plugin !=0)
    passd->activate_plugin(true, passd);
}

void Gx_fuzz_::clean_up()
{
  // delete the internal DSP mem
  if (fuzz->activate_plugin !=0)
    fuzz->activate_plugin(false, fuzz);
  if (pass->activate_plugin !=0)
    pass->activate_plugin(false, pass);
  if (passd->activate_plugin !=0)
    passd->activate_plugin(false, passd);
}

void Gx_fuzz_::deactivate_f()
{
  // delete the internal DSP mem
  if (fuzz->activate_plugin !=0)
    fuzz->activate_plugin(false, fuzz);
  if (pass->activate_plugin !=0)
    pass->activate_plugin(false, pass);
  if (passd->activate_plugin !=0)
    passd->activate_plugin(false, passd);
}

void Gx_fuzz_::run_dsp_(uint32_t n_samples)
{
  if (n_samples< 1) return;
#ifndef __SSE__
  wn->mono_audio(static_cast<int>(n_samples), input, input, wn);;
#endif
  pass->mono_audio(n_samples, input, output, pass);
  float buf[get_max_out_size(n_samples)];
  int32_t n = up(n_samples, output, buf);
  fuzz->mono_audio(n, buf, buf, fuzz);
  down(n_samples,buf, output);
  passd->mono_audio(n_samples, output, output, passd);
}

void Gx_fuzz_::connect_all__ports(uint32_t port, void* data)
{
  // connect the Ports used by the plug-in class
  connect_(port,data); 
  // connect the Ports used by the DSP class
  fuzz->connect_ports(port,  data, fuzz);
  passd->connect_ports(port,  data, passd);
}

////////////////////// STATIC CLASS  FUNCTIONS  ////////////////////////

LV2_Handle 
Gx_fuzz_::instantiate(const LV2_Descriptor* descriptor,
                            double rate, const char* bundle_path,
                            const LV2_Feature* const* features)
{
  // init the plug-in class
  Gx_fuzz_ *self = new Gx_fuzz_();
  if (!self)
    {
      return NULL;
    }

  self->init_dsp_((uint32_t)rate);

  return (LV2_Handle)self;
}

void Gx_fuzz_::connect_port(LV2_Handle instance, 
                                   uint32_t port, void* data)
{
  // connect all ports
  static_cast<Gx_fuzz_*>(instance)->connect_all__ports(port, data);
}

void Gx_fuzz_::activate(LV2_Handle instance)
{
  // allocate needed mem
  static_cast<Gx_fuzz_*>(instance)->activate_f();
}

void Gx_fuzz_::run(LV2_Handle instance, uint32_t n_samples)
{
  // run dsp
  static_cast<Gx_fuzz_*>(instance)->run_dsp_(n_samples);
}

void Gx_fuzz_::deactivate(LV2_Handle instance)
{
  // free allocated mem
  static_cast<Gx_fuzz_*>(instance)->deactivate_f();
}

void Gx_fuzz_::cleanup(LV2_Handle instance)
{
  // well, clean up after us
  Gx_fuzz_* self = static_cast<Gx_fuzz_*>(instance);
  self->clean_up();
  delete self;
}

const LV2_Descriptor Gx_fuzz_::descriptor =
{
  GXPLUGIN_URI "#fuzz_",
  Gx_fuzz_::instantiate,
  Gx_fuzz_::connect_port,
  Gx_fuzz_::activate,
  Gx_fuzz_::run,
  Gx_fuzz_::deactivate,
  Gx_fuzz_::cleanup,
  NULL
};

////////////////////////// LV2 SYMBOL EXPORT ///////////////////////////

LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
  switch (index)
    {
    case 0:
      return &Gx_fuzz_::descriptor;
    default:
      return NULL;
    }
}

///////////////////////////// FIN //////////////////////////////////////
