/*
 *  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/sndpcm.h"
#include "sound/midi.h"
#include "adlib.h"
#include "dmanew.h"
#include "sb16.h"
#include "dspio.h"
#include <string.h>
#include <stdlib.h>

#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;
    hitimer_t time_cur;
};

struct dspio_state {
    double input_time_cur, output_time_cur, midi_time_cur;
    int dma_strm, dac_strm;
    int input_running, output_running, dac_running, speaker;
    struct dspio_dma dma;
};

#define DSPIO ((struct dspio_state *)dspio)

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->output_running) {
	    pcm_flush(DSPIO->dma_strm);
	}
	if (DSPIO->dac_running) {
	    pcm_flush(DSPIO->dac_strm);
	    DSPIO->dac_running = 0;
	}
    }
    DSPIO->speaker = on;
}

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

void *dspio_init(void)
{
    struct dspio_state *dspio;
    dspio = malloc(sizeof(struct dspio_state));
    if (!dspio)
	return NULL;
    pcm_init();
    dspio->dac_strm = pcm_allocate_stream(1, "SB DAC");
    pcm_set_flag(dspio->dac_strm, PCM_FLAG_RAW);
    dspio->dma_strm = pcm_allocate_stream(2, "SB DMA");

    adlib_init();
    midi_init();

    return dspio;
}

void dspio_reset(void *dspio)
{
    memset(&DSPIO->dma, 0, sizeof(struct dspio_dma));
    DSPIO->input_running =
	DSPIO->output_running = DSPIO->dac_running = DSPIO->speaker = 0;

    pcm_reset();
    midi_reset();
}

void dspio_done(void *dspio)
{
    pcm_done();
    midi_done();
    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");
    /* We would need real time here, but the HACK is to use stream
     * timestamp instead.
     * That compensates the hack of dspio_process_dma() */
    state->output_time_cur = pcm_calc_tstamp(state->dma.rate, 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");
    if (state->speaker)
	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");
    /* TODO */
    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");
    /* TODO */
    state->input_running = 0;
}

static int do_run_dma(struct dspio_dma *dma)
{
    Bit8u dma_buf[2];

    dma_get_silence(dma->samp_signed, dma->is16bit, dma_buf);
    if (dma->input)
	sb_get_dma_data(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)
	sb_put_dma_data(dma_buf, dma->is16bit);
    return 1;
}

static int dspio_run_dma(struct dspio_dma *dma)
{
#define DMA_TIMEOUT_US 100000
    int ret;
    hitimer_t now = GETusTIME(0);
    sb_dma_processing();	// notify that DMA busy
    ret = do_run_dma(dma);
    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();
}

static int dspio_fill_output(struct dspio_state *state)
{
    int dma_cnt = 0;
    while (state->dma.running && !sb_output_fifo_filled()) {
	if (!dspio_run_dma(&state->dma))
	    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 (sb_output_fifo_filled())
#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 && !sb_input_fifo_empty()) {
	if (!dspio_run_dma(&state->dma))
	    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 && sb_output_fifo_filled())
	    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 void dspio_process_dma(struct dspio_state *state)
{
    int dma_cnt, nfr, in_fifo_cnt, out_fifo_cnt, i, j;
    unsigned long long time_dst;
    sndbuf_t buf[PCM_MAX_BUF][SNDBUF_CHANS];

    dma_cnt = in_fifo_cnt = out_fifo_cnt = 0;

    time_dst = GETusTIME(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();
    }

    if (state->output_running) {
	if (state->dma.rate) {
	     nfr = (time_dst - state->output_time_cur) /
		    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;
	}
    } else {
	nfr = 0;
    }
    for (i = 0; i < nfr; i++) {
	for (j = 0; j < state->dma.stereo + 1; j++) {
	    if (state->dma.running && !sb_output_fifo_filled()) {
		if (!dspio_run_dma(&state->dma))
		    break;
		dma_cnt++;
	    }
	    if (!sb_get_output_sample(&buf[i][j], state->dma.is16bit))
		break;
	}
	if (j != state->dma.stereo + 1)
	    break;
	out_fifo_cnt++;
    }
    if (out_fifo_cnt) {
	if (state->dma.rate) {
	    if (state->speaker && !state->dma.silence) {
		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);
	    }
	    state->output_time_cur += out_fifo_cnt *
		    pcm_frame_period_us(state->dma.rate);
	} else {
	    state->output_time_cur = time_dst;
	}
    }
    if (state->dma.running && state->output_time_cur > time_dst - 1)
	pcm_set_mode(state->dma_strm, PCM_MODE_NORMAL);
    if (out_fifo_cnt < nfr) {
	/* not enough samples, see why */
	if (!sb_dma_active()) {
	    dspio_stop_output(state);
	} else {
	    if (debug_level('S') > 7)
		S_printf("SB: Output FIFO exhausted while DMA is still active (ol=%f)\n",
			 time_dst - state->output_time_cur);
	    if (state->dma.running)
		S_printf("SB: Output FIFO exhausted while DMA is running\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_mode(state->dma_strm, PCM_MODE_POST);
	    /* awake dosemu */
	    reset_idle(0);
	}
    }

    if (state->input_running) {
	if (state->dma.rate) {
	    nfr = (time_dst - state->input_time_cur) /
		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;
	}
    } else {
	nfr = 0;
    }
    for (i = 0; i < nfr; i++) {
	for (j = 0; j < state->dma.stereo + 1; j++) {
	    if (sb_input_enabled()) {
		//if (!state->speaker)  /* TODO: input */
		dma_get_silence(state->dma.samp_signed,
			state->dma.is16bit, &buf[i][j]);
		if (!sb_put_input_sample(&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->dma))
		    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 (state->dma.running)
	dma_cnt += state->dma.input ? dspio_drain_input(state) :
	    dspio_fill_output(state);

    if (in_fifo_cnt || out_fifo_cnt || dma_cnt)
	S_printf("SB: Processed %i %i FIFO, %i DMA, or=%i dr=%i time=%lli\n",
	     in_fifo_cnt, out_fifo_cnt, dma_cnt, state->output_running, state->dma.running,
	     time_dst);
}

static void dspio_process_midi(void)
{
    Bit8u data;
    /* no timing for now */
    while (!sb_midi_output_empty()) {
	sb_get_midi_data(&data);
	midi_write(data);
    }

    while (midi_get_data_byte(&data)) {
	sb_put_midi_data(data);
    }

    midi_timer();
}

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

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);
    }
}
