package com.castlabs.sdk.demos;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ScrollView;

import com.castlabs.android.SdkConsts;
import com.castlabs.android.cas.CasConfiguration;
import com.castlabs.android.exoplayer2.ext.amlnativeplayer.AmlComponent;
import com.castlabs.android.exoplayer2.ext.amlnativeplayer.IPTVComponent;
import com.castlabs.android.exoplayer2.ext.amlnativeplayer.IPTVPlugin;
import com.castlabs.android.network.NetworkConfiguration;
import com.castlabs.android.player.BufferConfiguration;
import com.castlabs.android.player.IPTVPlayerPlugin;
import com.castlabs.android.player.PlayerConfig;
import com.castlabs.android.player.PlayerView;
import com.castlabs.android.views.SubtitlesViewComponent;
import com.castlabs.sdk.playerui.PlayerControllerView;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.analytics.IptvAnalyticsListener;
import com.google.android.exoplayer2.cas.CasSession;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.material.snackbar.Snackbar;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;

public class IPTVClearPlayback extends Activity {

	private static final String DEFAULT_MEDIA_URI = "rtp://233.49.82.198:7500";
	private static final int WIDEVINE_CAS_ID = 0x4AD4;

	private static final String TAG = "MainActivity";
	/*
	 * Setting the WVCAS Backend Endpoint
	 * */
	private static final String CAS_LICENSE_URL = "https://lic.test.drmtoday.com/license-proxy-widevine/cas/?uat=true&specConform=true";
	/*
	 * Setting the Device Provision URL from GOOGLE
	 * */
	private static final String GOOGLE_PROVISION_URL = "https://www.googleapis.com/certificateprovisioning/v1/devicecertificates/create?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
	/*
	 * Setting the DRMtoday Custom Header Data
	 * */
	private static final String DT_CUSTOM_DATA = "eyJtZXJjaGFudCI6Ind2X2NhcyIsInVzZXJJZCI6InB1cmNoYXNlIiwic2Vzc2lvbklkIjoicDEifQ==";
	private static final String SAVED_PLAYBACK_STATE_BUNDLE_KEY = "SAVED_PLAYBACK_STATE_BUNDLE_KEY";
	public IptvAnalyticsListener.EventDispatcher dispatcher;
	private PlayerView playerView;
	private int keyCount = 0;
	private PlayerControllerView playerControllerView;

	// Debug
	private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
	private ScrollView debugInfoScrollView;
	private AppCompatTextView debugInfoTv;
	private StringBuffer debugInfoBuff = new StringBuffer();
	private String lastDebugInfoMessage;
	Bundle playbackBundle = null;
	private long clicktime = System.currentTimeMillis();

	private String contentLic ="";
	private Renderer[] mRenderers;

	private Handler handler;

	private long totalBytesTransferred = 0;

	private long currentTime;

	Runnable runnable;

	/*
	 * Setting the Controller Event to move the subtitle block upward when displays the player control.
	 * */
	final 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);
			}
		}
	};

	@Override
	protected void onCreate(@Nullable Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);


		setContentView(R.layout.activity_simple_playback_demo);
		// Get the view components from the layout
		playerView = (PlayerView) findViewById(R.id.player_view);
		// Get the controller view
		playerControllerView = (PlayerControllerView) findViewById(R.id.player_controls);

		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 (getIntent() != null) {
				playbackBundle = getIntent().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);
		}
		Log.d(TAG,"URL = " + playbackBundle.get(SdkConsts.INTENT_URL));

		// Create Event Dispatcher for AML Track and CAS
		dispatcher = new IptvAnalyticsListener.EventDispatcher(
				new Handler(Looper.myLooper()),
				new IptvAnalyticsListnerImpl());

	}

	@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);
	}

	@Override
	public void onStart() {
		super.onStart();
		//Start the player cycle
		playerView.getLifecycleDelegate().start(this);
	}

	@Override
	public void onResume() {
		super.onResume();
		initializePlayer();
		playerControllerView.bind(playerView);
		playerControllerView.addListener(playerControllerViewListener);
		// Resume Player Activity
		playerView.getLifecycleDelegate().resume();
	}

	@Override
	public void onPause() {
		// immediately Pause and Release the Player
		playerView.getPlayerController().pause();
		playerView.getLifecycleDelegate().releasePlayer(false);
		super.onPause();
	}

	@Override
	public void onStop() {
		// immediately Release the Player
		playerView.getPlayerController().release();
		// Unbind the player controller view and remove the listener
		playerControllerView.unbind();
		playerControllerView.removeListener(playerControllerViewListener);
		playerControllerView.setSeekBarListener(null);

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

	@Override
	public void onDestroy() {
		playerView.getPlayerController().destroy();
		super.onDestroy();
	}

	@Override
	public boolean dispatchKeyEvent(KeyEvent event) {
		// Capture the button 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);
	}

	/*
	 * Initialise the player for the 1st content playback
	 * Paramters are getting from INTENT
	 * -d rtp://233.1.1.1:5000
	 * --ez useEth0 true
	 * --es provider <Provider_ID>
	 * --es contentId <Content_ID>
	 *
	 * */
	private void initializePlayer() {
		Intent intent = getIntent();
		Log.d(TAG, "initializePlayer() - " + intent);
		String action = intent.getAction();

		int casId = getCasIdFromParameters(intent, WIDEVINE_CAS_ID);

		Boolean useEth0 = intent.getBooleanExtra("useEth0", true);

		//Get provisionBytes from intent ..
		byte[] provisionBytes = null;
		// Multicast Address to receive content
		String uri = (String) playbackBundle.get(SdkConsts.INTENT_URL);
		if (uri == null) {
			Log.d(TAG, "URI WAS NULL");
			uri = Uri.parse(DEFAULT_MEDIA_URI).toString();
		}
		Log.d(TAG, "initializePlayer() - Loading URI:" + uri.toString() + " Use eth0 = " +useEth0.toString());
		if (provisionBytes != null) {
			Log.d(TAG, "initializePlayer() - ProvisionBytes:" + new String(provisionBytes));
		}
		Bundle keyRequestProperties = new Bundle();
		keyRequestProperties.putString("dt-custom-data", "eyJtZXJjaGFudCI6InRlbGlhX2lwdHYiLCJ1c2VySWQiOiJwdXJjaGFzZSIsInNlc3Npb25JZCI6InAxIn0=");
		keyRequestProperties.putString("Content-Type", "application/octet-stream");

		startPlayer(uri,
				provisionBytes,
				CAS_LICENSE_URL,
				keyRequestProperties );

	}

	private void startPlayer(String uri, byte[] provisionBytes, String licUrl, Bundle keyRequestProperties) {
		Log.d(TAG, "startPlayer()" + uri);
		/*
		 * Configure WVCAS parameters
		 * */
		CasConfiguration casConfig = null;
		NetworkConfiguration networkConfiguration = new NetworkConfiguration.Builder()
				.iptvReadTimeoutMs(2500).get();
		/*
		 * Buffer configuration
		 * */
		BufferConfiguration bufferConfiguration = new BufferConfiguration.Builder()
				.minPlaybackStart(2500, TimeUnit.MILLISECONDS)
				.minRebufferStart(2500, TimeUnit.MILLISECONDS)
				.get();
		/*
		 * Configure the player and setup preference Audio/Subtitle optional
		 * */
		PlayerConfig config = new PlayerConfig.Builder(uri)
				.casConfiguration(casConfig)
				.networkConfiguration(networkConfiguration)
				.useEth0(true)
				.bufferConfiguration(bufferConfiguration)
				.preferredAudioLanguage("fr")
				.get();

		Log.d("startPlayer", "player release()");
		playerView.getPlayerController().release();

		handler = new Handler();
		runnable = new Runnable() {
			public void run() {
				long timeElapsed = (System.currentTimeMillis() - currentTime)/1000;
				if (timeElapsed != 0) {
					long bw = 8 * (totalBytesTransferred / timeElapsed);
					Log.d("IPP", " Bitrate of stream ==== " + bw);
				}
				currentTime = System.currentTimeMillis();
				totalBytesTransferred = 0;
				handler.postDelayed(this, 1000);
			}
		};
		TransferListener transferListener = new TransferListener() {
			@Override
			public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) {
				Log.d("IPP", "Transfer Initializing");
			}

			@Override
			public void onTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork) {
				Log.d("IPP", "Transfer Start");
				runnable.run();
			}

			@Override
			public void onBytesTransferred(DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred) {
				//Log.d("IPP", "Bytes Transferred --- "+bytesTransferred);
				totalBytesTransferred += bytesTransferred;
			}

			@Override
			public void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) {
				Log.d("IPP", "Transfer End");
				if (handler != null) {
					handler.removeCallbacks(runnable);
				}
			}
		};

		final IPTVComponent iptvComponent = playerView.getPlayerController().getComponent( IPTVComponent.class);
		if (iptvComponent != null) {
			iptvComponent.setTransferListener(transferListener);
		}
		// Open the player
		playerView.getPlayerController().open(config);

		final IPTVComponent component = playerView.getPlayerController().getComponent(IPTVComponent.class);

		if (component != null) {
			component.setEventDispatcher(dispatcher);
		} else {
			Log.d(TAG, "Player Component was Null");
		}

		addDebugInfo("Loading URI: " + uri.toString());

		dispatcher.onUserSelectedChannel();  // This is for coldboot measurement. once we implement channel change we can address warm boot

	}


	private byte[] getProvisionBytes(Intent intent) {
		String contentIdStr = intent.getStringExtra("contentId");
		String providerStr = intent.getStringExtra("provider");
		return new byte[1];
	}

	private byte[] getProvisionBytesFromProvisionBytesParameters(Intent intent) {
		byte[] provisionBytes = null;
		int[] tmpProvisionBytes = intent.getIntArrayExtra("provisionBytes");
		if (tmpProvisionBytes != null) {
			provisionBytes = new byte[tmpProvisionBytes.length];
			for (int i = 0; i < tmpProvisionBytes.length; i++) {
				provisionBytes[i] = (byte) tmpProvisionBytes[i];
			}
		}
		Log.d(TAG, "getProvisionBytesFromProvisionBytesParameters() = " + byteArrayToString("provisionBytes", provisionBytes));
		return provisionBytes;
	}

	private static int getCasIdFromParameters(Intent intent, int defaultCasId) {
		int casId;
		String casIdStr = intent.getStringExtra("casId");
		if (casIdStr == null || casIdStr.isEmpty()) {
			casId = defaultCasId;
		} else {
			if (casIdStr.startsWith("0x") && casIdStr.length() == 6) {
				try {
					casId = Integer.parseInt(casIdStr.substring(2), 16);
				} catch (NumberFormatException nfe) {
					Log.e(TAG, "getCasIdFromParameters() - bad parameter casId :" + casIdStr);
					casId = defaultCasId;
				}
			} else {
				Log.e(TAG, "getCasIdFromParameters() - bad parameter casId should be like 0x.... => Use default");
				casId = defaultCasId;
			}
		}
		return casId;
	}

	/* Utility to convert Byte Array as String */
	private static String byteArrayToString(String name, byte[] array) {
		StringBuffer strBuff = new StringBuffer(name);
		strBuff.append(' ');
		if (array == null) {
			strBuff.append("null");
		} else {
			for (byte b : array) {
				strBuff.append(String.format("0x%x", b)).append(",");
			}
		}
		return strBuff.toString();
	}

	public void addDebugInfo(String message, boolean overrideLastMessage) {
		Log.d(TAG, "addDebugInfo(" + message + ", overrideLastMessage=)");
		if (debugInfoBuff.length() > 50000) {
			debugInfoBuff = new StringBuffer();
			debugInfoBuff.append("reset").append("\n");
		}
		if (!overrideLastMessage && lastDebugInfoMessage != null) {
			// Add the previous message to the buffer
			debugInfoBuff.append(lastDebugInfoMessage);
		}
		lastDebugInfoMessage = (new StringBuffer()).append(FORMAT.format(new Date())).append(" - ").append(message).append("\n").toString();
		//debugInfoTv.setText(debugInfoBuff.toString() + lastDebugInfoMessage);
	//	debugInfoScrollView.fullScroll(ScrollView.FOCUS_DOWN);
	}

	public void addDebugInfo(String message) {
		addDebugInfo(message, false);
	}

	public class IptvAnalyticsListnerImpl implements IptvAnalyticsListener {

		private final String TAG = "IptvAnalyticsLsnrImpl";
		int initialMissingSequenceNumber = -1;
		int lastMissingSequenceNumber = -1;
		private long channelSelectedTime = -1;

		@Override
		public void onMissingRtpPacket(int sequenceNumber) {
			Log.d(TAG, "onMissingRtpPacket");
			if (lastMissingSequenceNumber + 1 == sequenceNumber) {
				// Consecutive sequence number
				addDebugInfo("onMissingRtpPacket(from " + initialMissingSequenceNumber + " -> " + sequenceNumber + ")", true);
			} else {
				initialMissingSequenceNumber = sequenceNumber;
				addDebugInfo("onMissingRtpPacket(" + initialMissingSequenceNumber + ")");
			}
			lastMissingSequenceNumber = sequenceNumber;

		}

		@Override
		public void onCorruptedRtpPacket(String error, Uri uri) {
			Log.d(TAG, "onCorruptedRtpPacket");
			addDebugInfo("onCorruptedRtpPacket(" + error + ", " + uri + ")");
		}

		@Override
		public void onNetworkTimeout() {
			Log.d(TAG, "onNetworkTimeout");
			addDebugInfo("onNetworkTimeout()");
		}

		@Override
		public void onLicenceInfoUpdate(String type, long duration, String lic) {
			Log.d(TAG, "onLicenceInfoUpdate");
			contentLic = lic;
			addDebugInfo("onLicenceInfoUpdate(" + type + ", " + duration + ", " + contentLic + ")");
		}

		@Override
		public void onCasError(String error) {
			Log.d(TAG, "onCasError");
			addDebugInfo("onCasError(" + error + ")");
		}

		@Override
		public void onCasEvent(int eventId, String eventStr) {
			switch (eventId){
				case CasSession.LICENSE_EXPIRED:
					addDebugInfo("LICENSE_EXPIRED");
					initializePlayer();
					break;
				case CasSession.ACCESS_DENIED_BY_PARENTAL_CONTROL:
					addDebugInfo("ACCESS_DENIED_BY_PARENTAL_CONTROL");
					break;
				case CasSession.AGE_RESTRICTION_UPDATED:
					addDebugInfo("AGE_RESTRICTION_UPDATED: " + eventStr);
					break;
				default:
					addDebugInfo("onCasEvent" + eventId + ": " + eventStr);
					break;
			}
		}

		@Override
		public void onRenderedFirstFrame(long renderedTime) {
			if (channelSelectedTime != -1) {
				long videoRenderingTime = renderedTime - channelSelectedTime;
				addDebugInfo("onRenderedFirstFrame: " + renderedTime);
				Log.d(TAG, "onRenderedFirstFrame  :" + videoRenderingTime);
			}
			channelSelectedTime = -1;
		}

		@Override
		public void onUserSelectedChannel(long selectedTime) {
			channelSelectedTime = selectedTime;
		}

		@Override
		public void onNativePlayerErrorCode(int errorCode) {
			if (errorCode < errorMsgs.length) {
				String errorMsg = errorMsgs[errorCode];
				Log.d(TAG, "onNativePlayerErrorCode: " + errorMsg);
			}
		}

		public VideoAnalyticsEvent createAnalyticsEvent() {
			// instead of creating a object, we will clone the object with immutable data
			VideoAnalyticsEvent event = new VideoAnalyticsEvent();
			return event;
		}

		public void sendAnalyticsEvent(VideoAnalyticsEvent event) {
			// Here we can use Conviva, yobora or a plain Intent to broadcast
		}

		public class VideoAnalyticsEvent implements Cloneable {

			// Populate the data that is common to all events
			public String appVersion;
			public String sessionId;

			public Object clone() {
				try {
					return super.clone();
				} catch (CloneNotSupportedException e) {
					return null;
				}
			}
		}
	}
}
