package zero1hd.rhythmbullet.audio;

import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener;
import com.badlogic.gdx.files.FileHandle;

/**
 * Manages current games music playback and does this in tandem with the {@link MusicList} by asking to retrieve files and then feeding it to LibGDX.
 * Notifies observers when a new song is loaded.
 * The loading model is like taking a disk and loading it into a player. It doesn't necessarily mean it'll play right away, but its ready and the only track that has a stream opened.
 * @author yunya
 *
 */
public class MusicController extends Observable implements OnCompletionListener, Observer {
	public final class States {
		public final Integer LOADED = new Integer(0), PLAYING = new Integer(1), PAUSED = new Integer(2);
	}
	
	public final States states = new States();
	private MusicList musicList;
	private MinimalAudioHeader musicHeader;
	private volatile Music music;
	private int currentlyPlayingIndex;
	private boolean autoPlay;
	private boolean shuffle;
	private Random rand;
	private Preferences prefs;
	
	public MusicController(MusicList musicList, Preferences prefs) {
		if (prefs == null) throw new NullPointerException("preferences can't be null...");
		if (musicList == null) throw new NullPointerException("music list can't be null...");
		musicList.addObserver(this);
		this.prefs = prefs;
		this.musicList = musicList;
		rand = new Random();
	}
	
	/**
	 * This play method automatically sets the volume.
	 */
	public void play() {
		if (music != null) {
			Gdx.app.debug("MusicController", "Playing from controller.");
			music.play();
			music.setVolume(prefs.getFloat("music vol", 100f)/100f);
			setChanged();
			notifyObservers(states.PLAYING);
		} else {
			Gdx.app.debug("MusicController", "Music isn't loaded!");
			Thread.dumpStack();
		}
	}
	
	/**
	 * Called to pause current song. Does nothing if no song is playing or loaded.
	 */
	public void pause() {
		if (music != null) {
			music.pause();
			setChanged();
			notifyObservers(states.PAUSED);
		}
	}
	
	/**
	 * Loads music based on the index in the {@link MusicList}.
	 * @param index of music to play
	 */
	public void setMusicByIndex(int index) {
		this.currentlyPlayingIndex = index;
		loadMusic();
	}
	
	/**
	 * Loads music using the given file. The given file must be found in the {@link MusicList}.
	 * This function gets the index of the given file within {@link MusicList} and passes that index to {@link #setMusicByIndex(index)}.
	 * @param fileHandle to use.
	 */
	public void setMusicByFileHandle(FileHandle fileHandle) {
		setMusicByIndex(musicList.getIndexOfFileHandle(fileHandle));
	}
	
	/**
	 * Goes to the next track
	 */
	public void skip() {
		currentlyPlayingIndex++;
		if (shuffle) {
			shuffle();
		}
		loadMusic();
	}
	
	/**
	 * Goes to the previous track
	 */
	public void previous() {
		currentlyPlayingIndex--;
		if (shuffle) {
			shuffle();
		}
		loadMusic();
	}

	@Override
	public void onCompletion(Music music) {
		if (autoPlay) {
			if (shuffle) {
				shuffle();
			} else {
				currentlyPlayingIndex++;
			}
			loadMusic();
			play();
		}
	}
	
	/**
	 * Shuffles the controller whether the shuffle boolean is true or false.
	 */
	public void shuffle() {
		Gdx.app.debug("MusicListController", "shuffled.");
		if (musicList.getTotal() == 0) {
			currentlyPlayingIndex = 0;
		} else {
			currentlyPlayingIndex = rand.nextInt(musicList.getTotal());
		}
	}
	
	public void setAutoPlay(boolean autoPlay) {
		this.autoPlay = autoPlay;
	}
	
	public void setShuffle(boolean shuffle) {
		this.shuffle = shuffle;
	}
	
	public void setLoop(boolean loop) {
		music.setLooping(loop);
	}
	
	public boolean isShuffle() {
		return shuffle;
	}
	
	public boolean isAutoPlay() {
		return autoPlay;
	}
	
	public boolean isLoop() {
		return music.isLooping();
	}
	
	/**
	 * Loads the current selected song.
	 */
	public void loadMusic() {
		Gdx.app.debug("MusicListController", "music is being loaded from music list with " + musicList.getTotal() + " songs.");
		boolean playing = isPlaying();
		musicHeader = null;
		if (music != null) {
			music.dispose();
		}
		if (currentlyPlayingIndex < 0) {
			currentlyPlayingIndex = musicList.getTotal()-1;
		}
		if (currentlyPlayingIndex >= musicList.getTotal()) {
			currentlyPlayingIndex = 0;
		}
		if (musicList.getTotal() != 0) {
			this.music = Gdx.audio.newMusic(musicList.getAudioFileHandle(currentlyPlayingIndex));
			music.setOnCompletionListener(this);
			
			setChanged();
			
			notifyObservers(states.LOADED);
			if (playing || autoPlay) {
				play();
			}
		}
	}
	
	public MusicList getMusicList() {
		return musicList;
	}
	
	public FileHandle getCurrentMusicFileHandle() {
		return musicList.getSongFileHandleFromIndex(currentlyPlayingIndex);
	}
	
	public MinimalAudioHeader getCurrentMusicHeader() {
		if (musicHeader != null) {
			return musicHeader;
		} else {
			return musicHeader = musicList.newMinimalAudioHeader(getCurrentMusicFileHandle());
		}
	}
	
	@Override
	public void update(Observable o, Object arg) {
		if (o == musicList) {
			if (arg == musicList.states.LOADING) {
				pause();
			} else if (arg == musicList.states.COMPLETE) {
				if (shuffle) {
					shuffle();
				}
				loadMusic();
			}
		}
	}
	
	public String getCurrentSongName() {
		return getCurrentMusicFileHandle().nameWithoutExtension();
	}
	
	public float getCurrentPosition() {
		if (music != null) {
			return music.getPosition();
		}
		return 0;
	}
	
	public void setMusicPosition(float position) {
		if (music != null) {
			music.setPosition(position);
		}
	}
	
	public boolean isPlaying() {
		if (music != null) {
			return music.isPlaying();
		}
		return false;
	}
	
	/**
	 * Returns the current music. In no circumstances should this be used to begin playing or it would be playing independent of the controller.
	 * Doing otherwise would mean you are playing the music separately of the controller.
	 * @return the {@link Music} that is currently "loaded" and ready to play.
	 */
	public Music getCurrentMusic() {
		return music;
	}
	
	public int getCurrentlyPlayingIndex() {
		return currentlyPlayingIndex;
	}
	
	public boolean hasSongLoaded() {
		if (music != null) {
			return true;
		}
		return false;
	}
}
