/*
 * ************************************************************************
 *
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2013 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.manager;

import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.List;

import com.adobe.mediacore.*;
import com.adobe.mediacore.metadata.*;
import com.adobe.mediacore.timeline.advertising.*;
import com.adobe.mediacore.timeline.advertising.policy.AdPolicySelector;
import com.adobe.mediacore.timeline.generators.OpportunityGenerator;
import com.adobe.primetime.reference.PrimetimeReference;
import com.adobe.primetime.reference.advertising.*;
import com.adobe.primetime.reference.config.IAdConfig;
import com.adobe.primetime.reference.ui.catalog.VideoItem;

/**
 * 
 * The AdsManager handles the Ad configuration/insertion functionality of the
 * 
 */
public class AdsManagerOn extends AdsManager {

    private IAdConfig adConfig;
    private MediaPlayer mediaPlayer;
    private final String LOG_TAG = PrimetimeReference.LOG_APP_NAME + AdsManagerOn.class.getSimpleName();
    private final ArrayList<AdsManagerEventListener> eventListeners = new ArrayList<AdsManagerEventListener>();

    /**
     * Class constructor. This method begins the AdsManagerOn's initialisation
     * process.
     *
     * @param mediaPlayer the PSDK's implementation of MediaPlayer which the manager
     *                    wraps
     * @param adConfig    which is used to configure advertisement workflow settings for
     *                    the manager
     */
    public AdsManagerOn(IAdConfig adConfig, MediaPlayer mediaPlayer) {
        this.adConfig = adConfig;
        this.mediaPlayer = mediaPlayer;


        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_BREAK_START, adBreakStartedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_BREAK_COMPLETE, adBreakCompletedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_START, adStartedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_COMPLETE, adCompletedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_PROGRESS, adProgressEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_CLICK, adClickEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.AD_BREAK_SKIPPED, adBreakSkippedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.TIMELINE_UPDATED, timelineUpdatedEventListener);
        this.mediaPlayer.addEventListener(MediaPlayerEvent.OPERATION_FAILED, failedAdEventListener);
    }

    /**
     * Destroy manager by removing PSDK listeners
     */
    @Override
    public void destroy() {
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_BREAK_START, adBreakStartedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_BREAK_COMPLETE, adBreakCompletedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_START, adStartedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_COMPLETE, adCompletedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_PROGRESS, adProgressEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_CLICK, adClickEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.AD_BREAK_SKIPPED, adBreakSkippedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.TIMELINE_UPDATED, timelineUpdatedEventListener);
        mediaPlayer.removeEventListener(MediaPlayerEvent.OPERATION_FAILED, failedAdEventListener);
    }

    @Override
    public void addEventListener(AdsManagerEventListener eventListener) {
        eventListeners.add(eventListener);
    }

    @Override
    public void removeEventListener(AdsManagerEventListener eventListener) {
        eventListeners.remove(eventListener);
    }

    /**
     * Get Auditude metadata including server URL, zone ID and media ID from the
     * config
     *
     * @return Auditude metadata from the config
     */
    @Override
    public AdvertisingMetadata getAdvertisingMetadata() {
        return getAdvertisingMetadata(adConfig);
    }

    public static AdvertisingMetadata getAdvertisingMetadata(IAdConfig adConfig) {
        AdvertisingMetadata advertisingMetadata = adConfig.getMetadata();

        // adjust metadata based on application configuration
        if (advertisingMetadata != null) {
            advertisingMetadata.setDelayAdLoading(adConfig.useDelayAdLoading());
            advertisingMetadata.setPreroll(adConfig.hasPreroll());
        }
        return advertisingMetadata;
    }

    /**
     * @{inheritDoc}
     */
    @Override
    public void adjustAdSignalingMode(MediaPlayerItemConfig itemConfig) {
        adjustAdSignalingMode(itemConfig, adConfig, null);
    }

    /**
     * @{inheritDoc}
     */
    @Override
    public void adjustAdSignalingMode(MediaPlayerItemConfig itemConfig, VideoItem videoItem) {
        adjustAdSignalingMode(itemConfig, adConfig, videoItem);
    }

    /**
     * Set the AdSignalingMode from the app's global settings. If the app's AdSignalingMode is DEFAULT,
     * then use the AdSignalingMode configured in the video item if given.
     * @param itemConfig
     * @param adConfig
     * @param videoItem
     */
    public static void adjustAdSignalingMode(MediaPlayerItemConfig itemConfig, IAdConfig adConfig, VideoItem videoItem) {
        if (videoItem == null) {
            itemConfig.setAdSignalingMode(adConfig.getDefaultAdSignalingMode());
        }
        else {
            itemConfig.setAdSignalingMode(adConfig.getDefaultAdSignalingMode() == AdSignalingMode.DEFAULT ? videoItem.getAdSignalingMode() : adConfig.getDefaultAdSignalingMode());
        }
    }

    /**
     * Determine if clickable ad feature is enabled
     *
     * @return true if clickable ad is enabled, false otherwise
     */
    @Override
    public boolean isClickableAdsEnabled() {
        return adConfig.isClickableAdsEnabled();
    }

    /**
     * Notify the media player an clickable ad has been clicked
     */
    @Override
    public void adClick() {
        mediaPlayer.notifyClick();
    }

    /**
     * Register custom ad tags to PSDK config for ad break detection.
     */
    @Override
    public void setCustomTags(MediaPlayerItemConfig itemConfig) {
        setCustomTags(itemConfig, adConfig);
    }

    public static void setCustomTags(MediaPlayerItemConfig itemConfig, IAdConfig adConfig) {
        itemConfig.setAdTags(adConfig.getAdTags());
        itemConfig.setSubscribeTags(adConfig.getAdTags());
    }

    /**
     * Creates an AdvertisingFactory which actually disables the internal PSDK
     * advertising workflow by not providing an opportunity detector or a
     * content resolver.
     *
     * @return a valid instance on AdvertisingFactory.
     */
    private static ContentFactory createDisabledAdvertisingFactory() {
        return new ContentFactory() {

            @Override
            public List<ContentResolver> retrieveResolvers(MediaPlayerItem mediaPlayerItem) {
                List<ContentResolver> resolvers = new ArrayList<ContentResolver>();
                resolvers.add(new DisabledContentResolver());
                return resolvers;
            }

            @Override
            public List<OpportunityGenerator> retrieveGenerators(MediaPlayerItem mediaPlayerItem) {
                List<OpportunityGenerator> generators = new ArrayList<OpportunityGenerator>();
                generators.add(new DisabledOpportunityGenerator());
                return generators;
            }

            @Override
            public AdPolicySelector retrieveAdPolicySelector(MediaPlayerItem mediaPlayerItem) {
                return null;
            }

            @Override
            public List<CustomAdHandler> retrieveCustomAdPlaybackHandlers(MediaPlayerItem mediaPlayerItem) {
                return null;
            }

        };
    }

    /**
     * Register custom ad provider and opportunity detector
     */
    @Override
    public void registerAdClientFactory(MediaPlayerItemConfig itemConfig) {
        registerContentFactory(itemConfig, adConfig);
    }

    public static void registerContentFactory(MediaPlayerItemConfig itemConfig, IAdConfig adConfig)
    {
		ContentFactory contentFactory = null;
		if (!adConfig.isAdvertisingWorkflowEnabled())
		{
			contentFactory = createDisabledAdvertisingFactory();
		}
		// if not default, create a custom factory
        else if (!adConfig.getCustomAdFactoryType().equals(AdsManager.DEFAULT_CUSTOM_AD_CLIENT_FACTORY)) {
			contentFactory = createCustomAdvertisingFactory(itemConfig, adConfig.getCustomAdFactoryType());
		}

		if (contentFactory != null) {
			itemConfig.setAdvertisingFactory(contentFactory);
		}
	}

	/**
	 * @private
	 * 
	 *          Creates a custom AdvertisingFactory which uses Auditude or a
	 *          custom content resolver and a custom opportunity detector for in
	 *          stream cues.
	 * 
	 * @return a valid instance on AdvertisingFactory.
	 */
	private static ContentFactory createCustomAdvertisingFactory(final MediaPlayerItemConfig itemConfig, final String type) {
		return new ContentFactory() {

            @Override
            public List<ContentResolver> retrieveResolvers(MediaPlayerItem mediaPlayerItem) {
                List<ContentResolver> resolvers = new ArrayList<ContentResolver>();
                MediaPlayerItemConfig itemConfig = mediaPlayerItem.getConfig();

                if (itemConfig != null) {
                    AdvertisingMetadata metadata = mediaPlayerItem.getConfig().getAdvertisingMetadata();

                    if (metadata != null) {
                        if (metadata instanceof AuditudeSettings) {
                            resolvers.add(new AuditudeResolver());
                        }

                        if (metadata instanceof CustomAdProviderMetadata) {
                            resolvers.add(new CustomAdProviderContentResolver());
                        }

                    }
                }

                return resolvers;
            }

            @Override
            public List<OpportunityGenerator> retrieveGenerators(MediaPlayerItem mediaPlayerItem) {
                List<OpportunityGenerator> generators = new ArrayList<OpportunityGenerator>();
                if (!type.equals(AdsManager.DEFAULT_CUSTOM_AD_CLIENT_FACTORY)) {
                    generators.add(new CustomOpportunityGenerator());
                }
                return generators;
            }

            @Override
            public AdPolicySelector retrieveAdPolicySelector(MediaPlayerItem mediaPlayerItem) {
                return new CustomAdBreakPolicySelector(mediaPlayerItem);
            }

            @Override
            public List<CustomAdHandler> retrieveCustomAdPlaybackHandlers(MediaPlayerItem mediaPlayerItem) {
                // workaround to always use the default playback handler
                return itemConfig.getDefaultContentFactory().retrieveCustomAdPlaybackHandlers(mediaPlayerItem);
            }

        };
	}

    private TimelineUpdatedEventListener timelineUpdatedEventListener = new TimelineUpdatedEventListener() {
            @Override
            public void onTimelineUpdated(TimelineEvent timelineEvent) {
            for (AdsManagerEventListener listener : eventListeners) {
                listener.onTimelineUpdated(timelineEvent.getTimeline());
            }
        }
    };

    /**
     * AdBreakStartedEventLister that intercepts PSDK Ad break start Events
     */
    private AdBreakStartedEventListener adBreakStartedEventListener = new AdBreakStartedEventListener() {

        /**
         * On ad break starts, notify listeners to handle ad break start event
         */
        @Override
        public void onAdBreakStarted(AdBreakPlaybackEvent event) {
            PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.AdBreakStartedEventListener#onAdBreakStart()",
                    "Ad break start.");
            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdBreakStarted(event.getAdBreak());
            }
        }
    };

    /**
     * AdBreakCompletedEventLister that intercepts PSDK Ad break complete Events
     */
    private AdBreakCompletedEventListener adBreakCompletedEventListener = new AdBreakCompletedEventListener() {

        /**
         * On ad break complete, notify listeners to handle ad break stop event
         */
        @Override
        public void onAdBreakCompleted(AdBreakPlaybackEvent event) {
            PrimetimeReference.logger.i(LOG_TAG + "::MediaPlayer.AdBreakCompletedEventListener#onAdBreakComplete()",
                    "Ad break complete.");
            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdBreakCompleted(event.getAdBreak());
            }
        }

    };

    /**
     * AdStartedEventLister that intercepts PSDK Ad start Events
     */
    private AdStartedEventListener adStartedEventListener = new AdStartedEventListener() {

        /**
         * On ad starts, notify listeners to handle ad start event
         */
        @Override
        public void onAdStarted(AdPlaybackEvent event) {
            AdBreak adBreak = event.getAdBreak();
            Ad ad = event.getAd();
            if (ad == null) {
                PrimetimeReference.logger.e(LOG_TAG + "::MediaPlayer.AdStartedEventListener#onAdStart()",
                        "No ad to start.");
                return;
            }

            PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.AdStartedEventListener#onAdStart()",
                    "Ad playback start: id = " + ad.getId() + " url = "
                            + ad.getPrimaryAsset().getResource().getUrl()
                            + " duration = " + ad.getDuration());

            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdStarted(adBreak, ad);
            }
        }
    };

    /**
     * AdCompletedEventLister that intercepts PSDK Ad complete Events
     */
    private AdCompletedEventListener adCompletedEventListener = new AdCompletedEventListener() {

        /**
         * On ad complete, notify listeners to handle ad stop event
         */
        @Override
        public void onAdCompleted(AdPlaybackEvent event) {
            AdBreak adBreak = event.getAdBreak();
            Ad ad = event.getAd();
            PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.AdCompletedEventListener#onAdComplete()",
                    "Ad playback complete: " + ad.getId());
            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdCompleted(adBreak, ad);
            }
        }

    };

    /**
     * AdProgressEventLister that intercepts PSDK Ad progress Events
     */
    private AdProgressEventListener adProgressEventListener = new AdProgressEventListener() {

        /**
         * On ad progress, notify listeners to handle ad progress event
         */
        @Override
        public void onAdProgress(AdPlaybackEvent event) {
            AdBreak adBreak = event.getAdBreak();
            Ad ad = event.getAd();
            double percentage = event.getProgress();
            PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.AdPlaybackEventListener#onAdProgress()",
                    "Ad playback progress: " + percentage + "% for ad: " + ad.getId());
            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdProgress(adBreak, ad, percentage);
            }
        }

    };

    /**
     * AdClickEventLister that intercepts PSDK Ad click Events
     */
    private AdClickedEventListener adClickEventListener = new AdClickedEventListener() {

        /**
         * On ad click event, notify listeners to handle clickable ad event
         */
        @Override
        public void onAdClicked(AdClickEvent event) {
            Ad ad = event.getAd();
            AdClick adClick = event.getAdClick();
            PrimetimeReference.logger
                    .i(LOG_TAG + "::MediaPlayer.AdPlaybackEventListener#onAdClick()",
                            "AdClick " + adClick.toString() + " for Ad id = " + ad.getId());

            String url = adClick.getUrl();
            if (url == null || url.trim().equals("")) {
                PrimetimeReference.logger.w(LOG_TAG
                                + "::MediaPlayer.AdPlaybackEventListener#onAdClick()", "URL for clickable ads is invalid");
            } else {
                for (AdsManagerEventListener listener : eventListeners) {
                    listener.onAdClick(url);
                }
            }
        }

    };

    private AdBreakSkippedEventListener adBreakSkippedEventListener = new AdBreakSkippedEventListener() {
        @Override
        public void onAdBreakSkipped(AdBreakPlaybackEvent event) {
            PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.AdBreakSkippedEventListener#onAdBreakSkipped()",
                    "Ad break skipped.");
            for (AdsManager.AdsManagerEventListener listener : eventListeners) {
                listener.onAdBreakSkipped(event.getAdBreak());
            }

        }
    };

    private NotificationEventListener failedAdEventListener = new NotificationEventListener() {
        @Override
        public void onNotification(NotificationEvent notificationEvent) {
            if (notificationEvent.getNotification() != null)
            {
                Notification notification = notificationEvent.getNotification();
                if (notification.getCode() == PSDKErrorCode.AD_RESOLVER_FAILED ||
                        notification.getCode() == PSDKErrorCode.AD_MANIFEST_LOAD_FAILED)
                {
                    Notification.Type type = notification.getType();
                    Metadata metadata = notification.getMetadata();

                    StringBuilder sb = new StringBuilder();
                    sb.append("[" + type + "] ");
                    sb.append("Ad resolution failed");
                    sb.append(" (" + notification.getCode() + ")");

                    if (metadata != null)
                    {
                        if (metadata.containsKey("DESCRIPTION"))
                        {
                            sb.append(" - ");
                            sb.append(metadata.getValue("DESCRIPTION"));
                        }

                        if (metadata.containsKey("AD_ID"))
                        {
                            sb.append("; Ad ID = ").append(metadata.getValue("AD_ID"));
                        }

                        if (metadata.containsKey("AD_DURATION"))
                        {
                            sb.append("; Ad Duration = ").append(metadata.getValue("AD_DURATION"));
                        }

                        if (metadata.containsKey("AD_TYPE"))
                        {
                            sb.append("; Ad Type = ").append(metadata.getValue("AD_TYPE"));
                        }

                        if (metadata.containsKey("AD_URL"))
                        {
                            sb.append("; Ad URL = ").append(metadata.getValue("AD_URL"));
                        }

                    }

                    switch (type)
                    {
                        case ERROR:
                            PrimetimeReference.logger.e(LOG_TAG, sb.toString());
                            break;
                        case WARNING:
                            PrimetimeReference.logger.w(LOG_TAG, sb.toString());
                            break;
                        default:
                            PrimetimeReference.logger.i(LOG_TAG, sb.toString());
                            break;
                    }
                }
            }
        }
    };


}
