package zero1hd.rhythmbullet.audio;

import java.util.Comparator;
import java.util.Observable;
import java.util.Observer;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Sort;

import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.audio.metadata.MP3Metadata;
import zero1hd.rhythmbullet.audio.metadata.WAVMetadata;

public class AudioMetadataController extends Observable implements Disposable, Observer {
	private MusicList musicList;
	private volatile Array<AudioMetadata> metadataArray;
	private MetadataLoadingThread loadingThread;
	private volatile boolean searching;
	private Comparator<AudioMetadata> metadataComparer;
	
	public AudioMetadataController(MusicList musicList) {
		this.musicList = musicList;
		metadataArray = new Array<>();
		loadingThread = new MetadataLoadingThread();
		musicList.addObserver(this);
		metadataComparer = new Comparator<AudioMetadata>() {
			@Override
			public int compare(AudioMetadata o1, AudioMetadata o2) {
				return o1.getTitle().compareToIgnoreCase(o2.getTitle());
			}
		};
	}
	
	public MusicList getMusicList() {
		return musicList;
	}
	
	/**
	 * Non-blocking, loads on separate thread.
	 */
	public void loadAudioMetadata() {
		if (!loadingThread.start()) {
			loadingThread.stop();
			loadingThread = new MetadataLoadingThread();
			loadingThread.start();
		}
	}
	
	/**
	 * if there is the same amount of metadata as there is music in the music list.
	 * @return whether or not both sizes are equal.
	 */
	public boolean isSameSizeMusicList() {
		return (metadataArray.size == musicList.getTotal());
	}
	
	@Override
	public void dispose() {
		for (int i = 0; i < metadataArray.size; i++) {
			metadataArray.get(i).dispose();
		}
	}
	
	public int size() {
		return metadataArray.size;
	}
	
	public AudioMetadata getAudioMetadata(int index) {
		return metadataArray.get(index);
	}
	
	
	public AudioMetadata getAudioMetadata(FileHandle filehandle) {
		for (int i = 0; i < metadataArray.size; i++) {
			if (metadataArray.get(i).getFileHandle() == filehandle) {
				return metadataArray.get(i);
			}
		}
		throw new IllegalArgumentException("Couldn't find file " + filehandle.name());
	}
	
	public boolean isSearching() {
		return searching;
	}
	
	private class MetadataLoadingThread implements Runnable {
		private Thread thread;
		private String name = "AudioMetadata-Load";
		private volatile boolean work = true;
		
		@Override
		public void run() {
			Gdx.app.debug(name, "loading...");
			clear();
			synchronized (this) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			searching = true;
			Array<AudioMetadata> tempMetadataArray = new Array<>();
			for (int i = 0; i < musicList.getTotal() && work; i++) {
				FileHandle musicFile = musicList.getAudioFileHandle(i);
				if (musicFile == null) return;
				switch (SupportedFormats.valueOf(musicFile.extension().toUpperCase())) {
				case MP3:
					tempMetadataArray.add(new MP3Metadata(musicFile));
					break;
				case WAV:
					tempMetadataArray.add(new WAVMetadata(musicFile));
					break;
				default:
					break;
				}
			}
			
			Sort.instance().sort(tempMetadataArray, metadataComparer);
			
			if (work) {
				metadataArray = tempMetadataArray;
				searching = false;
				Gdx.app.debug(name, "load complete.");
				setChanged();
				notifyObservers();
			}
		}
		
 		public boolean start() {
 			if (thread == null) {
 				thread = new Thread(this, name);
 				thread.start();
 				return true;
 			} else {
 				return false;
 			}
 		}
 		
 		public void stop() {
 			work = false;
 		}
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o == musicList && arg == musicList.states.COMPLETE) {
			loadAudioMetadata();
		}
	}
	
	public void clear() {
		Gdx.app.postRunnable(() -> {
			for (int i = 0; i < metadataArray.size; i++) {
				metadataArray.get(i).dispose();
			}
			metadataArray.clear();
			synchronized (loadingThread) {
				loadingThread.notify();
			}
		});
	}
}
