package zero1hd.rhythmbullet.desktop.audio.processor;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.DecoderException;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.MP3Decoder;
import javazoom.jl.decoder.OutputBuffer;
import zero1hd.rhythmbullet.audio.MinimalAudioHeader;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;


public class MP3AudioProcessor implements AudioProcessor {
	private boolean stereo;
	private int sampleRate;
	private long sampleFrames;
	private FileHandle fileHandle;
	private byte[] currentByteSet;
	private byte[] workset;
	Bitstream bitstream;
	MP3Decoder decoder;
	OutputBuffer sampleBuffer;
	private int indexHead = -1;
	
	
	public MP3AudioProcessor(FileHandle fileHandle, MinimalAudioHeader minimalAudioHeader) {
		this.fileHandle = fileHandle;
		
		bitstream = new Bitstream(fileHandle.read());
		
		
		stereo = minimalAudioHeader.getChannelCount() == 1 ? false : true;
		sampleRate = minimalAudioHeader.getSampleRate();
		sampleFrames = minimalAudioHeader.estimateSampleFrames();
		
		decoder = new MP3Decoder();
		sampleBuffer = new OutputBuffer(stereo ? 2 : 1, false);
		decoder.setOutputBuffer(sampleBuffer);
		workset = new byte[(stereo ? 2 : 1)*2];
	}

	@Override
	public boolean isStereo() {
		return stereo;
	}


	@Override
	public int getSampleRate() {
		return sampleRate;
	}
	
	@Override
	public int readSamples(short[] pcm) {
		int samplesRead = 0;
		for (int sid = 0; sid < pcm.length; sid++) {
			for (int wsid = 0; wsid < workset.length; wsid++) {
				workset[wsid] = nextByte();
			}
			if (currentByteSet != null) {
				pcm[sid] += (workset[1] << 8) + (workset[0] & 0x00ff);
				if (stereo) {
					short altChan = (short) ((workset[3] << 8) + (workset[2] & 0x00ff));
					sid++;
					pcm[sid] = altChan;
				}
				samplesRead ++;
			}
		}
		return samplesRead;
	}
	
	@Override
	public int readFrames(float[] pcm) {
		int framesRead = 0;
		for (int sid = 0; sid < pcm.length; sid++) {
			for (int wsid = 0; wsid < workset.length; wsid++) {
				workset[wsid] = nextByte();
			}
			if (currentByteSet != null) {
				pcm[sid] += (workset[1] << 8) + (workset[0] & 0x00ff);
				if (stereo) {
					short altChan = (short) ((workset[3] << 8) + (workset[2] & 0x00ff));
					pcm[sid] = altChan > pcm[sid] ? altChan : pcm[sid];
				}
				framesRead++;
				pcm[sid] /= Short.MAX_VALUE+1;
			}
		}
		return framesRead;
	}
	
	public byte nextByte() {
		indexHead++;
		if (currentByteSet == null || indexHead >= currentByteSet.length) {
			loadNextBuffer();
			if (currentByteSet == null) {
				return 0;
			}
			indexHead = 0;
		}
		
		return currentByteSet[indexHead];
	}
	
	public int loadNextBuffer() {
		if (bitstream != null) {
			int bytesRead = 0;
			try {
				Header header = bitstream.readFrame();
				if (header != null) {
					
					try {
						decoder.decodeFrame(header, bitstream);
					} catch (ArrayIndexOutOfBoundsException ae) {
						Gdx.app.debug("Mp3Manager", "Last buffer reached since array was out of bounds.");
					} catch (DecoderException de) {
						Gdx.app.error("MP3 Decoder Error", de.toString());
					}
					bitstream.closeFrame();
					bytesRead = sampleBuffer.reset();
					currentByteSet = sampleBuffer.getBuffer();
				} else {
					currentByteSet = null;
				}
			} catch (BitstreamException be) {
				be.printStackTrace();
			}
			return bytesRead;
		} else {
			return 0;
		}
	}
	
	@Override
	public FileHandle getMusicFileHandle() {
		return fileHandle;
	}
	
	@Override
	public long getSampleFrames() {
		return sampleFrames;
	}
	
	@Override
	public void dispose() {
		Gdx.app.debug("MP3Manager", "Disposing...");
		try {
			bitstream.close();
			bitstream = null;
		} catch (BitstreamException e) {
			e.printStackTrace();
		}
	}
}
