package zero1hd.rhythmbullet.audio;

import java.io.IOException;

import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;

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;

public class Mp3AudioData implements MusicDataPack {
	private int readWindowSize = 1024;
	
	private int currentReadWindowIndex;
	private Music playbackMusic;
	private int readIndex;
	
	private int sampleRate;
	private long sampleCount;
	private float durationInSeconds;
	private byte channels;
	Bitstream bitstream;
	MP3Decoder decoder;
	OutputBuffer sampleBuffer;
	private byte[] currentByteSet;
	private byte[] workset;
	private int indexHead = -1;
	
	public Mp3AudioData(FileHandle audioFile) {
		try {
			MP3File mp3File = new MP3File(audioFile.file());
			sampleCount = MathUtils.round((float) (mp3File.getAudioHeader().getSampleRateAsNumber()*mp3File.getMP3AudioHeader().getPreciseTrackLength())); 
			durationInSeconds = mp3File.getMP3AudioHeader().getNumberOfFrames()/readWindowSize;
			sampleRate = mp3File.getMP3AudioHeader().getSampleRateAsNumber();
		} catch (IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
			e.printStackTrace();
		}
		
		bitstream = new Bitstream(audioFile.read());
		decoder = new MP3Decoder();
		
		try {
			Header header = bitstream.readFrame();
			if (header == null) throw new GdxRuntimeException("Empty MP3");
			channels = (byte) (header.mode() == Header.SINGLE_CHANNEL ? 1 : 2);
			sampleBuffer = new OutputBuffer(channels, false);
			decoder.setOutputBuffer(sampleBuffer);
			workset = new byte[channels*2];
		} catch (BitstreamException e) {
			throw new GdxRuntimeException("error while preloading mp3", e);
		}
		
		playbackMusic = Gdx.audio.newMusic(audioFile);
		
		reset();
	}

	@Override
	public void playbackIndexUpdate() {
		readIndex = (int) (playbackMusic.getPosition() * sampleRate / readWindowSize);
	}

	@Override
	public int getPlaybackIndexPosition() {
		return readIndex;
	}

	@Override
	public void reset() {
		playbackMusic.stop();
	}

	@Override
	public int getReadWindowSize() {
		return readWindowSize;
	}

	@Override
	public Music getPlaybackMusic() {
		return playbackMusic;
	}

	@Override
	public long getSampleCount() {
		return sampleCount;
	}

	@Override
	public void dispose() {
		reset();
		playbackMusic.dispose();
		try {
			bitstream.close();
		} catch (BitstreamException e) {
			e.printStackTrace();
		}
	}

	@Override
	public float getDuration() {
		return durationInSeconds;
	}

	@Override
	public int readSamples(float[] samples) {
		int framesRead = 0;
		for (int sid = 0; sid < samples.length; sid++) {
			for (int wsid = 0; wsid < workset.length; wsid++) {
				workset[wsid] = nextByte();
			}
			if (currentByteSet != null) {
				samples[sid] += (workset[1] << 8) + (workset[0] & 0x00ff);
				if (channels > 1) {
					samples[sid] += (workset[3] << 8) + (workset[2] & 0x00ff);
					samples[sid] /= 2;
				}
				framesRead ++;
				
				samples[sid] /= Short.MAX_VALUE+1;
			}
		}
		if (framesRead != 0) {
			currentReadWindowIndex++;
		}
		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() {
		int bytesRead = 0;
		try {
			Header header = bitstream.readFrame();
			if (header != null) {
				
				try {
					decoder.decodeFrame(header, bitstream);
				} catch (ArrayIndexOutOfBoundsException | DecoderException e) {
					System.out.println(e);
				}
				
				bitstream.closeFrame();
				bytesRead = sampleBuffer.reset();
				
				currentByteSet = sampleBuffer.getBuffer();
			} else {
				currentByteSet = null;
			}
		} catch (BitstreamException e1) {
			e1.printStackTrace();
		}
		return bytesRead;
	}
	
	@Override
	public float getSampleRate() {
		return sampleRate;
	}

	@Override
	public int getCurrentReadWindowIndex() {
		return currentReadWindowIndex;
	}
}
