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

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

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

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

    private final DownloadService downloadService;
    private final AtomicBoolean setup = new AtomicBoolean(false);
    private final ReentrantLock lock = new ReentrantLock();
    private Looper eventLooper;
    private Handler eventHandler;
    private BroadcastReceiver ejectEventReceiver;
    private PhoneStateListener phoneStateListener;
    private boolean externalStorageAvailable = true;
    private long lastPressTime = 0;

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


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

    public void onCreate() {
        new Thread(() -> {
            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 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(() -> {
                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.apply();

                        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() {
        if (!setup.get()) {
            return;
        }

        final List<DownloadFile> songs = new ArrayList<>(downloadService.getSongs());
        eventHandler.post(() -> {
            if (lock.tryLock()) {
                try {
                    serializeDownloadQueueNow(songs);
                } finally {
                    lock.unlock();
                }
            }
        });
    }

    private void serializeDownloadQueueNow(List<DownloadFile> songs) {
        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();
        }
        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);
    }

    private 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(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(() -> {
                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;
                }
            });
        }
    }
}