/*
#   iswr.c: inspired by libsamplerate's callback api
#   Copyright (C) 2025 Stephen Fairchild (s-fairchild@users.sourceforge.net)
#
#   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 in the file entitled COPYING.
#   If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <sys/param.h>
#include <samplerate.h>
#include "iswr.h"

#define MAGIC  ('I' << 24 | 'S' << 16 | 'W' << 8 | 'R')
#define BYTES(frames)  ((frames) * st->channels * sizeof (float))
#define FLOATS(frames) ((frames) * st->channels)

struct iswr_state {
    int32_t magic;
    SRC_STATE *state;
    src_callback_t func;
    int channels;
    int error;
    void *cb_data;
    SRC_DATA dat;
};

ISWR_STATE *iswr_callback_new(src_callback_t func, int converter_type,
                              int channels, int *error, void *cb_data)
{
    struct iswr_state *st;

    if (!(st = calloc(1, sizeof (struct iswr_state)))) {
        *error = 1;
        return NULL;
    }
    st->magic = MAGIC;
    st->state = src_new(converter_type, channels, error);
    st->func = func;
    st->channels = channels;
    st->cb_data = cb_data;

    return (ISWR_STATE *)st;
}

ISWR_STATE *iswr_delete(ISWR_STATE *st)
{
    ISWR_STATE *ret;

    ret = src_delete(((struct iswr_state *)st)->state);
    memset(st, 0, sizeof (struct iswr_state));
    free(st);
    return ret;
}

long iswr_callback_read(ISWR_STATE *src_state, double src_ratio,
                    long frames, float *data)
{
    struct iswr_state *st = (struct iswr_state *)src_state;

    // check inputs are sane
    if (src_state == NULL) {
        fprintf(stderr, "iswr_callback_read: src_state is NULL\n");
        exit(5);
    }
    if (st->magic != MAGIC) {
        fprintf(stderr, "iswr_callback_read: magic number not found\n");
        exit(5);
    }
    if (data == NULL) {
        fprintf(stderr, "iswr_callback_read: null given for data\n");
        exit(5);
    }
    if (src_ratio <= 0.0) {
        fprintf(stderr, "iswr_callback_read: src_ratio non positive\n");
        exit(5);
    }
    // short circ
    if (frames == 0)
        return 0;

    long frames_copied = 0;

    SRC_DATA *d = &st->dat;
    d->src_ratio = src_ratio;
    while(frames_copied != frames) {
        d->data_out = data + FLOATS(frames_copied);
        d->output_frames = frames - frames_copied;

        // shuffle remaining input data
        if (d->input_frames_used != d->input_frames) {
            d->data_in += FLOATS(d->input_frames_used);
            d->input_frames -= d->input_frames_used;
        } else {
            // grab some more input data
            if (!d->end_of_input) {
                d->input_frames = st->func(st->cb_data, (float **)&d->data_in);
                //if (!d->input_frames)
                //    d->end_of_input = 1;
            }
        }
        if (src_process(st->state, &st->dat)) {
            st->error = src_error(st->state);
            return 0;
        }
        frames_copied += d->output_frames_gen;
        if (d->output_frames_gen == 0)
            break;
    }
    if (d->end_of_input)
        iswr_reset((ISWR_STATE *)st);
    return frames_copied;
}

int iswr_reset(ISWR_STATE *st)
{
    SRC_DATA *dat = &((struct iswr_state *)st)->dat;
    memset(dat, 0, sizeof *dat);
    return src_reset(((struct iswr_state *)st)->state);
}

int iswr_set_ratio(ISWR_STATE *state, double new_ratio)
{
    ((struct iswr_state *)state)->dat.src_ratio = new_ratio;
    return 0;
}

int iswr_error(ISWR_STATE *state)
{
    return ((struct iswr_state *)state)->error;
}
