/*                                                     -*- linux-c -*-
    Copyright (C) 2004 Tom Szilagyi

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: tap_reflector.c,v 1.1 2004/06/18 20:12:41 tszilagyi Exp $
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <lv2.h>
#include "tap_utils.h"


/* The Unique ID of the plugin: */

#define ID_MONO         2154

/* The port numbers for the plugin: */

#define FRAGMENT        0
#define DRYLEVEL        1
#define WETLEVEL        2
#define INPUT           3
#define OUTPUT          4

/* Total number of ports */


#define PORTCOUNT_MONO   5


/* minimum & maximum fragment length [ms] */
#define MIN_FRAGMENT_LEN 20
#define MAX_FRAGMENT_LEN 2000

/* in kHz */
#define MAX_SAMPLE_RATE 192


#define COS_TABLE_SIZE 1024
float cos_table[COS_TABLE_SIZE];
int flagcos = 0;


/* The structure used to hold port connection information and state */

typedef struct {
    float * fragment;
    //float oldfragment; //for parameter smoothing
    float * drylevel;
    float olddry;     //for parameter smoothing
    float * wetlevel;
    float oldwet;     //for parameter smoothing
    float * input;
    float * output;



    float * ring0;
    unsigned long buflen0;
    unsigned long pos0;
    float * ring1;
    unsigned long buflen1;
    unsigned long pos1;
    float * delay1;
    unsigned long delay_buflen1;
    unsigned long delay_pos1;
    float * ring2;
    unsigned long buflen2;
    unsigned long pos2;
    float * delay2;
    unsigned long delay_buflen2;
    unsigned long delay_pos2;

    unsigned long fragment_pos;

    unsigned long sample_rate;
} Reflector;



/* Construct a new plugin instance. */
LV2_Handle
instantiate_Reflector(const LV2_Descriptor * Descriptor, double SampleRate, const char* bundle_path, const LV2_Feature* const* features) {

        LV2_Handle * ptr;

        int i;


        if(flagcos == 0)
        {
            for (i = 0; i < COS_TABLE_SIZE; i++)
                cos_table[i] = cosf(i * 2.0f * M_PI / COS_TABLE_SIZE);

            flagcos++;
        }

    if ((ptr = malloc(sizeof(Reflector))) != NULL) {
        ((Reflector *)ptr)->sample_rate = SampleRate;
        ((Reflector *)ptr)->olddry = 0.0f;  //initinating values for smoothing
        ((Reflector *)ptr)->oldwet = 0.0f;
        //((Reflector *)ptr)->oldfragment = 0.0f;

        if ((((Reflector *)ptr)->ring0 =
             calloc(2 * MAX_FRAGMENT_LEN * MAX_SAMPLE_RATE, sizeof(float))) == NULL)
            return NULL;
        ((Reflector *)ptr)->buflen0 = 2 * MAX_FRAGMENT_LEN * SampleRate / 1000;
        ((Reflector *)ptr)->pos0 = 0;

        if ((((Reflector *)ptr)->ring1 =
             calloc(2 * MAX_FRAGMENT_LEN * MAX_SAMPLE_RATE, sizeof(float))) == NULL)
            return NULL;
        ((Reflector *)ptr)->buflen1 = 2 * MAX_FRAGMENT_LEN * SampleRate / 1000;
        ((Reflector *)ptr)->pos1 = 0;

        if ((((Reflector *)ptr)->delay1 =
             calloc(2 * MAX_FRAGMENT_LEN * MAX_SAMPLE_RATE, sizeof(float))) == NULL)
            return NULL;
        ((Reflector *)ptr)->delay_buflen1 = 2 * MAX_FRAGMENT_LEN * SampleRate / 3000;
        ((Reflector *)ptr)->pos1 = 0;

        if ((((Reflector *)ptr)->ring2 =
             calloc(2 * MAX_FRAGMENT_LEN * MAX_SAMPLE_RATE, sizeof(float))) == NULL)
            return NULL;
        ((Reflector *)ptr)->buflen2 = 2 * MAX_FRAGMENT_LEN * SampleRate / 1000;
        ((Reflector *)ptr)->pos2 = 0;

        if ((((Reflector *)ptr)->delay2 =
             calloc(2 * MAX_FRAGMENT_LEN * MAX_SAMPLE_RATE, sizeof(float))) == NULL)
            return NULL;
        ((Reflector *)ptr)->delay_buflen2 = 4 * MAX_FRAGMENT_LEN * SampleRate / 3000;
        ((Reflector *)ptr)->pos2 = 0;

        return ptr;
    }
        return NULL;
}


void
activate_Reflector(LV2_Handle Instance) {


    Reflector * ptr = (Reflector *)Instance;
    unsigned long i;

    for (i = 0; i < ptr->buflen0; i++)
        ptr->ring0[i] = 0.0f;
    ptr->pos0 = 0;
    for (i = 0; i < ptr->buflen1; i++)
        ptr->ring1[i] = 0.0f;
    ptr->pos1 = 0;
    for (i = 0; i < ptr->buflen2; i++)
        ptr->ring2[i] = 0.0f;
    ptr->pos2 = 0;

    for (i = 0; i < ptr->delay_buflen1; i++)
        ptr->delay1[i] = 0.0f;
    ptr->delay_pos1 = 0;
    for (i = 0; i < ptr->delay_buflen2; i++)
        ptr->delay2[i] = 0.0f;
    ptr->delay_pos2 = 0;

    ptr->fragment_pos = 0;
}





/* Connect a port to a data location. */
void
connect_port_Reflector(LV2_Handle Instance,
               uint32_t Port,
               void * DataLocation) {


    Reflector * ptr = (Reflector *)Instance;

    switch (Port) {
    case FRAGMENT:
        ptr->fragment = (float *) DataLocation;
        break;
    case DRYLEVEL:
        ptr->drylevel = (float *) DataLocation;
        break;
    case WETLEVEL:
        ptr->wetlevel = (float *) DataLocation;
        break;
    case INPUT:
        ptr->input = (float *) DataLocation;
        break;
    case OUTPUT:
        ptr->output = (float *) DataLocation;
        break;
    }
}



void
run_Reflector(LV2_Handle Instance,
          uint32_t SampleCount) {


    Reflector * ptr = (Reflector *)Instance;
    float * input = ptr->input;
    float * output = ptr->output;

    float calcdry = (*(ptr->drylevel)+ptr->olddry)*0.5;  //first smooth, then limit
    ptr->olddry = calcdry;
    float drylevel = db2lin(LIMIT(calcdry,-90.0f,20.0f));

    float calcwet = (*(ptr->wetlevel)+ptr->oldwet)*0.5; //first smooth, then limit
    ptr->oldwet = calcwet;
    float wetlevel = 0.333333f * db2lin(LIMIT(calcwet,-90.0f,20.0f));


    float fragment = LIMIT(*(ptr->fragment),(float)MIN_FRAGMENT_LEN,(float)MAX_FRAGMENT_LEN);
    //ptr->oldfragment = fragment;

    unsigned long sample_index;
    unsigned long sample_count = SampleCount;

    float in = 0.0f;
    float in1 = 0.0f;
    float in2 = 0.0f;
    float out_0 = 0.0f;
    float out_1 = 0.0f;
    float out_2 = 0.0f;

    unsigned long fragment_pos1 = 0;
    unsigned long fragment_pos2 = 0;

    unsigned long arg_0 = 0;
    float am_0 = 0.0f;
    unsigned long arg_1 = 0;
    float am_1 = 0.0f;
    unsigned long arg_2 = 0;
    float am_2 = 0.0f;

    ptr->buflen0 = 2 * fragment * ptr->sample_rate / 1000.0f;
    ptr->buflen1 = ptr->buflen0;
    ptr->buflen2 = ptr->buflen0;
    ptr->delay_buflen1 = ptr->buflen0 / 3;
    ptr->delay_buflen2 = 2 * ptr->buflen0 / 3;


    for (sample_index = 0; sample_index < sample_count; sample_index++) {


        in = *(input++);
        in1 = push_buffer(in, ptr->delay1, ptr->delay_buflen1, &(ptr->delay_pos1));
        in2 = push_buffer(in, ptr->delay2, ptr->delay_buflen2, &(ptr->delay_pos2));

        push_buffer(in2, ptr->ring0, ptr->buflen0, &(ptr->pos0));
        push_buffer(in1, ptr->ring1, ptr->buflen1, &(ptr->pos1));
        push_buffer(in, ptr->ring2, ptr->buflen2, &(ptr->pos2));

        fragment_pos1 = (ptr->fragment_pos + ptr->buflen0 / 3) % ptr->buflen0;
        fragment_pos2 = (ptr->fragment_pos + 2 * ptr->buflen1 / 3) % ptr->buflen1;

        // printf("RING 0: %f, BUFLEN: %lu, POS0:%lu, CONTA:%lu \n", *(ptr->ring0), ptr->buflen0, ptr->pos0, ptr->buflen0 - ptr->fragment_pos - 1);

        out_0 = read_buffer(ptr->ring0, ptr->buflen0, ptr->pos0,
                    ptr->buflen0 - ptr->fragment_pos - 1);


        // printf("N PASSO\n");

        out_1 = read_buffer(ptr->ring1, ptr->buflen1, ptr->pos1,
                    ptr->buflen1 - fragment_pos1 - 1);


        out_2 = read_buffer(ptr->ring2, ptr->buflen2, ptr->pos2,
                    ptr->buflen2 - fragment_pos2 - 1);


        ptr->fragment_pos += 2;
        if (ptr->fragment_pos >= ptr->buflen0)
            ptr->fragment_pos = 0;

        arg_0 = (float)ptr->fragment_pos / (float)ptr->buflen0 * COS_TABLE_SIZE;
        am_0 = 1.0f - cos_table[arg_0];
        arg_1 = (float)fragment_pos1 / (float)ptr->buflen1 * COS_TABLE_SIZE;
        am_1 = 1.0f - cos_table[arg_1];
        arg_2 = (float)fragment_pos2 / (float)ptr->buflen2 * COS_TABLE_SIZE;
        am_2 = 1.0f - cos_table[arg_2];


        *(output++) = drylevel * in + wetlevel *
            (am_0 * out_0 + am_1 * out_1 + am_2 * out_2);
    }

}



//void
//set_run_adding_gain_Reflector(LV2_Handle Instance, float gain) {

    //Reflector * ptr = (Reflector *)Instance;

    //ptr->run_adding_gain = gain;
//}



//void
//run_adding_Reflector(LV2_Handle Instance,
             //unsigned long SampleCount) {

    //Reflector * ptr = (Reflector *)Instance;
    //float * input = ptr->input;
    //float * output = ptr->output;
    //float drylevel = db2lin(LIMIT(*(ptr->drylevel),-90.0f,20.0f));
    //float wetlevel = 0.333333f * db2lin(LIMIT(*(ptr->wetlevel),-90.0f,20.0f));
    //float fragment = LIMIT(*(ptr->fragment),(float)MIN_FRAGMENT_LEN,(float)MAX_FRAGMENT_LEN);

    //unsigned long sample_index;
    //unsigned long sample_count = SampleCount;

    //float in = 0.0f;
    //float in1 = 0.0f;
    //float in2 = 0.0f;
    //float out_0 = 0.0f;
    //float out_1 = 0.0f;
    //float out_2 = 0.0f;

    //unsigned long fragment_pos1 = 0;
    //unsigned long fragment_pos2 = 0;

    //unsigned long arg_0 = 0;
    //float am_0 = 0.0f;
    //unsigned long arg_1 = 0;
    //float am_1 = 0.0f;
    //unsigned long arg_2 = 0;
    //float am_2 = 0.0f;

    //ptr->buflen0 = 2 * fragment * ptr->sample_rate / 1000.0f;
    //ptr->buflen1 = ptr->buflen0;
    //ptr->buflen2 = ptr->buflen0;
    //ptr->delay_buflen1 = ptr->buflen0 / 3;
    //ptr->delay_buflen2 = 2 * ptr->buflen0 / 3;

    //for (sample_index = 0; sample_index < sample_count; sample_index++) {

        //in = *(input++);
        //in1 = push_buffer(in, ptr->delay1, ptr->delay_buflen1, &(ptr->delay_pos1));
        //in2 = push_buffer(in, ptr->delay2, ptr->delay_buflen2, &(ptr->delay_pos2));

        //push_buffer(in2, ptr->ring0, ptr->buflen0, &(ptr->pos0));
        //push_buffer(in1, ptr->ring1, ptr->buflen1, &(ptr->pos1));
        //push_buffer(in, ptr->ring2, ptr->buflen2, &(ptr->pos2));

        //fragment_pos1 = (ptr->fragment_pos + ptr->buflen0 / 3) % ptr->buflen0;
        //fragment_pos2 = (ptr->fragment_pos + 2 * ptr->buflen1 / 3) % ptr->buflen1;

        //out_0 = read_buffer(ptr->ring0, ptr->buflen0, ptr->pos0,
                    //ptr->buflen0 - ptr->fragment_pos - 1);
        //out_1 = read_buffer(ptr->ring1, ptr->buflen1, ptr->pos1,
                    //ptr->buflen1 - fragment_pos1 - 1);
        //out_2 = read_buffer(ptr->ring2, ptr->buflen2, ptr->pos2,
                    //ptr->buflen2 - fragment_pos2 - 1);

        //ptr->fragment_pos += 2;
        //if (ptr->fragment_pos >= ptr->buflen0)
            //ptr->fragment_pos = 0;

        //arg_0 = (float)ptr->fragment_pos / (float)ptr->buflen0 * COS_TABLE_SIZE;
        //am_0 = 1.0f - cos_table[arg_0];
        //arg_1 = (float)fragment_pos1 / (float)ptr->buflen1 * COS_TABLE_SIZE;
        //am_1 = 1.0f - cos_table[arg_1];
        //arg_2 = (float)fragment_pos2 / (float)ptr->buflen2 * COS_TABLE_SIZE;
        //am_2 = 1.0f - cos_table[arg_2];

        //*(output++) += ptr->run_adding_gain *
            //(drylevel * in + wetlevel * (am_0 * out_0 + am_1 * out_1 + am_2 * out_2));
    //}
//}




/* Throw away a Reflector effect instance. */
void
cleanup_Reflector(LV2_Handle Instance) {


    Reflector * ptr = (Reflector *)Instance;
    free(ptr->ring0);
    free(ptr->ring1);
    free(ptr->ring2);
    free(ptr->delay1);
    free(ptr->delay2);
    free(Instance);
}

const void*
extension_data_Reflector(const char* uri)
{
    return NULL;
}


static const
LV2_Descriptor Descriptor = {
    "http://moddevices.com/plugins/tap/reflector",
    instantiate_Reflector,
    connect_port_Reflector,
    activate_Reflector,
    run_Reflector,
    NULL,
    cleanup_Reflector,
    extension_data_Reflector
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index)
{
    if (index == 0) return &Descriptor;
    else return NULL;

}

//LV2_Descriptor * mono_descriptor = NULL;



///* _init() is called automatically when the plugin library is first
   //loaded. */
//void
//_init() {

    //int i;
    //char ** port_names;
    //LV2_PortDescriptor * port_descriptors;
    //LV2_PortRangeHint * port_range_hints;

    //if ((mono_descriptor =
         //(LV2_Descriptor *)malloc(sizeof(LV2_Descriptor))) == NULL)
        //exit(1);

    //for (i = 0; i < COS_TABLE_SIZE; i++)
        //cos_table[i] = cosf(i * 2.0f * M_PI / COS_TABLE_SIZE);


    //mono_descriptor->UniqueID = ID_MONO;
    //mono_descriptor->Label = strdup("tap_reflector");
    //mono_descriptor->Properties = LV2_PROPERTY_HARD_RT_CAPABLE;
    //mono_descriptor->Name = strdup("TAP Reflector");
    //mono_descriptor->Maker = strdup("Tom Szilagyi");
    //mono_descriptor->Copyright = strdup("GPL");
    //mono_descriptor->PortCount = PORTCOUNT_MONO;

    //if ((port_descriptors =
         //(LV2_PortDescriptor *)calloc(PORTCOUNT_MONO, sizeof(LV2_PortDescriptor))) == NULL)
        //exit(1);

    //mono_descriptor->PortDescriptors = (const LV2_PortDescriptor *)port_descriptors;
    //port_descriptors[FRAGMENT] = LV2_PORT_INPUT | LV2_PORT_CONTROL;
    //port_descriptors[DRYLEVEL] = LV2_PORT_INPUT | LV2_PORT_CONTROL;
    //port_descriptors[WETLEVEL] = LV2_PORT_INPUT | LV2_PORT_CONTROL;
    //port_descriptors[INPUT] = LV2_PORT_INPUT | LV2_PORT_AUDIO;
    //port_descriptors[OUTPUT] = LV2_PORT_OUTPUT | LV2_PORT_AUDIO;

    //if ((port_names =
         //(char **)calloc(PORTCOUNT_MONO, sizeof(char *))) == NULL)
        //exit(1);

    //mono_descriptor->PortNames = (const char **)port_names;
    //port_names[FRAGMENT] = strdup("Fragment Length [ms]");
    //port_names[DRYLEVEL] = strdup("Dry Level [dB]");
    //port_names[WETLEVEL] = strdup("Wet Level [dB]");
    //port_names[INPUT] = strdup("Input");
    //port_names[OUTPUT] = strdup("Output");

    //if ((port_range_hints =
         //((LV2_PortRangeHint *)calloc(PORTCOUNT_MONO, sizeof(LV2_PortRangeHint)))) == NULL)
        //exit(1);

    //mono_descriptor->PortRangeHints = (const LV2_PortRangeHint *)port_range_hints;
    //port_range_hints[FRAGMENT].HintDescriptor =
        //(LV2_HINT_BOUNDED_BELOW |
         //LV2_HINT_BOUNDED_ABOVE |
         //LV2_HINT_DEFAULT_LOW);
    //port_range_hints[DRYLEVEL].HintDescriptor =
        //(LV2_HINT_BOUNDED_BELOW |
         //LV2_HINT_BOUNDED_ABOVE |
         //LV2_HINT_DEFAULT_MINIMUM);
    //port_range_hints[WETLEVEL].HintDescriptor =
        //(LV2_HINT_BOUNDED_BELOW |
         //LV2_HINT_BOUNDED_ABOVE |
         //LV2_HINT_DEFAULT_0);
    //port_range_hints[FRAGMENT].LowerBound = (float)MIN_FRAGMENT_LEN;
    //port_range_hints[FRAGMENT].UpperBound = (float)MAX_FRAGMENT_LEN;
    //port_range_hints[DRYLEVEL].LowerBound = -90.0f;
    //port_range_hints[DRYLEVEL].UpperBound = 20.0f;
    //port_range_hints[WETLEVEL].LowerBound = -90.0f;
    //port_range_hints[WETLEVEL].UpperBound = 20.0f;
    //port_range_hints[INPUT].HintDescriptor = 0;
    //port_range_hints[OUTPUT].HintDescriptor = 0;
    //mono_descriptor->instantiate = instantiate_Reflector;
    //mono_descriptor->connect_port = connect_port_Reflector;
    //mono_descriptor->activate = activate_Reflector;
    //mono_descriptor->run = run_Reflector;
    //mono_descriptor->run_adding = run_adding_Reflector;
    //mono_descriptor->set_run_adding_gain = set_run_adding_gain_Reflector;
    //mono_descriptor->deactivate = NULL;
    //mono_descriptor->cleanup = cleanup_Reflector;
//}


//void
//delete_descriptor(LV2_Descriptor * descriptor) {
    ////unsigned long index;
    //if (descriptor) {
        //free((char *)descriptor->Label);
        //free((char *)descriptor->Name);
        //free((char *)descriptor->Maker);
        //free((char *)descriptor->Copyright);
        //free((LV2_PortDescriptor *)descriptor->PortDescriptors);
        //for (index = 0; index < descriptor->PortCount; index++)
            //free((char *)(descriptor->PortNames[index]));
        //free((char **)descriptor->PortNames);
        //free((LV2_PortRangeHint *)descriptor->PortRangeHints);
        //free(descriptor);
    //}
//}


///* _fini() is called automatically when the library is unloaded. */
//void
//_fini() {
    //delete_descriptor(mono_descriptor);
//}


///* Return a descriptor of the requested plugin type. */
//const LV2_Descriptor *
//lv2_descriptor(unsigned long Index) {

    //switch (Index) {
    //case 0:
        //return mono_descriptor;
    //default:
        //return NULL;
    //}
//}
