/*
 *  Copyright (C) 2006 Stas Sergeev <stsp@users.sourceforge.net>
 *
 * The below copyright strings have to be distributed unchanged together
 * with this file. This prefix can not be modified or separated.
 */

/*
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Purpose:
 *   DSP I/O layer, DMA and DAC handling. Also MIDI sometimes.
 *   Currently used by SB16, but may be used with anything, e.g. GUS.
 *
 * Author: Stas Sergeev.
 *
 */

#include "emu.h"
#include "timers.h"
#include "sound/sound.h"
#include "sound/midi.h"
#include "adlib.h"
#include "dmanew.h"
#include "sb16.h"
#include "dspio.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <semaphore.h>
#include <pthread.h>

static sem_t syn_sem;
static pthread_t syn_thr;

#define DAC_BASE_FREQ 5625
#define PCM_MAX_BUF 512

struct dspio_dma {
    int running;
    int num;
    int broken_hdma;
    int rate;
    int is16bit;
    int stereo;
    int samp_signed;
    int input;
    int silence;
    int dsp_fifo_enabled;
    hitimer_t time_cur;
};

struct dspio_state {
    double input_time_cur, midi_time_cur;
    int dma_strm, dac_strm;
    int input_running, output_running, dac_running, speaker;
    int i_handle, i_started;
#define DSP_FIFO_SIZE 64
    struct rng_s fifo_in;
    struct rng_s fifo_out;
#define DSP_OUT_FIFO_TRIGGER 32
#define DSP_IN_FIFO_TRIGGER 32
#define MIDI_FIFO_SIZE 32
    struct rng_s midi_fifo_in;
    struct rng_s midi_fifo_out;
    struct dspio_dma dma;
};

#define DSPIO ((struct dspio_state *)dspio)

static void *synth_thread(void *arg);

static void dma_get_silence(int is_signed, int is16bit, void *ptr)
{
    if (is16bit) {
	Bit16u *tmp16 = ptr;
	*tmp16 = is_signed ? 0 : 0x8000;
    } else {
	Bit8u *tmp8 = ptr;
	*tmp8 = is_signed ? 0 : 0x80;
    }
}

void dspio_toggle_speaker(void *dspio, int on)
{
    if (!on && DSPIO->speaker) {
	if (DSPIO->dac_running) {
	    pcm_flush(DSPIO->dac_strm);
	    DSPIO->dac_running = 0;
	}
	/* we don't flush PCM stream here because DSP uses PCM layer
	 * for timing, and timing is needed even when speaker is disabled... */
    }
    DSPIO->speaker = on;
}

int dspio_get_speaker_state(void *dspio)
{
    return DSPIO->speaker;
}

void dspio_write_midi(void *dspio, Bit8u value)
{
    rng_put(&DSPIO->midi_fifo_out, &value);

    run_new_sb();
}

static int dspio_out_fifo_len(struct dspio_dma *dma)
{
    return dma->dsp_fifo_enabled ? DSP_OUT_FIFO_TRIGGER : 2;
}

static int dspio_in_fifo_len(struct dspio_dma *dma)
{
    return dma->dsp_fifo_enabled ? DSP_IN_FIFO_TRIGGER : 2;
}

static int dspio_output_fifo_filled(struct dspio_state *state)
{
    return rng_count(&state->fifo_out) >= dspio_out_fifo_len(&state->dma);
}

static int dspio_input_fifo_filled(struct dspio_state *state)
{
    return rng_count(&state->fifo_in) >= dspio_in_fifo_len(&state->dma);
}

static int dspio_input_fifo_empty(struct dspio_state *state)
{
    return !rng_count(&state->fifo_in);
}

static int dspio_midi_output_empty(struct dspio_state *state)
{
    return !rng_count(&state->midi_fifo_out);
}

static Bit8u dspio_get_midi_data(struct dspio_state *state)
{
    Bit8u val;
    int ret = rng_get(&state->midi_fifo_out, &val);
    assert(ret == 1);
    return val;
}

Bit8u dspio_get_midi_in_byte(void *dspio)
{
    Bit8u val;
    int ret = rng_get(&DSPIO->midi_fifo_in, &val);
    assert(ret == 1);
    return val;
}

void dspio_put_midi_in_byte(void *dspio, Bit8u val)
{
    rng_put_const(&DSPIO->midi_fifo_in, val);
}

int dspio_get_midi_in_fillup(void *dspio)
{
    return rng_count(&DSPIO->midi_fifo_in);
}

void dspio_clear_midi_in_fifo(void *dspio)
{
    rng_clear(&DSPIO->midi_fifo_in);
}

static int dspio_get_dma_data(struct dspio_state *state, void *ptr, int is16bit)
{
    if (sb_get_dma_data(ptr, is16bit))
	return 1;
    if (rng_count(&state->fifo_in)) {
	if (is16bit) {
	    rng_get(&state->fifo_in, ptr);
	} else {
	    Bit16u tmp;
	    rng_get(&state->fifo_in, &tmp);
	    *(Bit8u *) ptr = tmp;
	}
	return 1;
    }
    error("SB: input fifo empty\n");
    return 0;
}

static void dspio_put_dma_data(struct dspio_state *state, void *ptr, int is16bit)
{
    if (dspio_output_fifo_filled(state)) {
	error("SB: output fifo overflow\n");
	return;
    }
    if (is16bit) {
	rng_put(&state->fifo_out, ptr);
    } else {
	Bit16u tmp = *(Bit8u *) ptr;
	rng_put(&state->fifo_out, &tmp);
    }
}

static int dspio_get_output_sample(struct dspio_state *state, void *ptr,
	int is16bit)
{
    if (rng_count(&state->fifo_out)) {
	if (is16bit) {
	    rng_get(&state->fifo_out, ptr);
	} else {
	    Bit16u tmp;
	    rng_get(&state->fifo_out, &tmp);
	    *(Bit8u *) ptr = tmp;
	}
	return 1;
    }
    return 0;
}

static int dspio_put_input_sample(struct dspio_state *state, void *ptr,
	int is16bit)
{
    int ret;
    if (!sb_input_enabled())
	return 0;
    if (dspio_input_fifo_filled(state)) {
	S_printf("SB: ERROR: input fifo overflow\n");
	return 0;
    }
    if (is16bit) {
	ret = rng_put(&state->fifo_in, ptr);
    } else {
	Bit16u tmp = *(Bit8u *) ptr;
	ret = rng_put(&state->fifo_in, &tmp);
    }
    return ret;
}

void dspio_clear_fifos(void *dspio)
{
    rng_clear(&DSPIO->fifo_in);
    rng_clear(&DSPIO->fifo_out);
    DSPIO->dma.dsp_fifo_enabled = 1;
}

static void dspio_i_start(void *arg)
{
    struct dspio_state *state = arg;
    state->i_started = 1;
}

static void dspio_i_stop(void *arg)
{
    struct dspio_state *state = arg;
    state->i_started = 0;
}

void *dspio_init(void)
{
    struct dspio_state *state;
    struct pcm_player player = {};
    state = malloc(sizeof(struct dspio_state));
    if (!state)
	return NULL;
    memset(&state->dma, 0, sizeof(struct dspio_dma));
    state->input_running =
	state->output_running = state->dac_running = state->speaker = 0;
    state->dma.dsp_fifo_enabled = 1;

    player.name = "SB REC";
    player.start = dspio_i_start;
    player.stop = dspio_i_stop;
    player.arg = state;
    player.id = PCM_ID_R;
    state->i_handle = pcm_register_player(player);
    pcm_init();
    state->dac_strm = pcm_allocate_stream(1, "SB DAC", PCM_ID_P);
    pcm_set_flag(state->dac_strm, PCM_FLAG_RAW);
    state->dma_strm = pcm_allocate_stream(2, "SB DMA", PCM_ID_P);
    pcm_set_flag(state->dma_strm, PCM_FLAG_SLTS);

    rng_init(&state->fifo_in, DSP_FIFO_SIZE, 2);
    rng_init(&state->fifo_out, DSP_FIFO_SIZE, 2);
    rng_init(&state->midi_fifo_in, MIDI_FIFO_SIZE, 1);
    rng_init(&state->midi_fifo_out, MIDI_FIFO_SIZE, 1);

    adlib_init();
    midi_init();

    sem_init(&syn_sem, 0, 0);
    pthread_create(&syn_thr, NULL, synth_thread, NULL);

    return state;
}

void dspio_reset(void *dspio)
{
    pcm_reset();
    midi_reset();
}

void dspio_done(void *dspio)
{
    pthread_cancel(syn_thr);
    pthread_join(syn_thr, NULL);
    sem_destroy(&syn_sem);

    pcm_done();
    midi_done();

    rng_destroy(&DSPIO->fifo_in);
    rng_destroy(&DSPIO->fifo_out);
    rng_destroy(&DSPIO->midi_fifo_in);
    rng_destroy(&DSPIO->midi_fifo_out);

    free(dspio);
}

void dspio_stop_midi(void *dspio)
{
    DSPIO->midi_time_cur = GETusTIME(0);
    midi_stop();
}

Bit32u dspio_get_midi_in_time(void *dspio)
{
    Bit32u delta = GETusTIME(0) - DSPIO->midi_time_cur;
    S_printf("SB: midi clock, delta=%i\n", delta);
    return delta;
}

static void dspio_start_output(struct dspio_state *state)
{
    if (state->output_running)
	return;
    S_printf("SB: starting output\n");
    pcm_prepare_stream(state->dma_strm);
    state->output_running = 1;
}

static void dspio_stop_output(struct dspio_state *state)
{
    if (!state->output_running)
	return;
    S_printf("SB: stopping output\n");
    pcm_flush(state->dma_strm);
    state->output_running = 0;
}

static void dspio_start_input(struct dspio_state *state)
{
    if (state->input_running)
	return;
    S_printf("SB: starting input\n");
    pcm_reset_player(state->i_handle);
    state->input_time_cur = GETusTIME(0);
    state->input_running = 1;
}

static void dspio_stop_input(struct dspio_state *state)
{
    if (!state->input_running)
	return;
    S_printf("SB: stopping input\n");
    state->input_running = 0;
}

static int do_run_dma(struct dspio_state *state)
{
    Bit8u dma_buf[2];
    struct dspio_dma *dma = &state->dma;

    dma_get_silence(dma->samp_signed, dma->is16bit, dma_buf);
    if (!dma->silence) {
	if (dma->input)
	    dspio_get_dma_data(state, dma_buf, dma->is16bit);
	if (dma_pulse_DRQ(dma->num, dma_buf) != DMA_DACK) {
	    S_printf("SB: DMA %i doesn't DACK!\n", dma->num);
	    return 0;
	}
	if (dma->broken_hdma) {
	    if (dma_pulse_DRQ(dma->num, dma_buf + 1) != DMA_DACK) {
		S_printf("SB: DMA (broken) %i doesn't DACK!\n", dma->num);
		return 0;
	    }
	}
    }
    if (!dma->input)
	dspio_put_dma_data(state, dma_buf, dma->is16bit);
    return 1;
}

static int dspio_run_dma(struct dspio_state *state)
{
#define DMA_TIMEOUT_US 100000
    int ret;
    struct dspio_dma *dma = &state->dma;
    hitimer_t now = GETusTIME(0);
    sb_dma_processing();	// notify that DMA busy
    ret = do_run_dma(state);
    if (ret) {
	sb_handle_dma();
	dma->time_cur = now;
    } else if (now - dma->time_cur > DMA_TIMEOUT_US) {
	S_printf("SB: Warning: DMA busy for too long, releasing\n");
//	error("SB: DMA timeout\n");
	sb_handle_dma_timeout();
    }
    return ret;
}

static void get_dma_params(struct dspio_dma *dma)
{
    int dma_16bit = sb_dma_16bit();
    int dma_num = dma_16bit ? sb_get_hdma_num() : sb_get_dma_num();
    int broken_hdma = (dma_16bit && dma_num == -1);
    if (broken_hdma) {
	dma_num = sb_get_dma_num();
	S_printf("SB: Warning: HDMA is broken, using 8-bit DMA channel %i\n",
	     dma_num);
    }

    dma->num = dma_num;
    dma->is16bit = dma_16bit;
    dma->broken_hdma = broken_hdma;
    dma->rate = sb_get_dma_sampling_rate();
    dma->stereo = sb_dma_samp_stereo();
    dma->samp_signed = sb_dma_samp_signed();
    dma->input = sb_dma_input();
    dma->silence = sb_dma_silence();
    dma->dsp_fifo_enabled = sb_fifo_enabled();
}

static int dspio_fill_output(struct dspio_state *state)
{
    int dma_cnt = 0;
    while (state->dma.running && !dspio_output_fifo_filled(state)) {
	if (!dspio_run_dma(state))
	    break;
	dma_cnt++;
    }
#if 0
    if (!state->output_running && !sb_output_fifo_empty())
#else
    /* incomplete fifo needs a timeout, so lets not deal with it at all.
     * Instead, deal with the filled fifo only. */
    if (dspio_output_fifo_filled(state))
#endif
	dspio_start_output(state);
    return dma_cnt;
}

static int dspio_drain_input(struct dspio_state *state)
{
    int dma_cnt = 0;
    while (state->dma.running && !dspio_input_fifo_empty(state)) {
	if (!dspio_run_dma(state))
	    break;
	dma_cnt++;
    }
    return dma_cnt;
}

void dspio_start_dma(void *dspio)
{
    int dma_cnt = 0;
    DSPIO->dma.running = 1;
    DSPIO->dma.time_cur = GETusTIME(0);
    get_dma_params(&DSPIO->dma);

    if (DSPIO->dma.input) {
	dspio_start_input(DSPIO);
    } else {
	dma_cnt = dspio_fill_output(DSPIO);
	if (DSPIO->dma.running && dspio_output_fifo_filled(DSPIO))
	    S_printf("SB: Output filled, processed %i DMA cycles\n",
		     dma_cnt);
	else
	    S_printf("SB: Output fillup incomplete (%i %i %i)\n",
		     DSPIO->dma.running, DSPIO->output_running, dma_cnt);
    }
}

void dspio_stop_dma(void *dspio)
{
    dspio_stop_input(DSPIO);
    DSPIO->dma.running = 0;
}

static int calc_nframes(struct dspio_state *state,
	hitimer_t time_beg, hitimer_t time_dst)
{
    int nfr;
    if (state->dma.rate) {
	nfr = (time_dst - time_beg) / pcm_frame_period_us(state->dma.rate);
	if (nfr < 0)	// happens because of get_stream_time() hack
	    nfr = 0;
	if (nfr > PCM_MAX_BUF)
	    nfr = PCM_MAX_BUF;
    } else {
	nfr = 1;
    }
    return nfr;
}

static void dspio_process_dma(struct dspio_state *state)
{
    int dma_cnt, nfr, in_fifo_cnt, out_fifo_cnt, i, j, tlocked;
    unsigned long long time_dst;
    double output_time_cur;
    sndbuf_t buf[PCM_MAX_BUF][SNDBUF_CHANS];
    static int warned;

    dma_cnt = in_fifo_cnt = out_fifo_cnt = 0;

    if (state->dma.running) {
	state->dma.stereo = sb_dma_samp_stereo();
	state->dma.rate = sb_get_dma_sampling_rate();
	state->dma.samp_signed = sb_dma_samp_signed();
	state->dma.dsp_fifo_enabled = sb_fifo_enabled();
	dma_cnt += state->dma.input ? dspio_drain_input(state) :
	    dspio_fill_output(state);
    }

    if (!state->output_running && !state->input_running)
	return;

    time_dst = GETusTIME(0);
    if (state->output_running) {
	output_time_cur = pcm_time_lock(state->dma_strm);
	tlocked = 1;
	nfr = calc_nframes(state, output_time_cur, time_dst);
    } else {
	nfr = 0;
	tlocked = 0;
    }
    for (i = 0; i < nfr; i++) {
	for (j = 0; j < state->dma.stereo + 1; j++) {
	    if (state->dma.running && !dspio_output_fifo_filled(state)) {
		if (!dspio_run_dma(state))
		    break;
		dma_cnt++;
	    }
	    if (!dspio_get_output_sample(state, &buf[i][j],
		    state->dma.is16bit))
		break;
	    /* if speaker disabled, overwrite DMA data with silence */
	    if (!state->speaker)
		dma_get_silence(state->dma.samp_signed,
			state->dma.is16bit, &buf[i][j]);
	}
	if (j != state->dma.stereo + 1)
	    break;
	out_fifo_cnt++;
    }
    if (out_fifo_cnt && state->dma.rate) {
	pcm_write_interleaved(buf, out_fifo_cnt, state->dma.rate,
			  pcm_get_format(state->dma.is16bit,
					 state->dma.samp_signed),
			  state->dma.stereo + 1, state->dma_strm);
	output_time_cur = pcm_get_stream_time(state->dma_strm);
	if (state->dma.running && output_time_cur > time_dst - 1) {
	    pcm_clear_flag(state->dma_strm, PCM_FLAG_POST);
	    warned = 0;
	}
    }
    if (out_fifo_cnt < nfr) {
	/* not enough samples, see why */
	if (!sb_dma_active()) {
	    dspio_stop_output(state);
	} else {
	    if (nfr && !warned) {
		S_printf("SB: Output FIFO exhausted while DMA is still active (ol=%f)\n",
			 time_dst - output_time_cur);
		warned = 1;
	    }
	    if (state->dma.running)
		S_printf("SB: Output FIFO exhausted while DMA is running (no DACK?)\n");
	    /* DMA is active but currently not running and the FIFO is
	     * already exhausted. Normally we should flush the channel
	     * and stop the output timing.
	     * HACK: try to not flush the channel for as long as possible
	     * in a hope the PCM buffers are large enough to hold till
	     * the DMA is restarted. */
	    pcm_set_flag(state->dma_strm, PCM_FLAG_POST);
	    /* awake dosemu */
	    reset_idle(0);
	}
    }
    if (tlocked)
	pcm_time_unlock(state->dma_strm);

    /* TODO: sync also input time with PCM? */
    if (state->input_running)
	nfr = calc_nframes(state, state->input_time_cur, time_dst);
    else
	nfr = 0;
    if (nfr && state->i_started) {
	struct player_params params;
	params.rate = state->dma.rate;
	params.channels = state->dma.stereo + 1;
	params.format = pcm_get_format(state->dma.is16bit,
		state->dma.samp_signed);
	params.handle = state->i_handle;
	nfr = pcm_data_get_interleaved(buf, nfr, &params);
    }
    /* the input data may be overwritten with silence below.
     * We still need to get it from PCM buffers to stay in sync. */
    if (state->speaker || !state->i_started) {
	for (i = 0; i < nfr; i++) {
	    for (j = 0; j < state->dma.stereo + 1; j++)
		dma_get_silence(state->dma.samp_signed,
			state->dma.is16bit, &buf[i][j]);
	}
    }
    for (i = 0; i < nfr; i++) {
	for (j = 0; j < state->dma.stereo + 1; j++) {
	    if (sb_input_enabled()) {
		if (!dspio_put_input_sample(state, &buf[i][j],
			state->dma.is16bit))
		    break;
	    }
	}
	if (j == state->dma.stereo + 1)
	    in_fifo_cnt++;
	for (j = 0; j < state->dma.stereo + 1; j++) {
	    if (state->dma.running) {
		if (!dspio_run_dma(state))
		    break;
		dma_cnt++;
	    }
	}
	if (j != state->dma.stereo + 1)
	    break;
    }
    if (in_fifo_cnt) {
	if (state->dma.rate) {
	    state->input_time_cur += in_fifo_cnt *
		    pcm_frame_period_us(state->dma.rate);
	} else {
	    state->input_time_cur = time_dst;
	}
    }

    if (debug_level('S') >= 7 && (in_fifo_cnt || out_fifo_cnt || dma_cnt))
	S_printf("SB: Processed %i %i FIFO, %i DMA, or=%i dr=%i\n",
	     in_fifo_cnt, out_fifo_cnt, dma_cnt, state->output_running, state->dma.running);
}

static void dspio_process_midi(struct dspio_state *state)
{
    Bit8u data;
    /* no timing for now */
    while (!dspio_midi_output_empty(state)) {
	data = dspio_get_midi_data(state);
	midi_write(data);
    }

    while (midi_get_data_byte(&data)) {
	dspio_put_midi_in_byte(state, data);
	sb_handle_midi_data();
    }
}

static void *synth_thread(void *arg)
{
    while (1) {
	sem_wait(&syn_sem);
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	adlib_timer();
	midi_timer();
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    }
    return NULL;
}

void dspio_run_synth(void)
{
    sem_post(&syn_sem);
}

void dspio_timer(void *dspio)
{
    dspio_process_dma(DSPIO);
    dspio_process_midi(DSPIO);
}

void dspio_write_dac(void *dspio, Bit8u samp)
{
    if (DSPIO->speaker) {
	sndbuf_t buf[1][SNDBUF_CHANS];
	buf[0][0] = samp;
	DSPIO->dac_running = 1;
	pcm_write_interleaved(buf, 1, DAC_BASE_FREQ, PCM_FORMAT_U8,
			  1, DSPIO->dac_strm);
    }
}
