/*
 * Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
 * Part of NewPipe
 *
 * License: GPL-3.0+
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.schabi.newpipe.player;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import org.schabi.newpipe.App;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer;
import org.schabi.newpipe.player.mediasession.PlayerServiceInterface;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;

import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;


/**
 * One service for all players.
 *
 * @author mauriciocolli
 */
public class PlayerService extends Service implements PlayerServiceInterface {
    private static final String TAG = "MainPlayer";
    private static final boolean DEBUG = Player.DEBUG;

    private Player player;
    private WindowManager windowManager;

    private final IBinder mBinder = new PlayerService.LocalBinder();

    public enum PlayerType {
        VIDEO,
        AUDIO,
        POPUP
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Notification
    //////////////////////////////////////////////////////////////////////////*/

    public static final String ACTION_CLOSE
            = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE";
    public static final String ACTION_PLAY_PAUSE
            = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE";
    static final String ACTION_REPEAT
            = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT";
    static final String ACTION_PLAY_NEXT
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT";
    static final String ACTION_PLAY_PREVIOUS
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS";
    static final String ACTION_FAST_REWIND
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND";
    static final String ACTION_FAST_FORWARD
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD";
    public static final String ACTION_SHUFFLE
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE";
    public static final String ACTION_CHANGE_PLAY_MODE
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_CHANGE_PLAY_MODE";
    public static final String ACTION_RECREATE_NOTIFICATION
            = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION";

    public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra";
    public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action";

    @Override
    public Service getInstance() {
        return this;
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Service's LifeCycle
    //////////////////////////////////////////////////////////////////////////*/

    @Override
    public void onCreate() {
        if (DEBUG) {
            Log.d(TAG, "onCreate() called");
        }
        assureCorrectAppLanguage(this);
        windowManager = ContextCompat.getSystemService(this, WindowManager.class);

        ThemeHelper.setTheme(this);
        createView();
    }

    private void createView() {
        final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this));

        player = new Player(this);
        player.setupFromView(binding);
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        if (DEBUG) {
            Log.d(TAG, "onStartCommand() called with: intent = [" + intent
                    + "], flags = [" + flags + "], startId = [" + startId + "]");
        }
        if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
                && player.getPlayQueue() == null) {
            // Player is not working, no need to process media button's action
            return START_NOT_STICKY;
        }
        // null check
        if (player == null) {
            player = new Player(this);
        }

        player.handleIntent(intent);
        if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
                || intent.getStringExtra(Player.PLAY_QUEUE_KEY) != null) {
            NotificationUtil.getInstance().createNotificationAndStartForeground(player, this);
        }
        if (player.getMediaSessionManager() != null) {
            player.getMediaSessionManager().handleMediaButtonIntent(intent);
        }
        return START_NOT_STICKY;
    }

    public void stopForImmediateReusing() {
        if (DEBUG) {
            Log.d(TAG, "stopForImmediateReusing() called");
        }

        if (!player.exoPlayerIsNull()) {
            player.saveWasPlaying();

            // Releases wifi & cpu, disables keepScreenOn, etc.
            // We can't just pause the player here because it will make transition
            // from one stream to a new stream not smooth
            player.smoothStopPlayer();
            player.setRecovery();

            // Android TV will handle back button in case controls will be visible
            // (one more additional unneeded click while the player is hidden)
            player.hideControls(0, 0);
            player.closeItemsList();

            // Notification shows information about old stream but if a user selects
            // a stream from backStack it's not actual anymore
            // So we should hide the notification at all.
            // When autoplay enabled such notification flashing is annoying so skip this case
        }
    }

    @Override
    public void onTaskRemoved(final Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        if (!player.videoPlayerSelected()) {
            return;
        }
        onDestroy();
        // Unload from memory completely
        Runtime.getRuntime().halt(0);
    }

    @Override
    public void onDestroy() {
        if (DEBUG) {
            Log.d(TAG, "destroy() called");
        }
        cleanup();
    }

    private void cleanup() {
        if (player != null) {
            // Exit from fullscreen when user closes the player via notification
            if (player.isFullscreen()) {
                player.toggleFullscreen();
            }
            removeViewFromParent();

            player.saveStreamProgressState();
            player.setRecovery();
            player.stopActivityBinding();
            player.removePopupFromView();
            player.destroy();

            player = null;
        }
    }

    public void stopService() {
        NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
        cleanup();
        stopSelf();
    }

    @Override
    protected void attachBaseContext(final Context base) {
        super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
    }

    @Override
    public IBinder onBind(final Intent intent) {
        return mBinder;
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Utils
    //////////////////////////////////////////////////////////////////////////*/

    public boolean isLandscape() {
        // DisplayMetrics from activity context knows about MultiWindow feature
        // while DisplayMetrics from app context doesn't
        return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null
                ? player.getParentActivity() : this);
    }

    @Nullable
    public View getView() {
        if (player == null) {
            return null;
        }

        return player.getRootView();
    }

    public void removeViewFromParent() {
        if (getView() != null && getView().getParent() != null) {
            if (player.getParentActivity() != null) {
                // This means view was added to fragment
                final ViewGroup parent = (ViewGroup) getView().getParent();
                parent.removeView(getView());
            } else {
                // This means view was added by windowManager for popup player
                windowManager.removeViewImmediate(getView());
            }
        }
    }

    @Override
    public MediaSessionCompat getMediaSession() {
        return null;
    }

    @Override
    public MediaBrowserPlaybackPreparer getMediaBrowserPlaybackPreparer() {
        return null;
    }

    public class LocalBinder extends Binder implements PlayerBinderInterface{

        public PlayerService getService() {
            return PlayerService.this;
        }

        public Player getPlayer() {
            return PlayerService.this.player;
        }
    }
}
