#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <inttypes.h>
#include "sndprint.h"
#include "ngspice/ngspice.h"

#if defined(HAVE_LIBSNDFILE) && defined(HAVE_LIBSAMPLERATE)
#include <sndfile.h>

static int o_samplerate = 48000;
static int o_sndfmt = (SF_FORMAT_WAV | SF_FORMAT_PCM_24);
static float o_mult = 1.0;
static float o_off = 0.0;

//////////////////////////////////   aliki  //////////////////////////////////

#define HDRSIZE 256

static void* my_open_aliki(char* fn, int nchannel) {
    char p[HDRSIZE];
    FILE* aldfile;
    if ((aldfile = fopen(fn, "w")) == 0) {
        fprintf(stderr, "Error: Not able to open output file '%s'\n", fn);
        controlled_exit(1);
    }

    strcpy(p, "aliki");
    p[6] = p[7] = 0;
    *(uint32_t*)(p + 8) = 2; //_vers
    *(uint32_t*)(p + 12) = nchannel; // _type;
    *(uint32_t*)(p + 16) = o_samplerate; //_rate_n;
    *(uint32_t*)(p + 20) = 1; //_rate_d;
    *(uint32_t*)(p + 24) = 486239; //_n_fram;
    *(uint32_t*)(p + 28) = 1; // _n_sect;
    *(uint32_t*)(p + 32) = 0; // _tref_i;
    *(uint32_t*)(p + 36) = 0; // _tref_n;
    *(uint32_t*)(p + 40) = 1; // _tref_d;
    *(uint32_t*)(p + 44) = 0; // _bits;

    memset(p + 48, 0, HDRSIZE - 48);
    if (fwrite(p, 1, HDRSIZE, aldfile) != HDRSIZE) {
        fprintf(stderr, "Error: Not able to write aliki header to '%s'\n", fn);
        fclose(aldfile);
        controlled_exit(1);
    }
    return ((void*)aldfile);
}

static size_t my_write_aliki(void* d, float val) {
    return(fwrite(&val, sizeof(float), 1, (FILE*)d));
}

static void my_close_aliki(void* d) {
    fclose((FILE*)d);
}


//////////////////////////////////  sndfile //////////////////////////////////

typedef struct {
    SNDFILE* outfile;
    int sf_channels;
    int sf_bptr;
    float* sf_buf;
} SSFILE;

static void* my_open_sf(char* fn, int nchannel) {
    SSFILE* d = calloc(1, sizeof(SSFILE));
    SF_INFO sfinfo;

    sfinfo.samplerate = o_samplerate;
    sfinfo.channels = nchannel;
    sfinfo.frames = 0;
    sfinfo.format = o_sndfmt;

    d->sf_channels = nchannel;
    d->sf_bptr = 0;
    d->sf_buf = calloc(nchannel, sizeof(float));

    if ((d->outfile = sf_open(fn, SFM_WRITE, &sfinfo)) == NULL) {
        fprintf(stderr, "Error: Not able to open output file '%s'\n", fn);
        controlled_exit(1);
    }

#if 1
    sf_command(d->outfile, SFC_SET_UPDATE_HEADER_AUTO, NULL, SF_TRUE);
    sf_command(d->outfile, SFC_SET_CLIPPING, NULL, SF_TRUE);
#endif

    return ((void*)d);
}

static size_t my_write_sf(void* d, float val) {
    SSFILE* p = (SSFILE*)d;
    p->sf_buf[p->sf_bptr++] = val;
    if (p->sf_bptr >= p->sf_channels) {
        sf_writef_float(p->outfile, p->sf_buf, 1);
        p->sf_bptr = 0;
    }
    return (1);
}

static void my_close_sf(void* d) {
    sf_close(((SSFILE*)d)->outfile);
    free(((SSFILE*)d)->sf_buf);
    free((SSFILE*)d);
}





//////////////////////////////////   spice  //////////////////////////////////

typedef struct SP_BUF {
    double tme;
    double* val;
} SP_BUF;

static void (*p_close)(void*);
static void* (*p_open)(char*, int);
static size_t (*p_write)(void*, float);
static void* outfile;
static uint32_t sample;
static int sp_nchannel;
#define SP_MAX (2)
SP_BUF* sp_buf;
static char* filename = NULL;

#define HAVE_SRC

#ifndef HAVE_SRC
#define OVERSAMPLING (1.0)
#else
#include <samplerate.h>
#define OBUFSIZE 256
static int oversampling = 4;
#define OVERSAMPLING ((double) oversampling)
static SRC_STATE* rabbit;
static int rabbit_err;
static float* interleaved;
static float* resampled;
static int iptr = 0;

static int resample_wrapper(void* d, float val) {
    interleaved[iptr++] = val;
    size_t ibufsize = sp_nchannel * OBUFSIZE * oversampling;
    size_t obufsize = sp_nchannel * OBUFSIZE;
    if (iptr == ibufsize) {

        SRC_DATA src_data;
        src_data.data_in = interleaved;
        src_data.data_out = resampled;
        src_data.input_frames = iptr / sp_nchannel;
        src_data.output_frames = OBUFSIZE;
        src_data.end_of_input = 0;
        src_data.src_ratio = 1.0 / OVERSAMPLING;
        src_data.input_frames_used = 0;
        src_data.output_frames_gen = 0;

        int procerr = src_process(rabbit, &src_data);
        if (procerr) {
            const char* errmsg = src_strerror(procerr);
            fprintf(stderr, "Error: Process src_process failed with message: %s\n", errmsg);
        }

        if (src_data.output_frames_gen * sp_nchannel != obufsize) {
            fprintf(stderr, "Warning: resample: out %li != %i\n", src_data.output_frames_gen * sp_nchannel, (int)obufsize);
        }

        if (src_data.input_frames_used * sp_nchannel != iptr) {
            fprintf(stderr, "Warning: resample: in: %li != %i\n", src_data.input_frames_used * sp_nchannel, iptr);
        }

        int i;
        for (i = 0; i < src_data.output_frames_gen * sp_nchannel; i++)
            p_write(d, resampled[i]);

        iptr = 0;
        return (src_data.output_frames_gen * sp_nchannel);
    }
    return (0);
}

#endif

void snd_configure(char* fn, int srate, int fmt, double mult, double off, int os) {
    if (filename) free(filename);
    filename = strdup(fn);

    o_samplerate = srate;
    o_mult = (float)mult;
    o_off = (float)off;
    oversampling = os;
    if (fmt != 0) {
        p_close = &my_close_sf;
        p_open = &my_open_sf;
        p_write = &my_write_sf;
        o_sndfmt = (fmt > 0) ? fmt : (SF_FORMAT_WAV | SF_FORMAT_PCM_24);
        printf("info: opened snd file '%s'\n", filename);
    }
    else {
        p_close = &my_close_aliki;
        p_open = &my_open_aliki;
        p_write = &my_write_aliki;
        printf("info: opened aliki file '%s'\n", filename);
    }
}

int snd_format(char* fmt) {
    int f = atoi(fmt);
    if (!strcmp(fmt, "wav")) f = (SF_FORMAT_WAV | SF_FORMAT_PCM_24);
    if (!strcmp(fmt, "wav16")) f = (SF_FORMAT_WAV | SF_FORMAT_PCM_16);
    if (!strcmp(fmt, "wav24")) f = (SF_FORMAT_WAV | SF_FORMAT_PCM_24);
    if (!strcmp(fmt, "wav32")) f = (SF_FORMAT_WAV | SF_FORMAT_PCM_32);
    if (!strcmp(fmt, "aiff")) f = (SF_FORMAT_AIFF | SF_FORMAT_PCM_16);
    if (!strcmp(fmt, "aliki")) f = 0;
    return (f);
}

void snd_init(int nchannel) {
    int i;
    if (!filename) snd_configure("spice.wav", 48000, o_sndfmt, o_mult, o_off, oversampling);
    outfile = p_open(filename, nchannel);
    sp_nchannel = nchannel;
    sp_buf = calloc(SP_MAX, sizeof(SP_BUF));
    for (i = 0; i < SP_MAX; i++) {
        sp_buf[i].tme = 0.0;
        sp_buf[i].val = calloc(nchannel, sizeof(double));
    }
    sample = 0;
#ifdef HAVE_SRC
    interleaved = calloc(nchannel * OBUFSIZE * oversampling, sizeof(float));
    resampled = calloc(nchannel * OBUFSIZE, sizeof(float));
    rabbit = src_new(SRC_SINC_BEST_QUALITY, nchannel, &rabbit_err);
    src_set_ratio(rabbit, 1.0 / OVERSAMPLING);
    src_reset(rabbit);
#endif
}

int snd_send(double tme, int c, double out) {
    int i;
    int rv = 0;
    static bool shown = FALSE;
    if (c == 0) for (i = SP_MAX - 1; i > 0; i--) {
        memcpy(&(sp_buf[i]), &(sp_buf[i - 1]), sizeof(SP_BUF));
    }
    sp_buf[0].tme = tme * OVERSAMPLING;
    sp_buf[0].val[c] = out;
#ifdef SND_DEBUG
    printf("INFO : c:%i tme:%f fsmp:%i val:%f\n", c, tme, sample, out);
#endif

    if (sample == 0) {
        if (c == (sp_nchannel - 1))
            sample = (uint32_t)ceil(tme * OVERSAMPLING);
        return (0);
    }

    if ((sample) < ceil(tme * OVERSAMPLING)) {
        if (!(sp_buf[0].tme > sample))
            fprintf(stderr, "Error: Oversampling error 1 %f !> %i\n", sp_buf[0].tme, sample);
        if (!shown && (sp_buf[1].tme > sample)) {
            fprintf(stderr, "Error: Oversampling error 2 %f !< %i\n", sp_buf[1].tme, sample);
            fprintf(stderr, "    Time step too large\n");
            shown = TRUE;
        }
#if 0 // DEBUG
        if ((sp_buf[0].tme - sample) > 1.0)
            fprintf(stderr, "Error: Oversampling error 3 large timestep: dv/dt=%e dt:%f dv:%e\n",
            (sp_buf[0].val[c] - sp_buf[1].val[c]) / (sp_buf[0].tme - sample),
            (sp_buf[0].tme - sample), (sp_buf[0].val[c] - sp_buf[1].val[c]));
#endif

        // linear
        double p = (sp_buf[0].tme - sample) / (sp_buf[0].tme - sp_buf[1].tme);
        double val = sp_buf[0].val[c] - p * (sp_buf[0].val[c] - sp_buf[1].val[c]);
#ifdef SND_DEBUG
        printf("DEBUG: writing c:%i p:%f*[%f - %f] v:%f\n", c, p, sp_buf[0].val[c], sp_buf[1].val[c], val);
#endif

#ifdef HAVE_SRC
        rv = resample_wrapper(outfile, o_off + (float)val * o_mult);
#else
        p_write(outfile, o_off + val * o_mult);
        if (c == (sp_nchannel - 1)) rv = 1;
#endif
        if (c == (sp_nchannel - 1)) sample++;

    }
    else {
#ifdef SND_DEBUG
        printf(" ^^^^^^^^^ SKIPPED ^^^^^^^^^\n");
#endif
    }
    return (rv);
}

void snd_close(void) {
#ifdef HAVE_SRC
    while (!resample_wrapper(outfile, 0.0)); // flush buffer.
#endif
    p_close(outfile);
    free(filename); filename = NULL;
#ifdef HAVE_SRC
    free(interleaved);
    free(resampled);
#endif
    /*
    int i;
    for (i=0; i< SP_MAX; i){
      free (sp_buf[i].val);
      sp_buf[i].val=NULL;
    }
    */
    free(sp_buf);
}

double snd_get_samplerate(void) {
    return ((double)o_samplerate);
}

/* vi:set ts=8 sts=2 sw=2: */

#endif // HAVE_LIBSNDFILE
