#include "gx_plugin.h"
#include <cmath>
#include <stdlib.h>

namespace pluginlib {
namespace reversedelay {

#define N_(x) (x)

class ReverseDelay : public PluginDef {
private:
    float sample_rate;
    float *buffer;
    unsigned int counter;
    unsigned int buf_size;

    unsigned int cur_buf_size;
    float feedback_buf;

    //Params
    float time, feedback, window, drywet;
    //Params buffs
    float time_old, window_old;

    //Indicator
    float buf_indication;

    class overlap_window
    {
    private:
	float val;
	float step;
	float acc;
	unsigned int active_samples;
	unsigned int full_samples;
	unsigned int counter;

    public:
	overlap_window() {
	    val = 0;
	    step = 0;
	    acc = 0;
	    active_samples = 0;
	    full_samples = 0;
	    counter = 0;
	}
	void set_coef(float t /* 0..1 */, unsigned int full_samples) {
	    set_full(0, full_samples, t*full_samples);
	}
	bool set_full(float min_val, unsigned int full_samples, unsigned int active_samples) {
	    if(active_samples >= full_samples) return false;

	    acc = min_val;
	    val = min_val;
	    this->full_samples = full_samples;
	    this->active_samples = active_samples;
	    counter = 0;
	    step = (1 - min_val)/(active_samples/2);

	    return true;
	}
	float get() {
	    if(counter >= 0 && counter < active_samples/2) {
		acc += step;
		counter++;
		return acc;
	    }
	    else if(counter >= active_samples/2 && counter <= full_samples - active_samples/2) {
		counter++;
		return 1;
	    }
	    else if(counter > full_samples - active_samples/2 && counter < full_samples) {
		acc -= step;
		counter++;
		return acc;
	    }
	    else if(counter >= full_samples) {
		float ret_val = acc;
		acc = val;
		counter = 0;
		return ret_val;
	    }
	    return 1;
	}
    };

    overlap_window ow;

    static float reverse_delay_line_impl(float in, float* buf, unsigned int* counter, unsigned int length)
	{
	    float out = 0;

	    //Read data
	    if(*counter < length - 1)
		{
		    unsigned int read_counter = length - 1 - (*counter);
		    out = buf[read_counter];
		}

	    //Write data
	    *(buf + (*counter)) = in;
	    (*counter)++;
	    if ((*counter) > length-1)
		(*counter) = 0;

	    return (out);
	}

public:
    ReverseDelay();
    static void init(unsigned int samplingFreq, PluginDef *plugin);
    static void process(int count, float *input, float *output, PluginDef *plugin);
    static int registerparam(const ParamReg& reg);
    static int uiloader(const UiBuilder& builder, int form);
    static void del_instance(PluginDef *plugin);
};

ReverseDelay::ReverseDelay():
    PluginDef(),
    sample_rate(0) {
    version = PLUGINDEF_VERSION;
    id = "reversedelay";
    name = N_("ReverseDelay");
    category = N_("Echo / Delay");
    mono_audio = process;
    set_samplerate = init;
    register_params = registerparam;
    load_ui = uiloader;
    delete_instance = del_instance;

    buffer = NULL;
    counter = 0;
    buf_size    = 0;
    cur_buf_size = 0;
    feedback_buf = 0;
    time_old = 0;
    window_old = 0;

    buf_indication = 0;
}

int ReverseDelay::registerparam(const ParamReg& reg) {
    ReverseDelay& self = *static_cast<ReverseDelay*>(reg.plugin);
    reg.registerFloatVar("reversedelay.time", N_("Time (ms)"), "S", N_("Delay time in milliseconds"), &self.time, 500, 200, 2000, 1, 0);
    reg.registerFloatVar("reversedelay.feedback", N_("Feedback"), "S", N_("Feedback"), &self.feedback, 0, 0, 1, 0.05, 0);
    reg.registerFloatVar("reversedelay.window", N_("Window (%)"), "S", N_("Crossfade between delayed chunks in percents"), &self.window, 50, 0, 100, 1, 0);
    reg.registerFloatVar("reversedelay.drywet", N_("Dry/Wet"), "S", N_("Dry/Wet"), &self.drywet, 0.5, 0, 1, 0.05, 0);

    reg.registerFloatVar("reversedelay.buf_indication", "", "SNO", "", &self.buf_indication, 0.0, 0.0, 1.0, 0.01, 0);
    self.buf_indication = 0;

    return 0;
}

void ReverseDelay::init(unsigned int samplingFreq, PluginDef *plugin) {
    ReverseDelay& self = *static_cast<ReverseDelay*>(plugin);
    self.sample_rate = samplingFreq;

    float* old_buf = self.buffer;

    //Provide dual buf size, with 2 seconds length for every part
    unsigned int new_buf_size = (unsigned int)(samplingFreq * 2 * 2);

    float *new_buf = new float[new_buf_size];
    for (size_t i = 0; i < new_buf_size; i++)
	new_buf[i] = 0.0f;

    // Assign new pointer and size
    self.buffer         = new_buf;
    self.buf_size       = new_buf_size;

    // Delete old buffer
    if (old_buf != NULL)
	delete [] old_buf;
}

void ReverseDelay::process(int count, float *input, float *output, PluginDef *plugin) {
    ReverseDelay& self = *static_cast<ReverseDelay*>(plugin);

    //Update params
    if(self.time_old != self.time) {
	self.cur_buf_size = (self.time/1000.0)*self.sample_rate;
	self.counter = 0;
	self.ow.set_coef((self.window)/(100.0 + 1.0), self.cur_buf_size/2); //Avoid to pass 1

	self.time_old = self.time;
	self.window_old = self.window;
    }
    else if(self.window_old != self.window) {
	self.ow.set_coef((self.window)/(100.0 + 1.0), self.cur_buf_size/2);
	self.window_old = self.window;
    }

    for (int i = 0; i < count; ++i) {
        float in = input[i];
        float out = 0;

        //Update indicator
        self.buf_indication = ((float)self.counter)/self.cur_buf_size;

        //Process
        out = reverse_delay_line_impl(in + self.feedback_buf * self.feedback, self.buffer, &self.counter, self.cur_buf_size);
        self.feedback_buf = out;

        out*= self.ow.get();

        out = out* self.drywet + in* (1 - self.drywet);
        output[i] = out;
    }
}

int ReverseDelay::uiloader(const UiBuilder& b, int form) {
    if (form & UI_FORM_GLADE) {
        b.load_glade_file("reversedelay_ui.glade");
        return 0;
    }
    if (!(form & UI_FORM_STACK)) {
	return -1;
    }
    b.openHorizontalhideBox("");
    {
        b.create_master_slider("reversedelay.drywet",N_("Dry/Wet"));
    }
    b.closeBox();
    b.openHorizontalBox("");
    {
	b.create_small_rackknob("reversedelay.time",N_("Time (ms)"));

        b.openVerticalBox("");
        b.insertSpacer();
        b.create_port_display("reversedelay.buf_indication", N_("Buf state"));
        b.insertSpacer();
        b.closeBox();

        b.create_small_rackknob("reversedelay.feedback",N_("Feedback"));
	b.create_small_rackknob("reversedelay.window",N_("Window (%)"));
	b.create_small_rackknobr("reversedelay.drywet",N_("Dry/Wet"));

    }
    b.closeBox();
    return 0;
}

void ReverseDelay::del_instance(PluginDef *plugin)
{
    ReverseDelay& self = *static_cast<ReverseDelay*>(plugin);
    delete [] self.buffer;
    delete static_cast<ReverseDelay*>(plugin);
}


#if true

PluginDef *plugin() {
    return new ReverseDelay;
}

#else

extern "C" __attribute__ ((visibility ("default"))) int
get_gx_plugin(unsigned int idx, PluginDef **pplugin)
{
    const int count = 1;
    if (!pplugin) {
	return count;
    }
    switch (idx) {
    case 0: *pplugin = new ReverseDelay; return count;
    default: *pplugin = 0; return -1;
    }
}

#endif

} // end namespace reverse_delay
} // end namespace pluginlib
