/*
 This file is part of Subsonic.

 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Subsonic is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.

 Copyright 2009 (C) Sindre Mehus
 */
package net.nullsum.audinaut.util;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import net.nullsum.audinaut.domain.MusicDirectory;
import net.nullsum.audinaut.service.DownloadService;
import net.nullsum.audinaut.service.MusicService;
import net.nullsum.audinaut.service.MusicServiceFactory;
import net.nullsum.audinaut.util.FileUtil;

/**
 * @author Sindre Mehus
 * @version $Id$
 */
public class ShufflePlayBuffer {

	private static final String TAG = ShufflePlayBuffer.class.getSimpleName();
	private static final String CACHE_FILENAME = "shuffleBuffer.ser";

	private ScheduledExecutorService executorService;
	private Runnable runnable;
	private boolean firstRun = true;
	private final ArrayList<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>();
	private int lastCount = -1;
	private DownloadService context;
	private boolean awaitingResults = false;
	private int capacity;
	private int refillThreshold;

	private SharedPreferences.OnSharedPreferenceChangeListener listener;
	private int currentServer;
	private String currentFolder = "";
	private String genre = "";
	private String startYear = "";
	private String endYear = "";

	public ShufflePlayBuffer(DownloadService context) {
		this.context = context;

		executorService = Executors.newSingleThreadScheduledExecutor();
		runnable = new Runnable() {
			@Override
			public void run() {
				refill();
			}
		};
		executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS);
		
		// Calculate out the capacity and refill threshold based on the user's random size preference
		int shuffleListSize = Math.max(1, Integer.parseInt(Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20")));
		// ex: default 20 -> 50
		capacity = shuffleListSize * 5 / 2;
		capacity = Math.min(500, capacity);
		
		// ex: default 20 -> 40
		refillThreshold = capacity * 4 / 5;
	}

	public List<MusicDirectory.Entry> get(int size) {
		clearBufferIfnecessary();
		// Make sure fetcher is running if needed
		restart();

		List<MusicDirectory.Entry> result = new ArrayList<MusicDirectory.Entry>(size);
		synchronized (buffer) {
			boolean removed = false;
			while (!buffer.isEmpty() && result.size() < size) {
				result.add(buffer.remove(buffer.size() - 1));
				removed = true;
			}

			// Re-cache if anything is taken out
			if(removed) {
				FileUtil.serialize(context, buffer, CACHE_FILENAME);
			}
		}
		Log.i(TAG, "Taking " + result.size() + " songs from shuffle play buffer. " + buffer.size() + " remaining.");
		if(result.isEmpty()) {
			awaitingResults = true;
		}
		return result;
	}

	public void shutdown() {
		executorService.shutdown();
		Util.getPreferences(context).unregisterOnSharedPreferenceChangeListener(listener);
	}

	private void restart() {
		synchronized(buffer) {
			if(buffer.size() <= refillThreshold && lastCount != 0 && executorService.isShutdown()) {
				executorService = Executors.newSingleThreadScheduledExecutor();
				executorService.scheduleWithFixedDelay(runnable, 0, 10, TimeUnit.SECONDS);
			}
		}
	}

	private void refill() {
		// Check if active server has changed.
		clearBufferIfnecessary();

		if (buffer != null && (buffer.size() > refillThreshold || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0)) {
			executorService.shutdown();
			return;
		}

		try {
			MusicService service = MusicServiceFactory.getMusicService(context);
			
			// Get capacity based 
			int n = capacity - buffer.size();
			String folder = null;
			if(!Util.isTagBrowsing(context)) {
				folder = Util.getSelectedMusicFolderId(context);
			}
			MusicDirectory songs = service.getRandomSongs(n, folder, genre, startYear, endYear, context, null);

			synchronized (buffer) {
				lastCount = 0;
				for(MusicDirectory.Entry entry: songs.getChildren()) {
					if(!buffer.contains(entry)) {
						buffer.add(entry);
						lastCount++;
					}
				}
				Log.i(TAG, "Refilled shuffle play buffer with " + lastCount + " songs.");

				// Cache buffer
				FileUtil.serialize(context, buffer, CACHE_FILENAME);
			}
		} catch (Exception x) {
			// Give it one more try before quitting
			if(lastCount != -2) {
				lastCount = -2;
			} else if(lastCount == -2) {
				lastCount = 0;
			}
			Log.w(TAG, "Failed to refill shuffle play buffer.", x);
		}
		
		if(awaitingResults) {
			awaitingResults = false;
			context.checkDownloads();
		}
	}

	private void clearBufferIfnecessary() {
		synchronized (buffer) {
			final SharedPreferences prefs = Util.getPreferences(context);
			if (currentServer != Util.getActiveServer(context)
					|| !Util.equals(currentFolder, Util.getSelectedMusicFolderId(context))
					|| (genre != null && !genre.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "")))
					|| (startYear != null && !startYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "")))
					|| (endYear != null && !endYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "")))) {
				lastCount = -1;
				currentServer = Util.getActiveServer(context);
				currentFolder = Util.getSelectedMusicFolderId(context);
				genre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
				startYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "");
				endYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "");
				buffer.clear();

				if(firstRun) {
					ArrayList cacheList = FileUtil.deserialize(context, CACHE_FILENAME, ArrayList.class);
					if(cacheList != null) {
						buffer.addAll(cacheList);
					}

					listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
						@Override
						public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
							clearBufferIfnecessary();
							restart();
						}
					};
					prefs.registerOnSharedPreferenceChangeListener(listener);
					firstRun = false;
				} else {
					// Clear cache
					File file = new File(context.getCacheDir(), CACHE_FILENAME);
					file.delete();
				}
			}
		}
	}
}
