package com.nexstreaming.nexplayerengine.neximaclient;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.view.ViewGroup;

import com.google.ads.interactivemedia.v3.api.Ad;
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
import com.google.ads.interactivemedia.v3.api.AdError;
import com.google.ads.interactivemedia.v3.api.AdErrorEvent;
import com.google.ads.interactivemedia.v3.api.AdEvent;
import com.google.ads.interactivemedia.v3.api.AdPodInfo;
import com.google.ads.interactivemedia.v3.api.AdsLoader;
import com.google.ads.interactivemedia.v3.api.AdsManager;
import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent;
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
import com.google.ads.interactivemedia.v3.api.AdsRequest;
import com.google.ads.interactivemedia.v3.api.ImaSdkFactory;
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;

import com.nexstreaming.nexplayerengine.NexEventReceiver;
import com.nexstreaming.nexplayerengine.NexLog;
import com.nexstreaming.nexplayerengine.NexPlayer;

import java.util.List;

/**
 * Ads logic for handling the IMA SDK integration code and events.
 */
public class NexIMAWrapper implements AdErrorEvent.AdErrorListener,
        AdsLoader.AdsLoadedListener, AdEvent.AdEventListener{
    private final static String LOG_TAG = "NexVASTController";

    // Factory class for creating Ima-SDK objects.
    private ImaSdkFactory mSdkFactory;

    // The AdsLoader instance exposes the requestAds method.
    private AdsLoader mAdsLoader;

    // AdsManager exposes methods to control ad playback and listen to ad events.
    private AdsManager mAdsManager;

    // IMA ad player
    private NexAdsPlayer mAdsPlayer = null;

    // NexPlayer for content playback
    private NexPlayer mContentPlayer;

    // VAST url to play with content.
    private String mVASTUrl = null;

    // AD Content url
    private String mAdsUrl = null;

    private int mStartTimeMs = 0;
    private AdsMediaEventListener mAdsContentEventListener = null;
    private OnErrorListener mErrorListener = null;

    private AdPointsListener mAdPointListner = null;
    private AdsReadyListener mAdsReadyListener = null;

    private ConnectivityManager mConnectivityManager = null;

    private boolean mIsDestroyed = false;

    private boolean mAllAdsCompleted = false;
    private boolean mContentCompleted = false;
    private boolean mPauseAfterReady = true;
    private boolean mContentPlayerReady = false;

    private int mAdStartTime = 0;

    private boolean mIsSkippable = false;
    private double mSkipTimeOffset = 0.0;

    private boolean mUseSeamlessMode = false;

    private static final Handler mHandler = new Handler();

    /**
     * \brief  This interface can be implemented in an application in order to receive
     *          AD point events from NexIMAWrapper.
     */
    public interface AdPointsListener {
        /**
         * \brief This method indicates when point of ADs is successfully loaded.
         *
         * @param adPoints Array of point of ADs while generating the event.
         */
        void onAdPointsLoaded(int[] adPoints);

        /**
         * \brief This method indicates when AD url and start time are successfully loaded.
         *
         * @param startTime Start time while generating the event.
         * @param adUrl AD url while generating the event.
         */
        void onAdUrlLoaded(int startTime, String adUrl);
    }

    /**
     * \brief  This interface can be implemented in an application in order to receive
     *          ads ready events from NexIMAWrapper.
     */
    public interface AdsReadyListener {
        /**
         * \brief This method indicates when ADs are successfully loaded.
         */
        void onAdsLoaded();

        /**
         * \brief This method indicates when getting errors.
         */
        void onAdsLoadError(AdError error);
    }

    /**
     * \brief  This interface can be implemented in an application in order to receive
     *          ADs media events from NexVastController;.
     */
    public interface AdsMediaEventListener {
        /**
         * \brief This method indicates when playback is successful up to the end of the content.
         *
         * @param mp The NexPlaye&tm; object generating the event.
         */
        void onAllCompletion(NexPlayer mp);

        /**
         * \brief This method indicates when pause command of the content is requested.
         */
        void onContentPauseRequested();

        /**
         * \brief This method indicates when resume command of the content is requested.
         */
        void onContentResumeRequested();

        /**
         * \brief This method indicates when AD is loaded.
         *
         * @param isLinear Determine the AD is linear or not.
         * @param adPosition Position of AD.
         * @param adCountInGroup The number of ADs in AD group.
         * @param adDuration The duration of AD.
         * @param mAdGroupidx The index of AD groups.
         */
        void onAdLoaded(boolean isLinear, int adPosition, int adCountInGroup, double adDuration, int mAdGroupidx);

        /**
         * \brief This method indicates when AD is started.
         */
        void onAdStarted(boolean isSkippable, double skipTimeOffset);

        /**
         * \brief This method indicates when AD is completed.
         */
        void onAdCompleted();
    }

    /**
     * \brief  This interface can be implemented in an application in order to receive
     *          error events from NexIMAWrapper.
     */
    public interface OnErrorListener {
        /**
         * \brief An error has occurred during playback.
         *
         * @param mp The NexPlayer&tm; object generating the event.
         * @param errorCode The error code for the generated error.
         */
        void onError(NexPlayer mp, NexPlayer.NexErrorCode errorCode);
    }

    /**
     * Sole constructor for NexIMAWrapper.
     *
     * After constructing a NexIMAWrapper object, you <i>must</i> call
     * NexIMAWrapper.setContentPlayer before you can call any other methods
     *
     * @param context
     * @param view Container for ad player.
     */
    public NexIMAWrapper(Context context, ViewGroup view) {
        mAdsPlayer = new NexAdsPlayer(context, view, mUseSeamlessMode);
        mConnectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
        createAdsLoader(context);

        mAdsPlayer.setAdsEventListener(new NexAdsPlayer.AdsEventListener() {
            @Override
            public void onAdsUrlLoaded(String adsUrl) {
                NexLog.d(LOG_TAG, "onAdsUrlLoaded : " + adsUrl);
                mAdsUrl= adsUrl;
                if(mAdPointListner != null){
                    mAdPointListner.onAdUrlLoaded(mAdStartTime, mAdsUrl);
                }
            }
            @Override
            public void onAdsPlayed(){
                NexLog.i(LOG_TAG,"onAdsPlayed : isSkippable("+ mIsSkippable + "), Skip Time Offset(" + mSkipTimeOffset + ")");
                if(mAdsContentEventListener != null){
                    mAdsContentEventListener.onAdStarted(mIsSkippable, mSkipTimeOffset);
                }
            }
        });
    }


    /**
     * \brief Set NexPlayer.
     *
     * @param nexplayer
     */
    public void setContentPlayer(NexPlayer nexplayer) {
        mContentPlayer = nexplayer;
        addContentPlayerEventReceiver();
    }

    /**
     * \brief Set VAST url.
     *
     * @param vastUrl url of the ad's VAST XML.
     */
    public void prepareVAST(String vastUrl) {
        init();
        mVASTUrl = vastUrl;
        boolean connectivity = isNetworkConnected(mConnectivityManager);
        NexLog.i(LOG_TAG, "play : Network Connectivity : " + connectivity);
        if ( connectivity ) {
            requestAds(mVASTUrl);
        } else {
            if( mErrorListener != null ) {
                mErrorListener.onError(null, NexPlayer.NexErrorCode.ERROR_NETWORK_PROTOCOL);
            }
        }
    }

    /**
     * \brief Starts contents playback.
     *
     * @param startTime The offset (in milliseconds) from the beginning of the media.
     */
    public void play(int startTime) {
        mStartTimeMs = startTime;
    }

    /**
     * \brief Pauses video playback.
     */
    public void pause() {
        NexLog.i(LOG_TAG, "pause");
        if ( mAdsManager != null) {
            if (mAdsManager != null && mAdsPlayer.isAdDisplayed()) {
                mAdsManager.pause();
            }

            if (mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PLAY && !mAdsPlayer.isAdDisplayed())
                mContentPlayer.pause();
        }
    }

    /**
     * \brief Resumes video playback.
     */
    public void resume() {
        NexLog.i(LOG_TAG, "resume");
        if ( mAdsManager != null) {
            if (mAdsManager != null && mAdsPlayer.isAdDisplayed()) {
                mAdsManager.resume();
            }

            if (mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PAUSE && !mAdsPlayer.isAdDisplayed())
                mContentPlayer.resume();
        }
    }

    /**
     * \brief Release all resources.
     */
    public void release() {
        NexLog.i(LOG_TAG, "release");
        mIsDestroyed = true;
        if (mAdsManager != null) {
            mAdsManager.removeAdErrorListener(this);
            mAdsManager.removeAdEventListener(this);
            mAdsManager.destroy();
            mAdsManager = null;
        }

        if (mAdsPlayer != null) {
            mAdsPlayer.releaseAdsPlayer();
            mAdsPlayer = null;
        }
        mContentPlayer = null;
    }

    /**
     * \brief Return AdsManager for User control.
     *
     * This method can be used after calling
     * NexIMAWrapper.onAdsManagerLoaded
     */
    public AdsManager getAdsManager() {
        return mAdsManager;
    }

    /**
     * \brief An event raised when ads are successfully loaded from the ad server via an AdsLoader.
     *
     * @param adsManagerLoadedEvent Loaded event from AdsManager.
     */
    @Override
    public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
        NexLog.d(LOG_TAG, "onAdsManagerLoaded : mIsDestroyed : " + mIsDestroyed);
        // Ads were successfully loaded, so get the AdsManager instance. AdsManager has
        // events for ad playback and errors.v
        if( !mIsDestroyed ) {
            mAdsManager = adsManagerLoadedEvent.getAdsManager();

            AdsRenderingSettings adsRenderingSettings = mSdkFactory.createAdsRenderingSettings();
            adsRenderingSettings.setEnablePreloading(true);

            // Attach event and error event listeners.
            mAdsManager.addAdErrorListener(this);
            mAdsManager.addAdEventListener(this);
            mAdsManager.init(adsRenderingSettings);

            List<Float> lstCuePoint = mAdsManager.getAdCuePoints();
            if(lstCuePoint.size() > 0) {
                int[] array = new int[lstCuePoint.size()];
                for (int i = 0; i < lstCuePoint.size(); i++) {
                    Float cp = lstCuePoint.get(i);
                    if (cp != null) {
                        cp = cp*1000;
                        array[i] = cp.intValue();

                        NexLog.d(LOG_TAG, "Cue Point[" + i + "] start:" + array[i]);
                    }
                }

                if( mAdPointListner != null )
                    mAdPointListner.onAdPointsLoaded(array);

            } else {
                NexLog.d(LOG_TAG, "Cue Point: Nothing ");
            }

            if (mAdsReadyListener != null) {
                mAdsReadyListener.onAdsLoaded();
            }
        }
    }

    /**
     * \brief Responds to AdEvents.
     *
     * @param adEvent Possible event of AdEvent class.
     */
    @Override
    public void onAdEvent(AdEvent adEvent) {
        NexLog.i(LOG_TAG, "AdEvent: " + adEvent.getType());
        // These are the suggested event types to handle. For full list of all ad event types,
        // see the documentation for AdEvent.AdEventType.
        Ad ad = adEvent.getAd();
        switch (adEvent.getType()) {
            case LOADED:
                // AdEventType.LOADED will be fired when ads are ready to be played.
                // AdsManager.start() begins ad playback. This method is ignored for VMAP or ad
                // rules playlists, as the SDK will automatically start executing the playlist.
                mAdsManager.start();
                break;
            case STARTED:
                AdPodInfo adPodInfo = ad.getAdPodInfo();
                int podIndex = adPodInfo.getPodIndex();
                int adGroupIndex = podIndex;
                int adPosition = adPodInfo.getAdPosition();
                int adCountInAdGroup = adPodInfo.getTotalAds();
                double adDuration = ad.getDuration();
                boolean isLinear = ad.isLinear();
                mIsSkippable = ad.isSkippable();
                mSkipTimeOffset = ad.getSkipTimeOffset();

                NexLog.d(LOG_TAG, "STARTED ad " + adPosition + " of " + adCountInAdGroup + " in ad group " + adGroupIndex);

                if(mAdsPlayer != null)
                    mAdsPlayer.setAdPodInfo(adPosition, adCountInAdGroup);

                if(mAdsContentEventListener != null)
                    mAdsContentEventListener.onAdLoaded(isLinear,adPosition,adCountInAdGroup,adDuration,adGroupIndex);
                break;
            case COMPLETED:
                if(mAdsContentEventListener != null)
                    mAdsContentEventListener.onAdCompleted();
                break;
            case CONTENT_PAUSE_REQUESTED:
                // AdEventType.CONTENT_PAUSE_REQUESTED is fired immediately before a video ad is played.
                // You should save content's current position and stop playing your content.
                if (mContentPlayer != null && !mUseSeamlessMode) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                        if(mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PLAY
                                || mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PAUSE ) {
                            mContentPlayer.pause();
                        } else if(mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_CLOSED) {
                            mPauseAfterReady = true;
                        } else if(mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_STOP) {
                            //stop - to prevent before this event content play was called.
                            mPauseAfterReady = true;
                        }
                        }
                    });
                }
                if(mAdsContentEventListener != null)
                    mAdsContentEventListener.onContentPauseRequested();
                break;
            case CONTENT_RESUME_REQUESTED:
                // AdEventType.CONTENT_RESUME_REQUESTED is fired when the ad is completed.
                // You should start playing your content.
                if (mContentPlayer != null && !mUseSeamlessMode) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                        if(!mContentCompleted && mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PAUSE
                                || mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PLAY ) {
                            mContentPlayer.resume();
                        } else if(mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_CLOSED) {
                            mPauseAfterReady = false;
                        } else {
                            mPauseAfterReady = false;
                        }
                        }
                    });
                }
                if(mAdsContentEventListener != null)
                    mAdsContentEventListener.onContentResumeRequested();
                break;
            case ALL_ADS_COMPLETED:
                // AdEventType.ALL_ADS_COMPLETED is fired when all ads managed by the current ads
                // manager have finished playing.
                if (mAdsManager != null) {
                    mAdsManager.destroy();
                    mAdsManager = null;
                }

                mAllAdsCompleted = true;
                if (mContentCompleted && mAdsContentEventListener != null)
                    mAdsContentEventListener.onAllCompletion(mContentPlayer);

                if (mContentCompleted)
                    mContentPlayer.stop();
                break;
            default:
                break;
        }
    }

    /**
     * \brief An event raised when there is an error loading or playing ads.
     *
     * @param adErrorEvent Possible error event of AdErrorEvent class.
     */
    @Override
    public void onAdError(AdErrorEvent adErrorEvent) {
        NexLog.e(LOG_TAG, "Ad Error: " + adErrorEvent.getError().getMessage());
        if( !mIsDestroyed ) {
            if(mAdsManager != null) {
                mAdsManager.destroy();
                mAdsManager = null;
            }

            if (mContentPlayer != null && mContentPlayer.getState() == NexPlayer.NEXPLAYER_STATE_PAUSE)
                mContentPlayer.resume();

            if (mAdsReadyListener != null) {
                mAdsReadyListener.onAdsLoadError(adErrorEvent.getError());
            }
        }
    }

    /**
     * \brief Set content error listener.
     *
     * @param listener NexVideoView.OnErrorListener.
     */
    public void setNexPlayerErrorListener(OnErrorListener listener) {
        mErrorListener = listener;
    }

    /**
     * \brief Set Content and Ad complete listener.
     *
     * @param listener NexVideoView.OnCompletionListener.
     */
    public void setAdsContentEventListener(AdsMediaEventListener listener) { mAdsContentEventListener = listener; }

    /**
     * \brief Set point of playing Ad.
     *
     * @param listener NexVideoView.OnCompletionListener.
     */
    public void setAdPointsListener(AdPointsListener listener) {
        mAdPointListner = listener;
    }

    /**
     * \brief Set Content and Ad complete listener.
     *
     * @param listener NexVideoView.OnCompletionListener.
     */
    public void setAdsReadyListener(AdsReadyListener listener) {
        mAdsReadyListener = listener;
    }

    protected void addContentPlayerEventReceiver() {
        NexEventReceiver eventReceiver = new NexEventReceiver() {
            @Override
            public void onEndOfContent(NexPlayer mp) {
                NexLog.i(LOG_TAG, "Content was completed.");
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // Signals to the SDK that the content is finished. This will allow the SDK to play post-roll ads, if any are loaded via ad rules.
                        if( mAdsLoader != null)
                            mAdsLoader.contentComplete();
                    }
                });

                mContentCompleted = true;

                if (mAllAdsCompleted)
                    mContentPlayer.stop();

                if(mAllAdsCompleted && mAdsContentEventListener != null)
                    mAdsContentEventListener.onAllCompletion(mp);
            }

            @Override
            public void onError(NexPlayer mp, NexPlayer.NexErrorCode errorCode) {
                NexLog.e(LOG_TAG, "onContentError");
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // Signals to the SDK that the content is finished. This will allow the SDK to play post-roll ads, if any are loaded via ad rules.
                        mAdsLoader.contentComplete();
                    }
                });

                if( mErrorListener != null )
                    mErrorListener.onError(mp, errorCode);
            }

            @Override
            public void onAsyncCmdComplete(NexPlayer mp, int command, int result, int param1, int param2) {
                NexLog.i(LOG_TAG, "onAsyncCmdComplete:" + command);
                if (!mUseSeamlessMode) {
                    switch (command) {
                        case NexPlayer.NEXPLAYER_ASYNC_CMD_OPEN_LOCAL:
                        case NexPlayer.NEXPLAYER_ASYNC_CMD_OPEN_STREAMING:
                            if (result == 0) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        NexLog.i(LOG_TAG, "content player start call, PauseAfterReady:" + mPauseAfterReady);
                                        mContentPlayer.start(mStartTimeMs, mPauseAfterReady);
                                    }
                                });
                            } else {
                                onError(mp, NexPlayer.NexErrorCode.fromIntegerValue(result));
                            }
                            break;
                        case NexPlayer.NEXPLAYER_ASYNC_CMD_START_LOCAL:
                        case NexPlayer.NEXPLAYER_ASYNC_CMD_START_STREAMING:
                            if (result == 0) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (mPauseAfterReady) {
                                            NexLog.i(LOG_TAG, "content player need to pause, PauseAfterReady:" + mPauseAfterReady);
                                            mContentPlayer.pause();
                                        }
                                    }
                                });
                            }
                            break;

                    }
                }
            }

            @Override
            public void onVideoRenderPrepared(NexPlayer mp) {
                mContentPlayerReady = true;
                if(mAdsPlayer != null) {
                    if (!mUseSeamlessMode)
                        mAdsPlayer.setContentPlayerState(mContentPlayerReady);
                }
            }
        };

        mContentPlayer.addEventReceiver(eventReceiver);
    }

    //Create an AdsLoader.
    private void createAdsLoader(Context context) {
        mSdkFactory = ImaSdkFactory.getInstance();
        mAdsLoader = mSdkFactory.createAdsLoader(context);
        mAdsLoader.addAdErrorListener(this);
        mAdsLoader.addAdsLoadedListener(this);
    }

    //Request video ads from the given VAST ad tag url. This method is called when Application start to play.
    private void requestAds(String vastUrl) {
        // Container with references to VideoAdPlayer and ad UI ViewGroup (NexVideoView).
        AdDisplayContainer adDisplayContainer = mSdkFactory.createAdDisplayContainer();
        adDisplayContainer.setPlayer(mAdsPlayer.getVideoAdPlayer());
        adDisplayContainer.setAdContainer(mAdsPlayer.getVideoViewContainer());

        // Create the ads request.
        AdsRequest request = mSdkFactory.createAdsRequest();
        request.setAdTagUrl(vastUrl);
        request.setAdDisplayContainer(adDisplayContainer);
        request.setContentProgressProvider(new ContentProgressProvider() {
            @Override
            public VideoProgressUpdate getContentProgress() {
            if (mContentPlayer != null) {
                if (getContentDuration() <= 0) {
                    return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
                }
                return new VideoProgressUpdate(mContentPlayer.getCurrentPosition(),
                        getContentDuration());
            } else {
                return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
            }
            }
        });

        // Request the ad. After the ad is loaded, onAdsManagerLoaded() will be called.
        mAdsLoader.requestAds(request);
    }

    private void init() {
        mIsDestroyed = false;
        mAllAdsCompleted = false;
        mContentCompleted = false;
        mPauseAfterReady = true;
        mStartTimeMs = 0;
    }

    private int getContentDuration() {
        int ret = 0;
        if( mContentPlayer != null && mContentPlayer.isInitialized() && mContentPlayer.getState() >= NexPlayer.NEXPLAYER_STATE_STOP ) {
            ret = mContentPlayer.getContentInfoInt(NexPlayer.CONTENT_INFO_INDEX_MEDIA_DURATION);
            if( ret < 0 ) {
                long[] seekableRange = mContentPlayer.getSeekableRangeInfo();
                long startTime = 0;
                long endTime = 0;

                if( seekableRange != null ) {
                    startTime = seekableRange[0];
                    endTime = seekableRange[1];
                }
                ret = (int)(endTime - startTime);
            }
        }

        return ret;
    }

    private boolean isNetworkConnected(ConnectivityManager connectivity) {
        boolean isConnected = false;
        if (connectivity != null) {
            NetworkInfo networkInfo = connectivity.getActiveNetworkInfo();
            if (networkInfo != null) {
                boolean isMobileAvailable = connectivity.getActiveNetworkInfo().isConnected();
                boolean isWifiAvailable = connectivity.getActiveNetworkInfo().isConnected();
                isConnected = isMobileAvailable || isWifiAvailable;
            }
        }
        return isConnected;
    }
}
