/*
 * Copyright (C) 2014 Guitarix project MOD project
 *
 * 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.
 * --------------------------------------------------------------------------
 */


#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <unistd.h>

///////////////////////// DENORMAL PROTECTION WITH SSE /////////////////

#ifdef NOSSE
#undef __SSE__
#endif

#ifdef __SSE__
#include <immintrin.h>
#ifndef _IMMINTRIN_H_INCLUDED
#include <fxsrintrin.h>
#endif
/* On Intel set FZ (Flush to Zero) and DAZ (Denormals Are Zero)
   flags to avoid costly denormals */
#ifdef __SSE3__
#ifndef _PMMINTRIN_H_INCLUDED
#include <pmmintrin.h>
#endif
#else
#ifndef _XMMINTRIN_H_INCLUDED
#include <xmmintrin.h>
#endif
#endif //__SSE3__

#endif //__SSE__

///////////////////////// MACRO SUPPORT ////////////////////////////////

#define __rt_func __attribute__((section(".rt.text")))
#define __rt_data __attribute__((section(".rt.data")))

///////////////////////// FAUST SUPPORT ////////////////////////////////

#define FAUSTFLOAT float
#ifndef N_
#define N_(String) (String)
#endif
#define max(x, y) (((x) > (y)) ? (x) : (y))
#define min(x, y) (((x) < (y)) ? (x) : (y))
#define PITCHBEND_INC 0.00146484375  // 24 / (2^14), +/- 1 octave

#define always_inline inline __attribute__((always_inline))

#ifndef signbit
#define signbit(x) std::signbit(x)
#endif

template<class T> inline T mydsp_faustpower2_f(T x) {return (x * x);}
template<class T> inline T mydsp_faustpower3_f(T x) {return ((x * x) * x);}
template<class T> inline T mydsp_faustpower4_f(T x) {return (((x * x) * x) * x);}
template<class T> inline T mydsp_faustpower5_f(T x) {return ((((x * x) * x) * x) * x);}
template<class T> inline T mydsp_faustpower6_f(T x) {return (((((x * x) * x) * x) * x) * x);}

////////////////////////////// LOCAL INCLUDES //////////////////////////

#include "Xmonk.h"        // define struct PortIndex
#include "xmonk.cc"    // dsp class generated by faust -> dsp2cc
#include "stereoverb.cc"    // dsp class generated by faust -> dsp2cc
#include "stereodelay.cc"    // dsp class generated by faust -> dsp2cc

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

namespace xmonk {

class DenormalProtection
{
private:
#ifdef __SSE__
  uint32_t  mxcsr_mask;
  uint32_t  mxcsr;
  uint32_t  old_mxcsr;
#endif

public:
  inline void set_() {
#ifdef __SSE__
    old_mxcsr = _mm_getcsr();
    mxcsr = old_mxcsr;
    _mm_setcsr((mxcsr | _MM_DENORMALS_ZERO_MASK | _MM_FLUSH_ZERO_MASK) & mxcsr_mask);
#endif
  };
  inline void reset_() {
#ifdef __SSE__
    _mm_setcsr(old_mxcsr);
#endif
  };

  inline DenormalProtection()
  {
#ifdef __SSE__
    mxcsr_mask = 0xffbf; // Default MXCSR mask
    mxcsr      = 0;
#if defined(__x86_64__) || defined(_M_X64)
    uint8_t fxsave[512] __attribute__ ((aligned (16))); // Structure for storing FPU state with FXSAVE command

    memset(fxsave, 0, sizeof(fxsave));
    __builtin_ia32_fxsave(&fxsave);
    uint32_t mask = *(reinterpret_cast<uint32_t *>(&fxsave[0x1c])); // Obtain the MXCSR mask from FXSAVE structure
    if (mask != 0)
        mxcsr_mask = mask;
#endif
#endif
  };

  inline ~DenormalProtection() {};
};

class Xmonk_
{
private:
  const LV2_Atom_Sequence* control;
  LV2_URID midi_MidiEvent;
  LV2_URID_Map* map;
  uint8_t note_on;
  uint8_t note_off;
  float* note;
  float* gate;
  float* panic;
  float pitchbend;
  float* vowel;
  float* ui_note;
  float* ui_gate;
  float* ui_vowel;
  float* sustain;
  float _sustain;
  int gatecounter;
  bool have_midi;
  uint8_t last_key[12];

  DenormalProtection MXCSR;
  // pointer to buffer
  float*          output;
  float*          output1;
  // pointer to dsp class
  Dsp*      xmonk;
  stereoverb::Dsp*      reverb;
  stereodelay::Dsp*      delay;

  // 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();

  void clear_key_list();
  void remove_first_key();
  void add_last_key(uint8_t *key);
  void remove_last_key(uint8_t *key);
  void get_last_key();

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);
  Xmonk_();
  ~Xmonk_();
};

// constructor
Xmonk_::Xmonk_() :
  note(NULL),
  gate(NULL),
  panic(NULL),
  vowel(NULL),
  output(NULL),
  output1(NULL),
  xmonk(xmonk::plugin()),
  reverb(stereoverb::plugin()),
  delay(stereodelay::plugin()) {};

// destructor
Xmonk_::~Xmonk_()
{
  // delete DSP class
  xmonk->del_instance(xmonk);
  reverb->del_instance(reverb);
  delay->del_instance(delay);
};

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

void Xmonk_::init_dsp_(uint32_t rate)
{
  xmonk->init_static(rate, xmonk); // init the DSP class
  reverb->init_static(rate, reverb); // init the DSP class
  delay->init_static(rate, delay); // init the DSP class
  gatecounter = 0;
  pitchbend = 0.0;
  clear_key_list();
  have_midi = false;
}

// connect the Ports used by the plug-in class
void Xmonk_::connect_(uint32_t port,void* data)
{
  switch ((PortIndex)port)
    {
    case EFFECTS_OUTPUT:
      output = static_cast<float*>(data);
      break;
    case EFFECTS_OUTPUT1:
      output1 = static_cast<float*>(data);
      break;
    case MIDI_IN:
      control = (const LV2_Atom_Sequence*)data;
      break;
    case MIDINOTE:
      note = (float*)data;
      break;
    case MIDIVOWEL:
      vowel = (float*)data;
      break;
    case MIDIGATE:
      gate = (float*)data;
      break;
    case NOTE:
      ui_note = (float*)data;
      break;
    case VOWEL:
      ui_vowel = (float*)data;
      break;
    case GATE:
      ui_gate = (float*)data;
      break;
    case PANIC:
      panic = (float*)data;
      break;
    case SUSTAIN:
      sustain = (float*)data;
      break;
    default:
      break;
    }
}

void Xmonk_::activate_f()
{
}

void Xmonk_::clean_up()
{
}

void Xmonk_::deactivate_f()
{
  xmonk->clear_state_f_static(xmonk);
  reverb->clear_state_f_static(reverb);
  delay->clear_state_f_static(delay);
}

void Xmonk_::clear_key_list() {
    int i = 0;
    for(;i<12;i++) {
        last_key[i] = 0;
    }
}

void Xmonk_::remove_first_key() {
    int i = 0;
    for(;i<11;i++) {
        last_key[i] = last_key[i+1];
    }
    last_key[i] = 0;
}

void Xmonk_::add_last_key(uint8_t *key) {
    int i = 0;
    bool set_key = false;
    for(;i<12;i++) {
        if(last_key[i] == 0) {
            last_key[i] = (*key);
            set_key = true;
            break;
        }
    }
    if(!set_key) {
        remove_first_key();
        add_last_key(key);
    }
}

void Xmonk_::remove_last_key(uint8_t *key) {
    int i = 0;
    for(;i<12;i++) {
        if(last_key[i] == (*key)) {
            last_key[i] = 0;
            break;
        }
    }
    for(;i<11;i++) {
        last_key[i] = last_key[i+1];
    }
    last_key[i] = 0;
}

void Xmonk_::get_last_key() {
    int i = 11;
    for(;i>-1;i--) {
        if(last_key[i] != 0) {
            (*note) = (float)last_key[i]+pitchbend;
            (*ui_note) = (float)last_key[i]+pitchbend;
            xmonk->note = (double) (*note);
            break;
        }
    }
}

void Xmonk_::run_dsp_(uint32_t n_samples)
{
    if(n_samples<1) return;
    MXCSR.set_();

    if((*sustain) != _sustain) {
        if(!(int)floor((*sustain)) && (int)floor((_sustain)))
            clear_key_list();
       _sustain = (*sustain); 
    }
    
    LV2_ATOM_SEQUENCE_FOREACH(control, ev) {
        if (ev->body.type == midi_MidiEvent) {
            const uint8_t* const msg = (const uint8_t*)(ev + 1);
            switch (lv2_midi_message_type(msg)) {
            case LV2_MIDI_MSG_NOTE_ON:
                note_on = msg[1];
                (*note) = max(0.0, min((float)note_on + pitchbend, 127.0));
                (*ui_note) = max(0.0, min((float)note_on + pitchbend, 127.0));
                (*gate) = 1.0;
                (*ui_gate) = 1.0;
                (*panic) = 1.0;
                xmonk->note = (double) (*note);
                xmonk->gate = 1.0;
                xmonk->panic = 1.0;
                gatecounter++;
                add_last_key(&note_on);
                have_midi = true;
            break;
            case LV2_MIDI_MSG_NOTE_OFF:
                note_off = msg[1];
              //  (*note) = (float)note_off;
                if(!(int)floor((*sustain))) remove_last_key(&note_off);
                gatecounter--;
                if (!gatecounter) {
                    (*gate) = 0.0;
                    (*ui_gate) = 0.0;
                    xmonk->gate = 0.0;
                } else {
                    get_last_key();
                }
            break;
            case LV2_MIDI_MSG_CONTROLLER:
                switch (msg[1]) {
                    case LV2_MIDI_CTL_MSB_MODWHEEL:
                    case LV2_MIDI_CTL_LSB_MODWHEEL:
                        (*vowel) = (float) (msg[2]/31.0);
                        (*ui_vowel) = (float) (msg[2]/31.0);
                        xmonk->vowel = (double)(*vowel);
                        have_midi = true;
                    break;
                    case LV2_MIDI_CTL_ALL_SOUNDS_OFF:
                    case LV2_MIDI_CTL_ALL_NOTES_OFF:
                        gatecounter = 0;
                        (*gate) = 0.0;
                        (*ui_gate) = 0.0;
                        (*panic) = 0.0;
                        xmonk->gate = 0.0;
                        xmonk->panic = 0.0;
                    break;
                    case LV2_MIDI_CTL_RESET_CONTROLLERS:
                        pitchbend = 0.0;
                        (*vowel) = 2.0;
                        (*ui_vowel) = 2.0;
                        xmonk->vowel = 2.0;
                    break;
                    default:
                    break;
                }
            break;
            case LV2_MIDI_MSG_BENDER:
                pitchbend = ((msg[2] << 7 | msg[1]) - 8192) * PITCHBEND_INC;
                (*note) = max(0.0, min((float)note_on + pitchbend, 127.0));
                (*ui_note) = max(0.0, min((float)note_on + pitchbend, 127.0));
                xmonk->note = (double) (*note);
            break;
            default:
                have_midi = false;
            break;
            }
        }
    }
    xmonk->compute_static(static_cast<int>(n_samples), output, output1, xmonk);
    reverb->compute_static(static_cast<int>(n_samples), output, output1, output, output1, reverb);
    delay->compute_static(static_cast<int>(n_samples), output, output1, output, output1, delay);
    MXCSR.reset_();
}

void Xmonk_::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
  xmonk->connect_static(port,  data, xmonk);
  reverb->connect_static(port,  data, reverb);
  delay->connect_static(port,  data, delay);
}

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

LV2_Handle 
Xmonk_::instantiate(const LV2_Descriptor* descriptor,
                            double rate, const char* bundle_path,
                            const LV2_Feature* const* features)
{

  LV2_URID_Map* map = NULL;
  for (int i = 0; features[i]; ++i) {
    if (!strcmp(features[i]->URI, LV2_URID__map)) {
        map = (LV2_URID_Map*)features[i]->data;
            break;
        }
    }
  if (!map) {
      return NULL;
  }

  // init the plug-in class
  Xmonk_ *self = new Xmonk_();
  if (!self) {
    return NULL;
  }
  self->map = map;
  self->midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);

  self->init_dsp_((uint32_t)rate);

  return (LV2_Handle)self;
}

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

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

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

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

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

const LV2_Descriptor Xmonk_::descriptor =
{
  PLUGIN_URI,
  Xmonk_::instantiate,
  Xmonk_::connect_port,
  Xmonk_::activate,
  Xmonk_::run,
  Xmonk_::deactivate,
  Xmonk_::cleanup,
  NULL
};


} // end namespace xmonk

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

extern "C"
LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
  switch (index)
    {
    case 0:
      return &xmonk::Xmonk_::descriptor;
    default:
      return NULL;
    }
}

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