/*
 * ***********************************************************************
 *
 *  ADOBE CONFIDENTIAL
 *  ___________________
 *
 *  Copyright 2013 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 *  NOTICE:  All information contained herein is, and remains
 *  the property of Adobe Systems Incorporated and its suppliers,
 *  if any.  The intellectual and technical concepts contained
 *  herein are proprietary to Adobe Systems Incorporated and its
 *  suppliers and are protected by trade secret or copyright law.
 *  Dissemination of this information or reproduction of this material
 *  is strictly forbidden unless prior written permission is obtained
 *  from Adobe Systems Incorporated.
 *
 * ***********************************************************************
 */

package com.adobe.primetime.reference.manager.test;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import com.adobe.mediacore.*;
import com.adobe.mediacore.drm.DRMManager;
import com.adobe.mediacore.metadata.Notification;
import com.adobe.mediacore.timeline.advertising.AdBreak;
import com.adobe.mediacore.timeline.advertising.AdClick;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.adobe.mediacore.ABRControlParameters.ABRPolicy;
import com.adobe.mediacore.info.AudioTrack;
import com.adobe.mediacore.info.ClosedCaptionsTrack;
import com.adobe.mediacore.metadata.Metadata;
import com.adobe.mediacore.qos.metrics.PlaybackMetrics;
import com.adobe.mediacore.timeline.Timeline;
import com.adobe.mediacore.timeline.TimelineMarker;
import com.adobe.mediacore.timeline.advertising.Ad;
import com.adobe.mediacore.utils.TimeRange;

import com.adobe.primetime.reference.manager.test.MediaPlayerEventHandlerUtil.MediaPlayerEventType;

/**
 * This is a fake media player implements from PSDK media player. It simulates a
 * playback session without loading media resource.
 * 
 */
public class MockMediaPlayer extends MediaPlayer {

	protected MediaPlayerEventHandlerUtil eventHandlers = new MediaPlayerEventHandlerUtil();

	// internal player tracking variables
	private MediaPlayerItem playerItem = null;
	private PlayerStatus playerState = null;
	private long currentTime, duration, width, height;
	private boolean isLive;
	private TimeRange playbackRange, seekableRange;
	private ABRControlParameters abrParams = new ABRControlParameters(0, 0, 0,
			ABRPolicy.ABR_MODERATE);;
	private List<ClosedCaptionsTrack> cctracks = null;
	private ClosedCaptionsTrack selectCCTrack = null;
	private List<AudioTrack> aatracks = null;
	@SuppressWarnings("unused")
	private List<Integer> failedAlternateAudioTracks = new ArrayList<Integer>();
	@SuppressWarnings("unused")
	private AudioTrack trackAudio = null;

	/**
	 * Create a mock media player with fake playback session
	 */
	public MockMediaPlayer(Context context) {
		super(context);
		playerState = PlayerStatus.IDLE;
		duration = 0;
		width = 0;
		height = 0;
		isLive = false;
	}

	/**
	 * Create a mock media player with a given duration
	 * 
	 * @param duration
	 *            the duration of the fake playback session.
	 * @param isLive
	 *            whether this fake playback session is live stream
	 * @param width
	 *            the width of the fake playback session
	 * @param height
	 *            the height of the fake playback session
	 * 
	 */
	public MockMediaPlayer(Context context, long duration, boolean isLive, long width, long height) {
		super(context);
		playerState = PlayerStatus.IDLE;
		this.duration = duration;
		this.isLive = isLive;
		this.width = width;
		this.height = height;
	}

	/**
	 * Create a mock media player with a given duration and CC tracks
	 * 
	 * @param duration
	 *            the duration of the fake playback session.
	 * @param isLive
	 *            whether this fake playback session is live stream
	 * @param width
	 *            the width of the fake playback session
	 * @param height
	 *            the height of the fake playback session
	 * @param cc
	 *            track list
	 * 
	 */

	public MockMediaPlayer(Context context, long duration, boolean isLive, long width, long height, List<ClosedCaptionsTrack> cc) {
		super(context);
		playerState = PlayerStatus.IDLE;
		this.duration = duration;
		this.isLive = isLive;
		this.width = width;
		this.height = height;
		this.cctracks = cc;
	}

	/**
	 * Create a mock media player with a given duration and AA tracks
	 * 
	 * @param aa
	 *            track list
	 */

	public MockMediaPlayer(Context context, List<AudioTrack> aa, List<Integer> failed) {
		super(context);
		playerState = PlayerStatus.IDLE;
		this.aatracks = aa;
		this.failedAlternateAudioTracks = failed;
	}

	@Override
	public void addEventListener(EventListener listener) {
		eventHandlers.addEventListener(listener);
	}

	/**
	 * Simulate media player buffer
	 */
	public void buffer() {
		Event event = BufferEvent.create();
		eventHandlers.dispatch(MediaPlayerEventType.kEventBufferingBegin, event);
		eventHandlers.dispatch(MediaPlayerEventType.kEventBufferingEnd, event);
	}

	/**
	 * Simulate media player error
	 */
	public void error(PSDKErrorCode error) {
		playerState = PlayerStatus.ERROR;

		Event event = MediaPlayerStatusChangeEvent.create(playerState, error);
		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged, event);
	}

	/**
	 * Simulates ad break life cycle
	 * 
	 * @param ads
	 *            Array of ads in the ad break
	 * @param progress
	 *            Array of progress percentages for each ad
	 */
	public void simulateAdBreakLifeCyle(ArrayList<Ad> ads, int[] progress) {
		onAdBreakStart();
		for (Ad ad : ads) {
			onAdStart(ad);
			for (int currentProgress : progress) {
				onAdProgress(ad, currentProgress);
			}
			onAdComplete(ad);
		}
		onAdBreakComplete();
	}

	/**
	 * Simulate on adBreakStart to the Ad playback event listeners.
	 */
	public void onAdBreakStart() {
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdBreakStarted, null);
	}

	/**
	 * Simulate on adStart to the Ad playback event listeners.
	 */
	public void onAdStart(Ad ad) {
		List<Ad> ads = new ArrayList<Ad>();
		ads.add(ad);
		AdBreak adBreak = AdBreak.createAdBreak(ads, null);
		Event event = AdPlaybackEvent.create(MediaPlayerEventType.kEventAdStarted.name(), adBreak, ad, 0, 0, 1);
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdStarted, event);
	}

	/**
	 * Simulate on adBreakComplete to the Ad playback event listeners.
	 */
	public void onAdBreakComplete() {
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdBreakCompleted, null);
	}

	/**
	 * Simulate on adComplete to the Ad playback event listeners.
	 */
	public void onAdComplete(Ad ad) {
		List<Ad> ads = new ArrayList<Ad>();
		ads.add(ad);
		AdBreak adBreak = AdBreak.createAdBreak(ads, null);
		Event event = AdPlaybackEvent.create(MediaPlayerEventType.kEventAdCompleted.name(), adBreak, ad, 0, 0, 1);
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdCompleted, event);
	}

	/**
	 * Simulate on ad click to the ad playback event listeners
	 */
	public void onAdClick(Ad ad) {
		List<Ad> ads = new ArrayList<Ad>();
		ads.add(ad);
		AdBreak adBreak = AdBreak.createAdBreak(ads, null);
		AdClick adClick = new AdClick("id", "title", "url");
		Event event = AdClickEvent.create(adBreak, ad, adClick);
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdClicked, event);
	}

	/**
	 * Simulate on adProgress to the Ad playback event listeners.
	 */
	public void onAdProgress(Ad ad, int percentage) {
		List<Ad> ads = new ArrayList<Ad>();
		ads.add(ad);
		AdBreak adBreak = AdBreak.createAdBreak(ads, null);
		Event event = AdPlaybackEvent.create(MediaPlayerEventType.kEventAdProgress.name(), adBreak, ad, percentage, 0, 1);
		eventHandlers.dispatch(MediaPlayerEventType.kEventAdProgress, event);
	}

	/**
	 * Simulate media player operation fail
	 */
	public void operationFail(Notification notification) {

		Event event = NotificationEvent.create(notification);
		eventHandlers.dispatch(MediaPlayerEventType.kEventOperationFailed, event);
	}

	public void onTimelineUpdated() {
		Event event = TimelineEvent.create(MediaPlayerEventType.kEventTimelineUpdated.name(), null);
		eventHandlers.dispatch(MediaPlayerEventType.kEventTimelineUpdated, event);
	}

	/**
	 * Simulate timeline update
	 */
	public void timelineUpdate() {

		Event event = MediaPlayerStatusChangeEvent.create(playerState, null);
		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged, event);
	}

	@Override
	public BufferControlParameters getBufferControlParameters()
			throws IllegalStateException {
		return null;
	}

	@Override
	public TimeRange getBufferedRange() throws IllegalStateException {
		return null;
	}

	@Override
	public TextFormat getCCStyle() throws IllegalStateException {
		return null;
	}

	@Override
	public Visibility getCCVisibility() throws IllegalStateException {
		return null;
	}

	@Override
	public MediaPlayerItem getCurrentItem() throws IllegalStateException {
		return playerItem;
	}

	@Override
	public long getCurrentTime() throws IllegalStateException {
		return currentTime;
	}

	@Override
	public DRMManager getDRMManager() {
		return null;
	}

	@Override
	public PlaybackMetrics getPlaybackMetrics() throws IllegalStateException {
		return null;
	}

	@Override
	public TimeRange getPlaybackRange() throws IllegalStateException {
		return playbackRange;
	}

	@Override
	public TimeRange getSeekableRange() throws IllegalStateException {
		return seekableRange;
	}

	@Override
	public PlayerStatus getStatus() throws IllegalStateException {
		return playerState;
	}

	@Override
	public Timeline<TimelineMarker> getTimeline() throws IllegalStateException {
		return null;
	}

	@Override
	public MediaPlayerView getView() throws IllegalStateException {
		return null;
	}

	@Override
	public void pause() throws IllegalStateException {
		if (playerState == PlayerStatus.PLAYING) {
			playerState = PlayerStatus.PAUSED;
		} else {
			throw new IllegalStateException("Illegal state transition");
		}

		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged,
				MediaPlayerStatusChangeEvent.create(playerState, null));

	}

	@Override
	public void play() throws IllegalStateException {
		if (playerState == PlayerStatus.PREPARED || playerState == PlayerStatus.PAUSED) {
			playerState = PlayerStatus.PLAYING;
		} else {
			throw new IllegalStateException("Illegal state transition");
		}

		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged,
				MediaPlayerStatusChangeEvent.create(playerState, null));

	}

	@Override
	public void prepareToPlay() throws IllegalStateException {
		playerState = PlayerStatus.PREPARED;
		currentTime = 0;
		seekableRange = playbackRange = TimeRange.createRange(currentTime,
				duration);

		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged,
				MediaPlayerStatusChangeEvent.create(playerState, null));


		eventHandlers.dispatch(MediaPlayerEventType.kEventSizeAvailable,
				SizeAvailableEvent.create(width, height));
	}

	@Override
	public void prepareToPlay(long arg0) throws IllegalStateException,
			IllegalArgumentException {

	}

	@Override
	public void release() {
		playerState = PlayerStatus.RELEASED;
	}

	@Override
	public void removeEventListener(EventListener listener) {
		eventHandlers.removeEventListener(listener);
	}

	@Override
	public void replaceCurrentItem(MediaPlayerItem mediaPlayerItem)
			throws IllegalStateException {
		playerState = PlayerStatus.INITIALIZING;

		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged,
				MediaPlayerStatusChangeEvent.create(playerState, null));


		if (playerItem == null) {
			playerItem = mock(MediaPlayerItem.class);
		}
		when(playerItem.getResource()).thenReturn(mediaPlayerItem.getResource());
		when(playerItem.isLive()).thenReturn(isLive);

		// For Audio functionality
		when(playerItem.getAudioTracks()).thenReturn(aatracks);

		if (aatracks == null || aatracks.isEmpty()) {
			when(playerItem.hasAlternateAudio()).thenReturn(false);
		} else {
			when(playerItem.getSelectedAudioTrack())
					.thenReturn(aatracks.get(0));
			when(playerItem.hasAlternateAudio()).thenReturn(true);
		}
		when(playerItem.selectAudioTrack(any(AudioTrack.class))).thenAnswer(
				new Answer<Boolean>() {

					@Override
					public Boolean answer(InvocationOnMock invocation)
							throws Throwable {
						Object[] args = invocation.getArguments();
						if (aatracks.contains((AudioTrack) args[0])) {
							trackAudio = (AudioTrack) args[0];
							return true;
						} else {
							return false;
						}

					}

				});

		// For Closed Captioning functionality
		when(playerItem.getClosedCaptionsTracks()).thenReturn(cctracks);
		if (cctracks == null || cctracks.isEmpty()) {
			when(playerItem.hasClosedCaptions()).thenReturn(false);
		} else {
			when(playerItem.hasClosedCaptions()).thenReturn(true);
		}
		when(
				playerItem
						.selectClosedCaptionsTrack(any(ClosedCaptionsTrack.class)))
				.thenAnswer(new Answer<Boolean>() {

					@Override
					public Boolean answer(InvocationOnMock invocation)
							throws Throwable {
						Object[] args = invocation.getArguments();
						if (cctracks.contains((ClosedCaptionsTrack) args[0])) {
							selectCCTrack = (ClosedCaptionsTrack) args[0];
							return true;
						} else {
							return false;
						}

					}

				});
		when(playerItem.getSelectedClosedCaptionsTrack()).thenReturn(
				selectCCTrack);
		playerState = PlayerStatus.INITIALIZED;

		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged,
				MediaPlayerStatusChangeEvent.create(playerState, null));

	}

	@Override
	public void reset() throws IllegalStateException {
		playerState = PlayerStatus.IDLE;
	}

	@Override
	public void seek(long adjustedTime) throws IllegalStateException,
			IllegalArgumentException {

		Event event;

		event = SeekEvent.create(MediaPlayerEventType.kEventSeekBegin.name(), adjustedTime, adjustedTime, true);
		eventHandlers.dispatch(MediaPlayerEventType.kEventSeekBegin, event);

		currentTime = adjustedTime;

		event = SeekEvent.create(MediaPlayerEventType.kEventSeekEnd.name(), adjustedTime, adjustedTime, true);
		eventHandlers.dispatch(MediaPlayerEventType.kEventSeekEnd, event);


		if (adjustedTime == playbackRange.getEnd()) {
			playerState = PlayerStatus.COMPLETE;
		}

		event = MediaPlayerStatusChangeEvent.create(playerState, null);
		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged, event);

	}

	@Override
	public void setABRControlParameters(ABRControlParameters abrParams)
			throws IllegalStateException {
		this.abrParams = abrParams;
	}

	/**
	 * Getter for ABR control parameters. This is inaccessible from PSDK media
	 * player.
	 * 
	 */
	public ABRControlParameters getABRControlParameters() {
		return abrParams;
	}

	@Override
	public void setBufferControlParameters(BufferControlParameters params)
			throws IllegalStateException {

	}

	@Override
	public void setCCStyle(TextFormat textFormat) throws IllegalStateException {

	}

	@Override
	public void setCCVisibility(Visibility visibility)
			throws IllegalStateException {

	}

	@Override
	public float getRate() throws IllegalStateException {

		return 0;
	}

	@Override
	public void setRate(float rate) throws IllegalStateException {

	}

	@Override
	public long getLocalTime() throws IllegalStateException {
		return 0;
	}

	@Override
	public void seekToLocal(long adjustedTime) throws IllegalStateException {
		Event event;

		event = SeekEvent.create(MediaPlayerEventType.kEventSeekBegin.name(), adjustedTime, adjustedTime, true);
		eventHandlers.dispatch(MediaPlayerEventType.kEventSeekBegin, event);

		currentTime = adjustedTime;

		event = SeekEvent.create(MediaPlayerEventType.kEventSeekEnd.name(), adjustedTime, adjustedTime, true);
		eventHandlers.dispatch(MediaPlayerEventType.kEventSeekEnd, event);


		if (adjustedTime == playbackRange.getEnd()) {
			playerState = PlayerStatus.COMPLETE;
		}

		event = MediaPlayerStatusChangeEvent.create(playerState, null);
		eventHandlers.dispatch(MediaPlayerEventType.kEventStatusChanged, event);

	}

	@Override
	public void setCustomConfiguration(String config)
			throws IllegalStateException {		
	}

}
