#ifndef PROTOTYPE1_SRC_L2_TESTS_R2_LIZA_COLLECTION_H
#define PROTOTYPE1_SRC_L2_TESTS_R2_LIZA_COLLECTION_H

#include "../../liza/instrument.h"

// todo: dfasjshfdasfkjld
// double get_decay_factor(double planned_decay_time, double passes_per_second) {
//     return pow(0.01, 1 / (planned_decay_time * passes_per_second));
// }

/* WeirdGuitar that produces StringTwitchSound */

// todo: remove decay_factor, replace with decay base
// todo: replace ding randomness with something better
typedef struct {
    double rate;
    double ta;
    double decay_base;
    double planned_time;

    double time;
    double* buffer;
    int buffer_size;
    int i;

} StringTwitchSound;

StringTwitchSound StringTwitchSound_new(double frequency, double time, double rate) {
    int buffer_size = (int)(rate / frequency);
    double* buffer = safe_calloc(buffer_size, sizeof(double));
    for (int i = 0; i < buffer_size; i++) {
        /* random noise in [-1, 1] */
        buffer[i] = ((double)rand() / RAND_MAX) * 2.0 - 1.0;
    }
    return (StringTwitchSound){
        .rate = rate, .ta = 0.01, .decay_base = pow(0.01, 1/time), .planned_time = 0.01 + time,
        .buffer = buffer, .buffer_size = buffer_size, .i = 0,
    };
}

void StringTwitchSound_drop(StringTwitchSound self) {
    free(self.buffer);
}

double StringTwitchSound_next(StringTwitchSound* self) {
    double volume_c = (self->time > self->ta) ? pow(self->decay_base, self->time) : self->time / self->ta;
    int j = (self->i + 1) % self->buffer_size;
    double A = self->buffer[self->i];
    double B = self->buffer[j];
    self->buffer[self->i] = 0.5 * (A + B);
    self->i = j;
    self->time += 1. / self->rate;
    return A * volume_c;
}

size_t StringTwitchSound_get_duration(const StringTwitchSound* self) {
    return (size_t)ceil(self->planned_time * self->rate);
}

void LizaSound_Table_StringTwitchSound_drop(void* g) {
    StringTwitchSound_drop(*(StringTwitchSound*)g);
}
double LizaSound_Table_StringTwitchSound_next(void* g) {
    return StringTwitchSound_next(g);
}
size_t LizaSound_Table_StringTwitchSound_get_duration(const void* g) {
    return StringTwitchSound_get_duration(g);
}
const LizaSound_Table LizaSound_Table_StringTwitchSound = {
    .drop = LizaSound_Table_StringTwitchSound_drop,
    .next = LizaSound_Table_StringTwitchSound_next,
    .get_duration = LizaSound_Table_StringTwitchSound_get_duration
};

BoxLizaSound BoxLizaSound_from_StringTwitchSound(StringTwitchSound obj) {
    void* mem = safe_malloc(sizeof(StringTwitchSound));
    memcpy(mem, &obj, sizeof(StringTwitchSound));
    return (BoxLizaSound){.m = mem, .t = &LizaSound_Table_StringTwitchSound};
}


typedef struct {
    double rate;
} WeirdGuitar;

WeirdGuitar WeirdGuitar_new(double rate) {
    return (WeirdGuitar){.rate = rate};
}

StringTwitchSound WeirdGuitar_ding(const WeirdGuitar* self, double frequency, double time) {
    return StringTwitchSound_new(frequency, time, self->rate);
}

void LizaInstrument_Table_WeirdGuitar_drop(void* self) {

}
BoxLizaSound LizaInstrument_Table_WeirdGuitar_ding(const void* self, double frequency, double time) {
    return BoxLizaSound_from_StringTwitchSound(WeirdGuitar_ding(self, frequency, time));
}
const LizaInstrument_Table LizaInstrument_Table_WeirdGuitar = {
    .drop = LizaInstrument_Table_WeirdGuitar_drop,
    .ding = LizaInstrument_Table_WeirdGuitar_ding
};

BoxLizaInstrument BoxLizaInstrument_from_WeirdGuitar(WeirdGuitar obj) {
    void* mem = safe_malloc(sizeof(WeirdGuitar));
    memcpy(mem, &obj, sizeof(WeirdGuitar));
    return (BoxLizaInstrument){.m = mem, .t = &LizaInstrument_Table_WeirdGuitar};
}

/* ElectroBlaster which produces AmplitudeModulationSound */

typedef struct {
    double rate;
    double mod_frequency;
    double second_frequency;
    double low_amplitude;
    double ta;
    double decay_base;
    double planned_time;

    double ph1;
    double ph2;
    double time;
} AmplitudeModulationSound;

double AmplitudeModulationSound_next(AmplitudeModulationSound* self) {
    double volume_c = (self->time > self->ta) ? pow(self->decay_base, self->time) : self->time / self->ta;
    double res = volume_c * (self->low_amplitude + (sin(self->ph2) + 1) * (1 - self->low_amplitude) / 2) * sin(self->ph1);
    self->ph1 += 2 * M_PI * self->mod_frequency / self->rate;
    if (self->ph1 > 2 * M_PI)
        self->ph1 -= 2 * M_PI;
    self->ph2 += 2 * M_PI * self->second_frequency / self->rate;
    if (self->ph2 > 2 * M_PI)
        self->ph2 -= 2 * M_PI;
    self->time += 1. / self->rate;
    return res;
}

size_t AmplitudeModulationSound_get_duration(const AmplitudeModulationSound* self) {
    return (size_t)ceil(self->planned_time * self->rate);
}

void LizaSound_Table_AmplitudeModulationSound_drop(void* self) {

}
double LizaSound_Table_AmplitudeModulationSound_next(void* self) {
    return AmplitudeModulationSound_next(self);
}
size_t LizaSound_Table_AmplitudeModulationSound_get_duration(const void* self) {
    return AmplitudeModulationSound_get_duration(self);
}
const LizaSound_Table LizaSound_Table_AmplitudeModulationSound = {
    .drop = LizaSound_Table_AmplitudeModulationSound_drop,
    .next = LizaSound_Table_AmplitudeModulationSound_next,
    .get_duration = LizaSound_Table_AmplitudeModulationSound_get_duration,
};

BoxLizaSound BoxLizaSound_from_AmplitudeModulationSound(AmplitudeModulationSound obj) {
    void* mem = safe_malloc(sizeof(AmplitudeModulationSound));
    memcpy(mem, &obj, sizeof(AmplitudeModulationSound));
    return (BoxLizaSound){.m = mem, .t = &LizaSound_Table_AmplitudeModulationSound};
}


typedef struct {
    double rate;
} AMKeys;

AMKeys AMKeys_new(double rate) {
    return (AMKeys){rate};
}

AmplitudeModulationSound AMKeys_ding(const AMKeys* self, double frequency, double time) {
    return (AmplitudeModulationSound){
        .rate = self->rate, .mod_frequency = frequency, .second_frequency = 600,
        .low_amplitude = 0.1, .ta = 0.01, .decay_base = pow(0.01, 1/time), .planned_time = 0.01 + time,
        };
}

void LizaInstrument_Table_AMKeys_drop(void* self) {

}
BoxLizaSound LizaInstrument_Table_AMKeys_ding(const void* self, double freq, double time) {
    return BoxLizaSound_from_AmplitudeModulationSound(AMKeys_ding(self, freq, time));
}
const LizaInstrument_Table LizaInstrument_Table_AMKeys = {
    .drop = LizaInstrument_Table_AMKeys_drop,
    .ding = LizaInstrument_Table_AMKeys_ding
};

BoxLizaInstrument BoxLizaInstrument_from_AMKeys(AMKeys obj) {
    void* mem = safe_malloc(sizeof(AMKeys));
    memcpy(mem, &obj, sizeof(AMKeys));
    return (BoxLizaInstrument){.m = mem, .t = &LizaInstrument_Table_AMKeys};
}

/* FMKeys which produces FrequencyModulationSound */

typedef struct {
    double rate;
    double freq_1;
    double freq_2;
    double fmc;
    double ta;
    double decay_base;
    double planned_time;

    double ph1;
    double ph2;
    double time;
} FrequencyModulationSound;

double FrequencyModulationSound_next(FrequencyModulationSound* self) {
    double volume_c = (self->time > self->ta) ? pow(self->decay_base, self->time) : self->time / self->ta;
    double res = volume_c * sin(self->ph1);
    self->ph1 += 2 * M_PI / self->rate * pow(self->freq_1, self->fmc * sin(self->ph2));
    if (self->ph1 > 2 * M_PI)
        self->ph1 -= 2 * M_PI;
    self->ph2 += 2 * M_PI * self->freq_2 / self->rate;
    if (self->ph2 > 2 * M_PI)
        self->ph2 -= 2 * M_PI;
    self->time += 1. / self->rate;
    return res;
}

size_t FrequencyModulationSound_get_duration(const FrequencyModulationSound* self) {
    return (size_t)ceil(self->planned_time * self->rate);
}

void LizaSound_Table_FrequencyModulationSound_drop(void* self) {

}
double LizaSound_Table_FrequencyModulationSound_next(void* self) {
    return FrequencyModulationSound_next(self);
}
size_t LizaSound_Table_FrequencyModulationSound_get_duration(const void* self) {
    return FrequencyModulationSound_get_duration(self);
}
const LizaSound_Table LizaSound_Table_FrequencyModulationSound = {
    .drop = LizaSound_Table_FrequencyModulationSound_drop,
    .next = LizaSound_Table_FrequencyModulationSound_next,
    .get_duration = LizaSound_Table_FrequencyModulationSound_get_duration
};

BoxLizaSound BoxLizaSound_from_FrequencyModulationSound(FrequencyModulationSound obj) {
    void* mem = safe_malloc(sizeof(FrequencyModulationSound));
    memcpy(mem, &obj, sizeof(FrequencyModulationSound));
    return (BoxLizaSound){.m = mem, .t = &LizaSound_Table_FrequencyModulationSound};
}

typedef struct {
    double rate;
} FMKeys;

FMKeys FMKeys_new(double rate) {
    return (FMKeys){rate};
}

FrequencyModulationSound FMKeys_ding(FMKeys* self, double frequency, double time) {
    return (FrequencyModulationSound){
        .rate = self->rate, .freq_1 = frequency, .freq_2 = 600,
        .fmc = 3, .ta = 0.01, .decay_base = pow(0.01, 1/time), .planned_time = 0.01 + time,
    };
}

void LizaInstrument_Table_FMKeys_drop(void* self) {

}
BoxLizaSound LizaInstrument_Table_FMKeys_ding(const void* self, double freq, double time) {
    return BoxLizaSound_from_FrequencyModulationSound(FMKeys_ding(self, freq, time));
}
const LizaInstrument_Table LizaInstrument_Table_FMKeys = {
    .drop = LizaInstrument_Table_FMKeys_drop,
    .ding = LizaInstrument_Table_FMKeys_ding
};

BoxLizaInstrument BoxLizaInstrument_from_FMKeys(FMKeys obj) {
    void* mem = safe_malloc(sizeof(FMKeys));
    memcpy(mem, &obj, sizeof(FMKeys));
    return (BoxLizaInstrument){.m = mem, .t = &LizaInstrument_Table_FMKeys};
}


#endif