// Copyright 2011-2012 Paulo Augusto Peccin. See licence.txt distributed with this file.

package org.javatari.atari.tia.audio;

import org.javatari.general.av.audio.AudioMonitor;
import org.javatari.general.av.audio.AudioSignal;
import org.javatari.general.board.ClockDriven;
import org.javatari.parameters.Parameters;


public abstract class AudioGenerator implements AudioSignal, ClockDriven {

	@Override
	public void connectMonitor(AudioMonitor monitor) {
		this.monitor = monitor;
	}

	@Override
	public void clockPulse() {
		if (frameSamples < samplesPerFrame) generateNextSamples(1);
		if (generatedSamples >= SEND_CHUNK) sendGeneratedSamples();
	}

	public AudioMonitor monitor() {
		return monitor;
	}

	public ChannelStream channel0() {
		return channel0;
	}

	public ChannelStream channel1() {
		return channel1;
	}

	public void fps(double fps) {
		// Normal amount is 2 sample per scanline = 31440, 524 for NTSC(60Hz) and 624 for PAL(50hz)
		// Calculate total samples per frame based on fps
		samplesPerFrame = (int) Math.round(SAMPLE_RATE / fps);
		if (samplesPerFrame > MAX_SAMPLES) samplesPerFrame = MAX_SAMPLES;
	}

	public void signalOff() {
		generatedSamples = 0;
		frameSamples = 0;
		if (monitor != null) monitor.nextSamples(null, 0);
	}

	public void finishFrame() {
		int missingSamples = samplesPerFrame - frameSamples;
		if (missingSamples > 0) generateNextSamples(missingSamples);
			
		int available = sendGeneratedSamples();

		// Check available samples on monitor to prevent starvation. Send additional samples if needed
		if (available >= 0 && available < MIN_MONITOR_BUFFER_CHUNKS * SEND_CHUNK) {
			int add = MIN_MONITOR_BUFFER_CHUNKS * SEND_CHUNK - available + SEND_CHUNK / MONITOR_BUFFER_CHUNKS_ADD_FACTOR;
			if (add > MAX_SAMPLES) add = MAX_SAMPLES;
			generateNextSamples(add);
			sendGeneratedSamples();
			// System.out.println("Available in Monitor: " + available + ", add: " + add);
		}
		
		frameSamples = 0;
		
		// Synch with audio monitor as needed
		if (SYNC_WITH_AUDIO_MONITOR && monitor != null) monitor.synchOutput();
	}

	private int sendGeneratedSamples() {
		int available = -1;
		if (monitor != null)
			available = monitor.nextSamples(samples, generatedSamples);
		generatedSamples = 0;
		return available;
	}

	protected abstract void generateNextSamples(int quant);

	
	protected final ChannelStream channel0 = new ChannelStream(); 
	protected final ChannelStream channel1 = new ChannelStream(); 

	protected final byte[] samples = new byte[MAX_SAMPLES];
	protected int generatedSamples = 0;
	protected int frameSamples = 0;
	private int samplesPerFrame = 0;

	private AudioMonitor monitor;

	
	private static final int SAMPLE_RATE = Parameters.TIA_AUDIO_SAMPLE_RATE;
	private static final int SEND_CHUNK = Parameters.TIA_AUDIO_SEND_CHUNK;
	private static final int MIN_MONITOR_BUFFER_CHUNKS = Parameters.TIA_AUDIO_MIN_MONITOR_BUFFER_CHUNKS;
	private static final int MONITOR_BUFFER_CHUNKS_ADD_FACTOR = Parameters.TIA_AUDIO_MONITOR_BUFFER_CHUNKS_ADD_FACTOR;
	private static final int MAX_SAMPLES = 4 * 1024;

	private static final boolean SYNC_WITH_AUDIO_MONITOR = Parameters.TIA_SYNC_WITH_AUDIO_MONITOR;

}
