package com.castlabs.sdk.mux_demo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;

import com.castlabs.android.player.AbstractPlayerListener;
import com.castlabs.android.player.PlayerConfig;
import com.castlabs.android.player.PlayerController;
import com.castlabs.android.player.PlayerListener;
import com.castlabs.android.player.PlayerView;
import com.castlabs.android.player.playlist.MultiControllerPlaylist;
import com.castlabs.android.player.exceptions.CastlabsPlayerException;
import com.castlabs.android.views.SubtitlesViewComponent;
import com.castlabs.sdk.playerui.PlayerControllerProgressBar;
import com.castlabs.sdk.playerui.PlayerControllerView;

public class PlaylistPlaybackDemo extends Activity {
	public static final String INTENT_PLAYLIST_PLAY_BACKGROUND_KEY = "INTENT_PLAYLIST_PLAY_BACKGROUND_KEY";
	public static final String INTENT_PLAYLIST_BACK_PLAYERS_KEY = "INTENT_PLAYLIST_BACK_PLAYERS_KEY";
	public static final String INTENT_PLAYLIST_FORWARD_PLAYERS_KEY = "INTENT_PLAYLIST_FORWARD_PLAYERS_KEY";
	public static final String INTENT_JUST_ENABLE_ANALYTICS_FOR_CURRENT_ITEM = "INTENT_JUST_ENABLE_ANALYTICS_FOR_CURRENT_ITEM";

	private static final String TAG = "PlaylistPlaybackDemo";
	private static final String SAVED_PLAYBACK_STATE_BUNDLE_KEY = "SAVED_PLAYBACK_STATE_BUNDLE_KEY";

	// This is the player view that we use to start playback
	private PlayerView playerView;

	// This is an example implementation that demonstrates how you can
	// listen to changes on the player controller and how you can access the
	// SubtitleViewComponent to apply adjustments.
	//
	// In this example we add some padding to move the subtitles view up a bit when
	// we are showing the controller.
	private PlayerControllerView.Listener playerControllerViewListener = new PlayerControllerView.Listener() {
		@Override
		public void onVisibilityChanged(int visibility) {
			// This is how you can get a component, in this case the SubtitlesViewComponent
			// from the player view.
			SubtitlesViewComponent svc = playerView.getComponent(SubtitlesViewComponent.class);
			if (svc != null && svc.getView() != null) {
				int height = visibility == View.VISIBLE ? playerControllerView.getHeight() : 0;
				svc.getView().setPadding(0, 0, 0, height);
			}
		}
	};
	private PlayerControllerView playerControllerView;
	private MultiControllerPlaylist multiControllerPlaylist;

	private boolean setPositionToDuration = false;
	private PlayerListener playerListener = new AbstractPlayerListener() {
		@Override
		public void onDurationChanged(long durationUs) {
			if (setPositionToDuration) {
				seekToEnd(multiControllerPlaylist.getCurrentController());
				setPositionToDuration = false;
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_playlist_demo);

		// Get the view components from the layout
		playerView = (PlayerView) findViewById(R.id.player_view);

		// Extract config from the Intent
		final Intent intent = getIntent();
		boolean playBackground = intent.getBooleanExtra(INTENT_PLAYLIST_PLAY_BACKGROUND_KEY, false);
		int backPlayers = intent.getIntExtra(INTENT_PLAYLIST_BACK_PLAYERS_KEY, 0);
		int forwardPlayers = intent.getIntExtra(INTENT_PLAYLIST_FORWARD_PLAYERS_KEY, 1);
		boolean justEnableAnalyticsForCurrentItem = intent.getBooleanExtra(INTENT_JUST_ENABLE_ANALYTICS_FOR_CURRENT_ITEM, true);

		// Create MultiControllerPlaylist, and bind it to our PlayerView
		multiControllerPlaylist = new MultiControllerPlaylist.Builder(playerView)
				// We don't want background players to start playing in background, only
				// when we switch to that playlist item
				.playBackground(playBackground)
				// Start loading the background controllers as soon as possible
				.nextItemThresholdMs(MultiControllerPlaylist.ITEM_THRESHOLD_UPON_START)
				// We keep all track selections when item changes. Meaning that, if an equivalent track
				// (or quality) exists in the new item, it will be selected.
				// This also keeps TrickplayConfiguration.
				.itemChangeFlags(MultiControllerPlaylist.KEEP_ALL)
				// We don't want any "back" players, this means once the current playback item
				// is done, it will be released.
				.backPlayers(backPlayers)
				// We only want to pre-cache the next playlist item, so switching to it will be
				// almost instantaneous.
				.forwardPlayers(forwardPlayers)
				// Set justEnableAnalyticsForCurrentItem value
				.justEnableAnalyticsForCurrentItem(justEnableAnalyticsForCurrentItem)
				// Add playlist listener
				.listener(new MultiControllerPlaylist.PlaylistListener() {
					@Override
					public void onItemChange(@NonNull PlayerConfig newConfig, @Nullable PlayerController oldController, @Nullable PlayerController newController) {
						// Here we need to unbind from the old controller and bind to the new one
						// we also use the migratePlayerListeners helper method
						playerControllerView.unbind();
						if (oldController != null) {
							oldController.movePlayerListeners(newController);
						}
						PlayerControllerProgressBar progressBar = findViewById(R.id.progress_bar);
						progressBar.unbind();

						if (newController != null) {
							playerControllerView.bind(newController, playerView, multiControllerPlaylist);
							progressBar.bind(newController);
						}

						// If we are switching "back" (playback speed < 0), we want to seek to the end of
						// the upcoming item (previous, in playlist order). This depends on your business
						// logic and this is just an example.
						if (oldController != null && newController != null) {
							if (oldController.getSpeed() < 0) {
								long duration = newController.getDuration();
								if (duration != -1) {
									seekToEnd(newController);
								} else {
									// We don't have a duration available yet. Set a flag so we can perform
									// the seek operation as soon as we have a duration.
									setPositionToDuration = true;
								}
							}
						}
					}

					@Override
					public void onControllerCreate(@NonNull PlayerController playerController) {
						Log.d(TAG, "onControllerCreate");
					}

					@Nullable
					@Override
					public PlayerConfig onControllerLoad(@NonNull PlayerConfig config, @NonNull PlayerController playerController) {
						Log.d(TAG, "onControllerLoad");
						// In case you don't use
						// MultiControllerPlaylist.setItemChangeFlags(MultiControllerPlaylist.KEEP_TRICKPLAY_CONFIG)
						// you may want to forward here the Trickplay config to the new PlayerController.
						// This is how it would be done.
						//
						//float speed = multiControllerPlaylist.getCurrentController().getSpeed();
						//return new PlayerConfig.Builder(config)
						//		.trickplayConfiguration(new TrickplayConfiguration.Builder()
						//				.speed(speed)
						// 				// Hardcoded settings we want always the player to use
						//				.preferTrickPlayTracks(true)
						//				.speedupMode(TrickplayConfiguration.SpeedupMode.SEEK)
						//				.maxTrackBitrate(0)
						//				.keepAudioEnabled(false)
						//			.get())
						//		.enableTrickplayMode(speed != 1)
						//		.get();
						return config;
					}

					@Override
					public void onControllerRelease(@NonNull PlayerController playerController) {
						Log.d(TAG, "onControllerRelease");
					}

					@Override
					public void onControllerDestroy(@NonNull PlayerController playerController) {
						Log.d(TAG, "onControllerDestroy");
					}

					@Override
					public void onControllerError(@NonNull PlayerController controller, @NonNull CastlabsPlayerException error, @NonNull PlayerConfig config) {
						if (error.getSeverity() == CastlabsPlayerException.SEVERITY_ERROR) {
							Snackbar.make(playerView, "onControllerError: " + CastlabsPlayerException.typeString(error.getType()) + " : " + error.getCauseMessage(),
									Snackbar.LENGTH_SHORT).show();
							multiControllerPlaylist.removeItem(config);
						}
					}

					@Override
					public void onPlaylistEndReached(boolean willLoop) {
						Log.d(TAG, "onPlaylistEndReached, looping: " + willLoop);
					}
				}).get();

		// Set up Previous and Next button listeners
		findViewById(R.id.buttonPrevious).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				multiControllerPlaylist.previousItem();
			}
		});

		findViewById(R.id.buttonNext).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				multiControllerPlaylist.nextItem();
			}
		});

		// Get the controller view
		playerControllerView = (PlayerControllerView) findViewById(R.id.player_controls);

		Bundle playbackBundle = null;
		if (savedInstanceState == null) {
			Log.d(TAG, "Opening playback from intent bundle");
			// This demo assumes that you send an intent to this Activity that contains the
			// playback information.
			if (intent != null) {
				playbackBundle = intent.getExtras();
			} else {
				Snackbar.make(playerView, "No intent specified", Snackbar.LENGTH_INDEFINITE).show();
			}
		} else {
			Log.d(TAG, "Opening playback from saved state bundle");
			playbackBundle = savedInstanceState.getBundle(SAVED_PLAYBACK_STATE_BUNDLE_KEY);
		}

		if (playbackBundle != null) {
			Parcelable [] parcelables = playbackBundle.getParcelableArray(MainActivity.INTENT_PLAYLIST);
			if (parcelables == null) {
				throw new IllegalArgumentException("Expected an ArrayList of PlayerConfig for INTENT_PLAYLIST key.");
			}
			PlayerConfig [] configs = new PlayerConfig[parcelables.length];
			for (int i = 0; i < parcelables.length; i++) {
				configs[i] = (PlayerConfig) parcelables[i];
			}
			multiControllerPlaylist.open(configs);
		} else {
			Snackbar.make(playerView, "Can not start playback: no bundle specified", Snackbar.LENGTH_INDEFINITE).show();
		}
	}

	private void seekToEnd(@NonNull PlayerController playerController) {
		long durationUs = playerController.getDuration();
		if (durationUs > 1_000_000) {
			playerController.setPosition(durationUs - 1_000_000);
		}
	}

	@Override
	public boolean dispatchKeyEvent(KeyEvent event) {
		boolean handled = false;
		if (playerControllerView != null && playerControllerView.getVisibility() == View.VISIBLE) {
			handled = playerControllerView.dispatchKeyEvent(event);
		} else { // Not visible
			if (event.getAction() == KeyEvent.ACTION_DOWN){
				handled = playerControllerView.dispatchKeyEvent(event);
			}
		}
		return handled || super.dispatchKeyEvent(event);
	}

	// Delegate the onStart event to the player views lifecycle delegate.
	// The delegate will make sure that the screen safer will be disabled and
	// the display will not go to sleep
	@Override
	protected void onStart() {
		super.onStart();
		playerView.getLifecycleDelegate().start(this);
	}

	// Delegate the onResume event to the player views lifecycle delegate.
	// The delegate ensures that the player recovers from a saved state. This needs to
	// be implemented to ensure the the user can for example go to the home screen and
	// come back to this activity.
	@Override
	protected void onResume() {
		super.onResume();
		// Bind the controller view and its listener
		playerControllerView.bind(playerView);
		playerControllerView.addListener(playerControllerViewListener);
		multiControllerPlaylist.getCurrentController().addPlayerListener(playerListener);

		PlayerControllerProgressBar progressBar = (PlayerControllerProgressBar) findViewById(R.id.progress_bar);
		progressBar.bind(playerView.getPlayerController());

		playerView.getLifecycleDelegate().resume();
	}

	// Delegate the onStop event to the player views lifecycle delegate.
	// We release the player when the activity is stopped. This will release all the player
	// resources and save the current playback state. Saving the state is required so the
	// onResume callback can recover properly.
	@Override
	protected void onStop() {
		super.onStop();

		// Unbind the player controller view and remove the listener
		playerControllerView.unbind();
		playerControllerView.removeListener(playerControllerViewListener);
		multiControllerPlaylist.getCurrentController().removePlayerListener(playerListener);

		playerView.getLifecycleDelegate().releasePlayer(false);
	}

	// Save the playback state when the activity is destroyed in order to correctly
	// resume after the activity is re-created again i.e. onCreate is called
	@Override
	public void onSaveInstanceState(Bundle outState) {
		Bundle savedStateBundle = new Bundle();
		PlayerConfig playerConfig = playerView.getPlayerController().getPlayerConfig();
		if (playerConfig != null) {
			playerView.getPlayerController().getPlayerConfig().save(savedStateBundle);
			outState.putBundle(SAVED_PLAYBACK_STATE_BUNDLE_KEY, savedStateBundle);
		}
		super.onSaveInstanceState(outState);
	}
}

