/*
 * ************************************************************************
 *
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2015 Adobe Systems Incorporated
 * All Rights Reserved.
 * NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
 * terms of the Adobe license agreement accompanying it.  If you have received this file from a
 * source other than Adobe, then your use, modification, or distribution of it requires the prior
 * written permission of Adobe.
 *
 **************************************************************************
 */

package com.adobe.primetime.reference.advertising;

import com.adobe.mediacore.MediaPlayerItem;
import com.adobe.mediacore.MediaPlayerItemConfig;
import com.adobe.mediacore.metadata.Metadata;
import com.adobe.mediacore.metadata.AdSignalingMode;
import com.adobe.mediacore.timeline.Opportunity;
import com.adobe.mediacore.timeline.Placement;
import com.adobe.mediacore.metadata.TimedMetadata;
import com.adobe.mediacore.metadata.AdvertisingMetadata;
import com.adobe.mediacore.timeline.generators.OpportunityGenerator;
import com.adobe.mediacore.timeline.generators.OpportunityGeneratorClient;
import com.adobe.mediacore.utils.TimeRange;
import com.adobe.mediacore.utils.NumberUtils;
import com.adobe.primetime.reference.PrimetimeReference;

import android.content.Context;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.Collections;

public class SpliceInOpportunityGenerator implements OpportunityGenerator {
	
	private static final String OPPORTUNITY_DURATION_KEY ="DURATION";
    private static final String OPPORTUNITY_ID_KEY = "ID";
    private static final String OPPORTUNITY_TYPE_KEY = "TYPE";
    private static final String OPPORTUNITY_TIME_KEY = "TIME";
    private static final String[] OPPORTUNITY_ELAPSED_TIME_KEY = {"ELAPSEDTIME", "ELAPSED"};
    private static final String SPLICE_IN_OPPORTUNITY_TYPE_VALUE = "SpliceIn";
    private static final String OPPORTUNITY_TYPE_VALUE = "SpliceOut";
    private static final String SPLICE_OUT_MARKER = "#EXT-X-CUE-OUT";
    private static final String SPLICE_IN_MARKER = "#EXT-X-CUE-IN";
    private static final String CUE_MARKER = "#EXT-X-CUE";
	private static final String CUE_CONT_MARKER = "#EXT-X-CUE-OUT-CONT";
	
	/*	
	private Stack<Opportunity> _opportunities = new Stack<Opportunity>();
    private Stack<Opportunity> _deletions = new Stack<Opportunity>();*/
	
	private MediaPlayerItem _item;
	private OpportunityGeneratorClient _client;
	private AdvertisingMetadata _advertisingMetadata;
	private AdSignalingMode _adSignalingMode;
	private List<TimedMetadata> _skippedTimedMetadatas = new ArrayList();
    private List<TimedMetadata> _processedTimedMetadatas = new ArrayList();
	
	private static String[] _adTags;
	private static String _id;
	private static String _type;
	private static long _duration;
	private static long _time;
	private static long _timeOfSpliceOut;
	private static long _playbackTime;
	private static long _processingTime;

	public void configure(MediaPlayerItem item, Context context, 
			OpportunityGeneratorClient client, AdSignalingMode mode, long playhead, TimeRange playbackRange) {
		if (item == null || client == null)
			return;
		MediaPlayerItemConfig itemConfig = item.getConfig();
		this._adTags = itemConfig.getAdTags();
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "configure() _adTags = ");
		for (String adtag : this._adTags)
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "adtag = " + adtag);
		this._item = item;
		this._client = client;
		this._adSignalingMode = mode;
		
		if (_adSignalingMode != AdSignalingMode.MANIFEST_CUES)
			return;
		MediaPlayerItemConfig config = item.getConfig();
		if (config==null)
			return;
		_advertisingMetadata = config.getAdvertisingMetadata();
		
		this._processingTime = playhead;
		this._playbackTime = playhead;
		
		List<TimedMetadata> timedMetadataList = _item.getTimedMetadata();
		processMetadata(timedMetadataList);
	}
	
    public void cleanup() {
		_adTags = null;
		_item = null;
		_playbackTime = -1;
		_processingTime = -1;
		_skippedTimedMetadatas  = new ArrayList<TimedMetadata>();
		_processedTimedMetadatas  = new ArrayList<TimedMetadata>();
    }
	
	private void processMetadata(List<TimedMetadata> timedMetadataList) {

		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "processMetadata() called.");
		for(TimedMetadata timedMetadata : timedMetadataList) {
			if (!isAlreadyProcessed(timedMetadata)) {
				if (isSkippable(timedMetadata)) {
					_skippedTimedMetadatas.add(timedMetadata);
				} else {
					_processedTimedMetadatas.add(timedMetadata);
					if (_processingTime < timedMetadata.getTime())
						_processingTime = timedMetadata.getTime();

					if (isPlacementOpportunity(timedMetadata)) {
						Opportunity opportunity = createPlacementOpportunity(timedMetadata, _advertisingMetadata);
						if (opportunity != null) {
							_client.resolve(opportunity);
						}
					}
				}
			}
		}
		Collections.sort(_skippedTimedMetadatas);
		Collections.sort(_processedTimedMetadatas);
	}

	public boolean isPlacementOpportunity(TimedMetadata timedMetadata)
	{
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "inside isPlacementOpportunity.");
		
		String tagName = timedMetadata.getName();
		if (!isTagSupported(tagName)) {
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity returning False for tagName = " + tagName);
			return false;
		}

		Metadata tagMetadata = timedMetadata.getMetadata();
		if (tagMetadata == null) {
			return false;
		}

		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity tagName = " + tagName);

		if ((tagName.equals(CUE_MARKER) && isSpliceOpportunity(timedMetadata, OPPORTUNITY_TYPE_VALUE))
			|| tagName.equals(SPLICE_OUT_MARKER)) {
			
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity Got a Splice Out TimedMetadata.");
			
			Metadata metadata = timedMetadata.getMetadata();
			long duration = -1;
			if (metadata.containsKey(OPPORTUNITY_DURATION_KEY)) {
				duration = NumberUtils.parseNumber(metadata.getValue(OPPORTUNITY_DURATION_KEY), 0) * 1000;
			}

			String id = null;
			if (metadata.containsKey(OPPORTUNITY_ID_KEY)) {
				id = metadata.getValue(OPPORTUNITY_ID_KEY);
			}

			long time = -1;
			if (metadata.containsKey(OPPORTUNITY_TIME_KEY)) {
				time = NumberUtils.parseNumber(metadata.getValue(OPPORTUNITY_TIME_KEY), 0) * 1000;
			}

			if (id == null || duration == -1)
				return false;

			_id = id;
			_duration = duration;
			_time = timedMetadata.getTime();
			_timeOfSpliceOut = time;
            
            PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity returning False");
			return false;
		}
		else if ((tagName.equals(CUE_MARKER) && isSpliceOpportunity(timedMetadata, SPLICE_IN_OPPORTUNITY_TYPE_VALUE))
			|| tagName.equals(SPLICE_IN_MARKER)) {
			
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity Got a Splice In TimedMetadata.");
			
			Metadata metadata = timedMetadata.getMetadata();
			String id = null;
			if (metadata.containsKey(OPPORTUNITY_ID_KEY))
				id = metadata.getValue(OPPORTUNITY_ID_KEY);

			long time = -1;
			if (metadata.containsKey(OPPORTUNITY_TIME_KEY))
				time = NumberUtils.parseNumber(metadata.getValue(OPPORTUNITY_TIME_KEY), 0) * 1000;

			if (id == null || (!id.equals(_id)))
			{
				PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity returning False because id = null");
				return false;
			}
            
            PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity returning True");
			return true;
		}
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isPlacementOpportunity returning False");
		return false;
	}
	
	public Opportunity createPlacementOpportunity(TimedMetadata timedMetadata, AdvertisingMetadata advertisingMetadata) {
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "createPlacementOpportunity\n");
		long placementTime = timedMetadata.getTime();
		long placementDuration = 0;
		Placement.Type placementType = placementTime <= 0 ? Placement.Type.PRE_ROLL : Placement.Type.MID_ROLL;
		Placement.Mode placementMode = Placement.Mode.SPLICEIN;
			
		Metadata tagMetadata = timedMetadata.getMetadata();

		long time = -1;
		if (tagMetadata.containsKey(OPPORTUNITY_TIME_KEY))
			time = NumberUtils.parseNumber(tagMetadata.getValue(OPPORTUNITY_TIME_KEY), 0) * 1000;

		long spliceOutTime = _timeOfSpliceOut;
		long spliceOutDuration = _duration;
		placementDuration = spliceOutDuration - (time - spliceOutTime);	

		String id = null;
		if (tagMetadata.containsKey(OPPORTUNITY_ID_KEY))
			id = tagMetadata.getValue(OPPORTUNITY_ID_KEY);

		if (id == null)
			return null;

		if (placementDuration > 0) {
			Placement placement = new Placement(placementType, placementTime, placementDuration, placementMode);
			return new Opportunity(id, placement, advertisingMetadata, null);
		}
		
		return null;
	}

	private boolean isAlreadyProcessed(TimedMetadata timedMetadata) {
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isAlreadyProcessed() called\n");
		for (TimedMetadata tmp : _processedTimedMetadatas)
			if (tmp.equals(timedMetadata))
				return true;
		return false;
	}
	
	private boolean isSkippable(TimedMetadata timedMetadata) {
		return (timedMetadata.getTime() < _processingTime);
	}

	private boolean isTagSupported(String tagName)
	{
		if (_adTags.length == 0) {
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isTagSupported returning False because _adTags is empty");
			return false;
		}

		for (String tag : _adTags) {
			if (tag.contains(tagName)) {
				PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isTagSupported returning True");
				return true;
			}
		}

		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isTagSupported returning False");
		return false;
	}
	
	private boolean isSpliceOpportunity(TimedMetadata timedMetadata, String expectedType)
	{
		Metadata metadata = timedMetadata.getMetadata();
		if (metadata == null) {
			PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "isSpliceOpportunity Unable to retrieve the metadata.\n");
			return false;
		}

		if (metadata.containsKey(OPPORTUNITY_TYPE_KEY))
		{
			String type = metadata.getValue(OPPORTUNITY_TYPE_KEY);
			if (type.toLowerCase().equals(expectedType.toLowerCase())) {
				return true;
			}
		}
		return false;
	}

	private void removeObsoleteMetadata(List<TimedMetadata> timedMetadataList, TimeRange range) {    
		int removeCount = 0;
		for (int i = 0; i < timedMetadataList.size();) {
			long time = timedMetadataList.get(i).getTime();
			if (time >= range.getBegin()) {
				return;
			}
			removeCount++;
			timedMetadataList.remove(i);			
		}
	}
	
	private List<TimedMetadata> retrieveNewTimedMetadata(MediaPlayerItem item) {
		List<TimedMetadata> newTimedMetadata = new ArrayList();

		List<TimedMetadata> timedMetadataList = item.getTimedMetadata();

		int length = timedMetadataList.size();
		if (length > 0)	{
			for (int i = length - 1; i >= 0; i--) {
				TimedMetadata timedMetadata = timedMetadataList.get(i);
				if (timedMetadata.getTime() >= _processingTime && !isAlreadyProcessed(timedMetadata)) {
						newTimedMetadata.add(timedMetadata);
				} else {
					break;
				}
			}
		}

		if (newTimedMetadata.size() > 0)
			return newTimedMetadata;

		return null;
	}

	public void update(long playhead, TimeRange playbackRange) {
		
		PrimetimeReference.logger.i("SpliceInOpportunityGenerator", "update() called.");
		if (_adSignalingMode != AdSignalingMode.MANIFEST_CUES)
			return;
		
		removeObsoleteMetadata(_skippedTimedMetadatas, playbackRange);
		removeObsoleteMetadata(_processedTimedMetadatas, playbackRange);
	
		List<TimedMetadata> availableMetadata = new ArrayList();
		if (playhead < _playbackTime) {
			availableMetadata.addAll(_skippedTimedMetadatas);
			_skippedTimedMetadatas.clear();
			_processingTime = playhead;
		}
		
		List<TimedMetadata> newTimedMetadata = retrieveNewTimedMetadata(_item);
		if (newTimedMetadata != null) {
			availableMetadata.addAll(newTimedMetadata);
		}

		if (availableMetadata.size() > 0) {
			Collections.sort(availableMetadata);

			_playbackTime = playhead;
			processMetadata(availableMetadata);
		}
    }
}
