/*
 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.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;

import net.nullsum.audinaut.domain.MusicDirectory;
import net.nullsum.audinaut.domain.PlayerQueue;
import net.nullsum.audinaut.domain.PlayerState;
import net.nullsum.audinaut.util.CacheCleaner;
import net.nullsum.audinaut.util.Constants;
import net.nullsum.audinaut.util.FileUtil;
import net.nullsum.audinaut.util.Pair;
import net.nullsum.audinaut.util.SilentBackgroundTask;
import net.nullsum.audinaut.util.SongDBHandler;
import net.nullsum.audinaut.util.Util;

import static net.nullsum.audinaut.domain.PlayerState.PREPARING;

/**
 * @author Sindre Mehus
 */
public class DownloadServiceLifecycleSupport {
	private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName();
	public static final String FILENAME_DOWNLOADS_SER = "downloadstate2.ser";
	private static final int DEBOUNCE_TIME = 200;

	private final DownloadService downloadService;
	private Looper eventLooper;
	private Handler eventHandler;
	private BroadcastReceiver ejectEventReceiver;
	private PhoneStateListener phoneStateListener;
	private boolean externalStorageAvailable= true;
	private ReentrantLock lock = new ReentrantLock();
	private final AtomicBoolean setup = new AtomicBoolean(false);
	private long lastPressTime = 0;
	private SilentBackgroundTask<Void> currentSavePlayQueueTask = null;
	private Date lastChange = null;

	/**
	 * This receiver manages the intent that could come from other applications.
	 */
	private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(final Context context, final Intent intent) {
			eventHandler.post(new Runnable() {
				@Override
				public void run() {
					String action = intent.getAction();
					Log.i(TAG, "intentReceiver.onReceive: " + action);
					if (DownloadService.CMD_PLAY.equals(action)) {
						downloadService.play();
					} else if (DownloadService.CMD_NEXT.equals(action)) {
						downloadService.next();
					} else if (DownloadService.CMD_PREVIOUS.equals(action)) {
						downloadService.previous();
					} else if (DownloadService.CMD_TOGGLEPAUSE.equals(action)) {
						downloadService.togglePlayPause();
					} else if (DownloadService.CMD_PAUSE.equals(action)) {
						downloadService.pause();
					} else if (DownloadService.CMD_STOP.equals(action)) {
						downloadService.pause();
						downloadService.seekTo(0);
					}
				}
			});
		}
	};


	public DownloadServiceLifecycleSupport(DownloadService downloadService) {
		this.downloadService = downloadService;
	}

	public void onCreate() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				Looper.prepare();
				eventLooper = Looper.myLooper();
				eventHandler = new Handler(eventLooper);

				// Deserialize queue before starting looper
				try {
					lock.lock();
					deserializeDownloadQueueNow();

					// Wait until PREPARING is done to mark lifecycle as ready to receive events
					while(downloadService.getPlayerState() == PREPARING) {
						Util.sleepQuietly(50L);
					}

					setup.set(true);
				} finally {
					lock.unlock();
				}

				Looper.loop();
			}
		}, "DownloadServiceLifecycleSupport").start();

		// Stop when SD card is ejected.
		ejectEventReceiver = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				externalStorageAvailable = Intent.ACTION_MEDIA_MOUNTED.equals(intent.getAction());
				if (!externalStorageAvailable) {
					Log.i(TAG, "External media is ejecting. Stopping playback.");
					downloadService.reset();
				} else {
					Log.i(TAG, "External media is available.");
				}
			}
		};
		IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
		ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
		ejectFilter.addDataScheme("file");
		downloadService.registerReceiver(ejectEventReceiver, ejectFilter);

		// React to media buttons.
		Util.registerMediaButtonEventReceiver(downloadService);

		// Pause temporarily on incoming phone calls.
		phoneStateListener = new MyPhoneStateListener();

		// Android 6.0 removes requirement for android.Manifest.permission.READ_PHONE_STATE;
		TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
		telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

		// Register the handler for outside intents.
		IntentFilter commandFilter = new IntentFilter();
		commandFilter.addAction(DownloadService.CMD_PLAY);
		commandFilter.addAction(DownloadService.CMD_TOGGLEPAUSE);
		commandFilter.addAction(DownloadService.CMD_PAUSE);
		commandFilter.addAction(DownloadService.CMD_STOP);
		commandFilter.addAction(DownloadService.CMD_PREVIOUS);
		commandFilter.addAction(DownloadService.CMD_NEXT);
		commandFilter.addAction(DownloadService.CANCEL_DOWNLOADS);
		downloadService.registerReceiver(intentReceiver, commandFilter);

		new CacheCleaner(downloadService, downloadService).clean();
	}

	public boolean isInitialized() {
		return setup.get();
	}

	public void onStart(final Intent intent) {
		if (intent != null) {
			final String action = intent.getAction();

			if(eventHandler == null) {
				Util.sleepQuietly(100L);
			}
			if(eventHandler == null) {
				return;
			}

			eventHandler.post(new Runnable() {
				@Override
				public void run() {
					if(!setup.get()) {
						lock.lock();
						lock.unlock();
					}

					if(DownloadService.START_PLAY.equals(action)) {
						int offlinePref = intent.getIntExtra(Constants.PREFERENCES_KEY_OFFLINE, 0);
						if(offlinePref != 0) {
							boolean offline = (offlinePref == 2);
							Util.setOffline(downloadService, offline);
							if (offline) {
								downloadService.clearIncomplete();
							} else {
								downloadService.checkDownloads();
							}
						}

						if(intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false)) {
							// Add shuffle parameters
							SharedPreferences.Editor editor = Util.getPreferences(downloadService).edit();
							String startYear = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR);
							if(startYear != null) {
								editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, startYear);
							}

							String endYear = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR);
							if(endYear != null) {
								editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, endYear);
							}

							String genre = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_GENRE);
							if(genre != null) {
								editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
							}
							editor.commit();

							downloadService.clear();
							downloadService.setShufflePlayEnabled(true);
						} else {
							downloadService.start();
						}
					} else if(DownloadService.CMD_TOGGLEPAUSE.equals(action)) {
						downloadService.togglePlayPause();
					} else if(DownloadService.CMD_NEXT.equals(action)) {
						downloadService.next();
					} else if(DownloadService.CMD_PREVIOUS.equals(action)) {
						downloadService.previous();
					} else if(DownloadService.CANCEL_DOWNLOADS.equals(action)) {
						downloadService.clearBackground();
					} else if(intent.getExtras() != null) {
						final KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
						if (event != null) {
							handleKeyEvent(event);
						}
					}
				}
			});
		}
	}

	public void onDestroy() {
		serializeDownloadQueue();
		eventLooper.quit();
		downloadService.unregisterReceiver(ejectEventReceiver);
		downloadService.unregisterReceiver(intentReceiver);

		TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
		telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
	}

	public boolean isExternalStorageAvailable() {
		return externalStorageAvailable;
	}

	public void serializeDownloadQueue() {
		serializeDownloadQueue(true);
	}
	public void serializeDownloadQueue(final boolean serializeRemote) {
		if(!setup.get()) {
			return;
		}

		final List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
		eventHandler.post(new Runnable() {
			@Override
			public void run() {
				if(lock.tryLock()) {
					try {
						serializeDownloadQueueNow(songs, serializeRemote);
					} finally {
						lock.unlock();
					}
				}
			}
		});
	}

	public void serializeDownloadQueueNow(List<DownloadFile> songs, boolean serializeRemote) {
		final PlayerQueue state = new PlayerQueue();
		for (DownloadFile downloadFile : songs) {
			state.songs.add(downloadFile.getSong());
		}
		for (DownloadFile downloadFile : downloadService.getToDelete()) {
			state.toDelete.add(downloadFile.getSong());
		}
		state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
		state.currentPlayingPosition = downloadService.getPlayerPosition();

		DownloadFile currentPlaying = downloadService.getCurrentPlaying();
		if(currentPlaying != null) {
			state.renameCurrent = currentPlaying.isWorkDone() && !currentPlaying.isCompleteFileAvailable();
		}
		state.changed = lastChange = new Date();

		Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
		FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
	}

	public void post(Runnable runnable) {
		eventHandler.post(runnable);
	}

	private void deserializeDownloadQueueNow() {
		PlayerQueue state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER, PlayerQueue.class);
		if (state == null) {
			return;
		}
		Log.i(TAG, "Deserialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);

		// Rename first thing before anything else starts
		if(state.renameCurrent && state.currentPlayingIndex != -1 && state.currentPlayingIndex < state.songs.size()) {
			DownloadFile currentPlaying = new DownloadFile(downloadService, state.songs.get(state.currentPlayingIndex), false);
			currentPlaying.renamePartial();
		}

		downloadService.restore(state.songs, state.toDelete, state.currentPlayingIndex, state.currentPlayingPosition);

		if(state != null) {
			lastChange = state.changed;
		}
	}

	public Date getLastChange() {
		return lastChange;
	}

	public void handleKeyEvent(KeyEvent event) {
		if(event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() > 0) {
			switch (event.getKeyCode()) {
				case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
					downloadService.fastForward();
					break;
				case KeyEvent.KEYCODE_MEDIA_NEXT:
					downloadService.rewind();
					break;
			}
		} else if(event.getAction() == KeyEvent.ACTION_UP) {
			switch (event.getKeyCode()) {
				case KeyEvent.KEYCODE_HEADSETHOOK:
				case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
					if(lastPressTime < (System.currentTimeMillis() - 500)) {
						lastPressTime = System.currentTimeMillis();
						downloadService.togglePlayPause();
					} else {
						downloadService.next(false, true);
					}
					break;
				case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
					if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
						lastPressTime = System.currentTimeMillis();
						downloadService.previous();
					}
					break;
				case KeyEvent.KEYCODE_MEDIA_NEXT:
					if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
						lastPressTime = System.currentTimeMillis();
						downloadService.next();
					}
					break;
				case KeyEvent.KEYCODE_MEDIA_REWIND:
					downloadService.rewind();
					break;
				case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
					downloadService.fastForward();
					break;
				case KeyEvent.KEYCODE_MEDIA_STOP:
					downloadService.stop();
					break;
				case KeyEvent.KEYCODE_MEDIA_PLAY:
					if(downloadService.getPlayerState() != PlayerState.STARTED) {
						downloadService.start();
					}
					break;
				case KeyEvent.KEYCODE_MEDIA_PAUSE:
					downloadService.pause();
				default:
					break;
			}
		}
	}

	/**
	 * Logic taken from packages/apps/Music.  Will pause when an incoming
	 * call rings or if a call (incoming or outgoing) is connected.
	 */
	private class MyPhoneStateListener extends PhoneStateListener {
		private boolean resumeAfterCall;

		@Override
		public void onCallStateChanged(final int state, String incomingNumber) {
			eventHandler.post(new Runnable() {
				@Override
				public void run() {
					switch (state) {
						case TelephonyManager.CALL_STATE_RINGING:
						case TelephonyManager.CALL_STATE_OFFHOOK:
							if (downloadService.getPlayerState() == PlayerState.STARTED) {
								resumeAfterCall = true;
								downloadService.pause(true);
							}
							break;
						case TelephonyManager.CALL_STATE_IDLE:
							if (resumeAfterCall) {
								resumeAfterCall = false;
								if(downloadService.getPlayerState() == PlayerState.PAUSED_TEMP) {
									downloadService.start();
								}
							}
							break;
						default:
							break;
					}
				}
			});
		}
	}
}
