package community.peers.internetradio.network;

import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;

import java.util.ArrayList;
import java.util.HashMap;

import community.peers.internetradio.R;
import community.peers.internetradio.activity.MainActivity;
import community.peers.internetradio.broadcastintent.PlayPause;
import community.peers.internetradio.broadcastintent.Stop;
import community.peers.internetradio.storage.Settings;

public class Player {

    /**
     * Needed when we receive actions occurring in the notification
     * player (via BroadcastIntent). This identify the notification.
     */
    public static final int NOTIFICATION_ID = 0;

    /**
     * Truncate ticker message to this length.
     * The reason is that if it's too long, the user will receive
     * an excessively long notification in the status bar.
     */
    public static final int TICKER_MAX_LENGTH = 100;

    /**
     * This object represent a radio station.
     */
    protected static HashMap<String, String> radio;

    /**
     * libvlc instance.
     */
    protected static LibVLC vlc;

    /**
     * VLC Media Player.
     */
    protected static MediaPlayer player;

    /**
     * This object is created, then passed to the vlc player when a
     * new station is played. It represents a media to be played.
     * http://web.archive.org/web/20180413183729/https://code.videolan.org/videolan/vlc-android/blob/master/libvlc/src/org/videolan/libvlc/Media.java
     */
    protected static Media media_object;

    /**
     * Activity context.
     */
    protected static Activity context;

    /**
     * The notification that will be displayed in the notification drawer.
     */
    protected static NotificationCompat.Builder streamNotification;

    /**
     * This function is called when a user clicks on a radio station.
     * The default action is to stop any open stream and play the new one.
     *
     * @param theContext
     * @param theRadio
     */
    public static void playStream (
        Activity theContext,
        HashMap<String, String> theRadio)
    {
        // Stop player if already playing another station
        Player.stop();

        context = theContext;
        radio = theRadio;

        // Initialize libvlc player
        final ArrayList<String> vlc_args = new ArrayList<>();
        // vlc_args.add("-vvv");
        vlc = new LibVLC (context, vlc_args);
        player = new MediaPlayer (vlc);

        /**
         * Add events listener to the VLC player.
         */
        player.setEventListener (new MediaPlayer.EventListener () {
            @Override
            public void onEvent (MediaPlayer.Event event)
            {
            switch (event.type)
            {
                /**
                 * Change notification "Play" button to "Pause".
                 * The notification has 2 buttons "Play/Pause" and "Stop".
                 */
                case MediaPlayer.Event.Playing:
                    if (Player.streamNotification.mActions != null &&
                        !Player.streamNotification.mActions.isEmpty ())
                    {
                        Player.streamNotification.mActions.get (0).title =
                            context.getString (R.string.stream_pause);

                        Player.streamNotification.mActions.get (0).icon =
                            R.drawable.ic_pause;
                    }

                    // Add a timer to the notification
                    Player.streamNotification
                        .setWhen (System.currentTimeMillis ())
                        .setUsesChronometer (true)
                        .setShowWhen (true);

                    Player.update ();

                    break;

                /**
                 * Change notification "Pause" button to "Play".
                 * The notification has 2 buttons "Play/Pause" and "Stop".
                 */
                case MediaPlayer.Event.Stopped:
                    if (Player.streamNotification.mActions != null &&
                        !Player.streamNotification.mActions.isEmpty ())
                    {
                        Player.streamNotification.mActions.get (0).title =
                            context.getString(R.string.stream_play);

                        Player.streamNotification.mActions.get (0).icon =
                            R.drawable.ic_play;
                    }

                    // Remove timer to the notification
                    Player.streamNotification
                        .setWhen (0)
                        .setUsesChronometer (false)
                        .setShowWhen (false);

                    Player.update ();

                    break;
            }
            }
        });

        // Show a "buffering..." notification for the new station
        Player.notifyNewStream();

        // Add media to VLC player and start streaming
        media_object = new Media (vlc, Uri.parse (radio.get ("stream")));

        // Add event listeners to detect changes in the metadata (song title)
        media_object.setEventListener (new Media.EventListener ()
        {
            @Override
            public void onEvent (Media.Event event)
            {
                // New metadata received
                if (event.type == Media.Event.MetaChanged)
                {
                    try {
                        Player.updateStreamMetadata (
                            Player.player.getMedia ().getMeta (Media.Meta.NowPlaying));
                    } catch (Exception e) {
                        Player.updateStreamMetadata ("");
                    }
                }
            }
        });

        player.setMedia (Player.media_object);

        // Play stream immediately
        Player.play();
    }

    /**
     * Start playing stream.
     * Used when a new station is selected, or to resume a "Pause" action.
     */
    public static void play ()
    {
        if (player == null)
            return;

        player.play ();
    }

    /**
     * Pause media player.
     */
    public static void pause ()
    {
        if (player == null)
            return;

        /**
         * We actually stop() the player instead of pause()ing it.
         * The reason is that with pause(), what happens is
         *   - vlc keeps buffering stream
         *   - when play() again, it will resume where it was paused
         * We don't want this behavior for a live stream, what we want
         * is the current stream playing right now. So, simply stop the
         * player and restart it again later.
         */
        // player.pause ();
        player.stop ();
    }

    /**
     * Stop media player entirely.
     */
    public static void stop ()
    {
        if (player == null)
            return;

        Player.media_object.release();

        player.stop();
        player.release();
        player = null;

        Player.radio = null;
        Player.context = null;
    }

    /**
     * Toggle player pause/play status.
     */
    public static void playOrPause ()
    {
        if (player == null)
            return;

        if (player.isPlaying ())
        {
            Player.pause ();

            // Change notification to display "Pause" message
            Player.updateNotification("Paused", null, null);
        } else {
            // Restart player
            Player.play ();

            // Resume fetching stream metadata
            Player.notifyNewStream ();
        }
    }

    /**
     * Use a compact notification with no buttons.
     */
    public static void setupCompactPlayer ()
    {
        // Play/Pause intent when tapping the notification
        // PendingIntent.getBroadcast (Context context, int requestCode, Intent intent, int flags)
        Intent play_intent = new Intent(Player.context, PlayPause.class);
        PendingIntent play_pintent = PendingIntent.getBroadcast(Player.context, 0, play_intent, 0);

        // Stop intent when removing the notification
        Intent stop_intent = new Intent(Player.context, Stop.class);
        PendingIntent stop_pintent = PendingIntent.getBroadcast(Player.context, 0, stop_intent, 0);

        // Remove buttons
        Player.streamNotification.mActions.clear ();

        Player.streamNotification
            // Add intent when tapping the notification
            .setContentIntent(play_pintent)
            // Add intent when deleting notification
            .setDeleteIntent(stop_pintent)
            // Set removable notification
            .setOngoing (false);
    }

    /**
     * Use a larger notification with buttons.
     */
    public static void setupLargePlayer ()
    {
        // Play/Pause intent when tapping the play/pause button
        // PendingIntent.getBroadcast (Context context, int requestCode, Intent intent, int flags)
        Intent play_intent = new Intent (Player.context, PlayPause.class);
        PendingIntent play_pintent = PendingIntent.getBroadcast (
            Player.context, 0, play_intent, 0);

        // Stop intent when tapping the stop button
        Intent stop_intent = new Intent (Player.context, Stop.class);
        PendingIntent stop_pintent = PendingIntent.getBroadcast (
            Player.context, 0, stop_intent, 0);

        // Intent when tapping the notification (open Main Activity)
        // The .set()/.add() lines below should allow to reopen the
        // existing activity instead of creating a new Main Activity.
        // It works, even though I don't fully understand how.
        Intent mainactivity_intent =
            new Intent (Player.context, MainActivity.class)
            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            .setAction(Intent.ACTION_MAIN)
            .addCategory(Intent.CATEGORY_LAUNCHER);
        PendingIntent mainactivity_pintent = PendingIntent.getActivity (
            Player.context, 0, mainactivity_intent, 0);

        Player.streamNotification
            // Open app Main Activity on tap
            .setContentIntent(mainactivity_pintent)
            // Add intent when deleting notification
            .setDeleteIntent(stop_pintent)
            // Set fixed (unremovable) notification
            .setOngoing (true);

        // Add buttons
        if (player.isPlaying ())
            Player.streamNotification
                .addAction (
                    R.drawable.ic_pause,
                    context.getString (R.string.stream_pause),
                    play_pintent)
                .addAction (
                    R.drawable.ic_stop,
                    context.getString (R.string.stream_stop),
                    stop_pintent);
        else
            Player.streamNotification
                .addAction (
                    R.drawable.ic_play,
                    context.getString (R.string.stream_play),
                    play_pintent)
                .addAction (
                    R.drawable.ic_stop,
                    context.getString (R.string.stream_stop),
                    stop_pintent);
    }

    /**
     * Show/Hide extended notification with Play/Stop buttons.
     */
    public static void togglePlayerCompact()
    {
        if (Player.context == null)
            return;

        // Play/Pause intent when tapping the notification
        // PendingIntent.getBroadcast (Context context, int requestCode, Intent intent, int flags)
        Intent play_intent = new Intent(Player.context, PlayPause.class);
        PendingIntent play_pintent = PendingIntent.getBroadcast(Player.context, 0, play_intent, 0);

        // Stop intent when removing the notification
        Intent stop_intent = new Intent(Player.context, Stop.class);
        PendingIntent stop_pintent = PendingIntent.getBroadcast(Player.context, 0, stop_intent, 0);

        if (Settings.isPlayerCompact (Player.context))
            Player.setupLargePlayer ();
        else
            Player.setupCompactPlayer();

        Player.update ();
    }

    /**
     * Show a notification about the new stream.
     */
    protected static void notifyNewStream()
    {
        if (radio == null || player == null)
            return;

        // Normalize radio name
        String radioName = radio.get("name").trim();
        if (radioName.length() > Player.TICKER_MAX_LENGTH)
            radioName = radioName.substring(0, Player.TICKER_MAX_LENGTH) + "...";

        Player.streamNotification = new NotificationCompat.Builder (context)
            .setAutoCancel (false)
            .setVisibility (NotificationCompat.VISIBILITY_PUBLIC)
            .setSmallIcon (R.drawable.ic_graphic_eq)
            .setContentTitle (radioName)
            .setContentText (context.getString(R.string.notification_buffering))
            .setTicker (context.getString(R.string.notification_buffering) + " " + radioName)
            .setWhen (0).setUsesChronometer (false).setShowWhen (false);

        // Add/Remove buttons depending if player is compact or not
        // This call will also issue the notification.
        if (Settings.isPlayerCompact (Player.context))
            Player.setupCompactPlayer ();
        else
            Player.setupLargePlayer ();

        Player.update ();

        // Update notification icon with radio logo
        updateNotificationLargeIcon();
    }

    /**
     * Change notification parameters and update the notification.
     *
     * @param content
     * @param ticker
     * @param largeIcon
     */
    protected static void
    updateNotification (String content, String ticker, Bitmap largeIcon)
    {
        if (Player.streamNotification == null)
            return;

        // Content is changed to update song info
        if (content != null)
            Player.streamNotification.setContentText(content);

        // Show a new ticker
        if (ticker != null)
        {
            // Remove leading/trailing spaces
            ticker = ticker.trim();

            // Cut ticker message if it's too long
            if (ticker.length() > Player.TICKER_MAX_LENGTH)
                ticker = ticker.substring(0, Player.TICKER_MAX_LENGTH) + "...";

            Player.streamNotification.setTicker(ticker);
        }

        // Update large icon in notification
        if (largeIcon != null)
            Player.streamNotification.setLargeIcon(largeIcon);

        Player.update ();
    }

    /**
     * Send a new async HTTP request to fetch radio image, and then
     * upload the notification with the new logo.
     */
    protected static void updateNotificationLargeIcon()
    {
        RequestQueue queue = Volley.newRequestQueue(context);
        ImageLoader imageLoader = new ImageLoader(queue, new ImageLoader.ImageCache() {
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {

            }
        });

        imageLoader.get(Player.radio.get("logo"), new ImageLoader.ImageListener() {
            @Override
            public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                Player.updateNotification(null, null, response.getBitmap());
            }

            @Override
            public void onErrorResponse(VolleyError error) {

            }
        });
    }

    /**
     * Update the notification containing the player.
     * Called after Player.streamNotification has changed and we're
     * ready to submit the new changes.
     */
    protected static void update ()
    {
        // Gets an instance of the NotificationManager service
        NotificationManager mNotifyMgr =
            (NotificationManager) Player.context.getSystemService(
                Player.context.NOTIFICATION_SERVICE);

        // Builds the notification and issues it.
        // Because the ID remains unchanged, the existing notification is updated
        mNotifyMgr.notify (
            Player.NOTIFICATION_ID,
            Player.streamNotification.build());
    }

    /**
     * Update the player notification with new metadata about the
     * song that is currently being played.
     */
    protected static void updateStreamMetadata (String metadata)
    {
        if (metadata == null)
            metadata = "";

        if (metadata.length() == 0)
            Player.updateNotification ("", null, null);
        else
            Player.updateNotification (
                metadata,
                context.getString (R.string.notification_listening) + metadata,
                null);
    }
}