/*******************************************************************************
 * ADOBE CONFIDENTIAL
 *  ___________________
 *
 *  Copyright 2015 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;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import com.adobe.primetime.reference.PrimetimeReference;
import com.adobe.primetime.reference.logging.Logger;
import com.adobe.primetime.reference.ui.catalog.VideoItem;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

public class InstantOnManager {

    public static final String SETTINGS_ENABLED = "settings_instant_on_enabled";
    public static final String SETTINGS_MAX_LOAD = "settings_instant_on_max_load";
    public static final String SETTINGS_CONCURRENT_LOAD = "settings_instant_on_concurrent_load";

    public static final boolean DEFAULT_ENABLED = false;
    public static final int DEFAULT_MAX_LOAD = 5;
    public static final int DEFAULT_CONCURRENT_LOAD = 10;


    private static final Map<String, InstantOnItem> items = new HashMap<String, InstantOnItem>();
    private static Context context;
    private static final ConcurrentLinkedQueue<String> preloadQueue = new ConcurrentLinkedQueue<String>();
    private static final ConcurrentLinkedQueue<String> processQueue = new ConcurrentLinkedQueue<String>();


        /**
         * Set the application context. Do this first before calling any other methods in this class.
         * @param value the context for this application
         */
        public static void setContext(Context value)
        {
            context = value;
        }

        /**
         * Adds video information to this manager, which in turn creates a new instance of an InstantOn item.
         * If the video information is already added to this manager, as determined by its id, then it is ignored.
         * If the application context isn't set, than an IllegalStateException is thrown.
         * @param videoInfo
         */
        public static synchronized boolean add(VideoItem videoInfo) {
            checkContext();

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            boolean enabled = prefs.getBoolean(SETTINGS_ENABLED, DEFAULT_ENABLED);
            int maxLoad = Integer.parseInt(prefs.getString(SETTINGS_MAX_LOAD, String.valueOf(DEFAULT_MAX_LOAD)));

            // only add item if enabled and under max item load
            if (!enabled || items.size() >= maxLoad) return false;

            if (!items.containsKey(videoInfo.getId()))
            {
                InstantOnItem item = new InstantOnItem(context, videoInfo);
                item.addEventListener(bufferingCompleteEventListener);
                items.put(videoInfo.getId(), item);

                preloadQueue.offer(videoInfo.getId());
                beginPreload();
                return true;
            }
            return false;
        }

        public static void beginPreload() {

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            int concurrentLoad = Integer.parseInt(prefs.getString(SETTINGS_CONCURRENT_LOAD, String.valueOf(DEFAULT_CONCURRENT_LOAD)));

            while (!preloadQueue.isEmpty() && processQueue.size() < concurrentLoad)
            {
                String id = preloadQueue.poll();
                if (items.containsKey(id))
                {
                    items.get(id).prepareBuffer();
                    processQueue.offer(id);
                }
            }

        }

        /**
         * Remove an InstantOn item from this manager.
         * @param id the id of the video item
         * @return the InstantOnItem or null if no item exists for the given id
         */
        public static synchronized InstantOnItem remove(String id) {
            removeEventListener(id);
            preloadQueue.remove(id);
            return items.remove(id);
        }

        public static synchronized void clear() {
            allBufferingCompleteListenerList.clear();
            itemListeners.clear();

            preloadQueue.clear();
            processQueue.clear();

            for (InstantOnItem item : items.values())
            {
                item.destroy();
            }

            items.clear();
        }

        public static synchronized boolean contains(String id) {
            return items.containsKey(id);
        }

        public static synchronized boolean isBufferingComplete(String id) {
            return items.containsKey(id) && items.get(id).isBufferComplete();
        }

        private static void checkContext() {
            if (context == null) {
                throw new IllegalStateException("InstantOnManager does not contain a valid Context.");
            }
        }

        private static InstantOnItem.InstantOnBufferingCompleteEventListener bufferingCompleteEventListener = new InstantOnItem.InstantOnBufferingCompleteEventListener() {
            @Override
            public void onEvent(String id) {

                processQueue.remove(id);
                beginPreload();

                if (itemListeners.containsKey(id))
                {
                    itemListeners.get(id).onComplete(id);
                }

                if (allItemsBuffered())
                {
                    for (InstantOnAllBufferingCompleteListener listener : allBufferingCompleteListenerList)
                    {
                        listener.onComplete();
                    }
                }
            }
        };

        private static Map<String, InstantOnItemBufferingCompleteListener> itemListeners = new HashMap<String, InstantOnItemBufferingCompleteListener>();
        private static List<InstantOnAllBufferingCompleteListener> allBufferingCompleteListenerList = new ArrayList<InstantOnAllBufferingCompleteListener>();

        public static synchronized void addEventListener(String id, InstantOnItemBufferingCompleteListener listener) {
            if (!itemListeners.containsKey(id))
            {
                itemListeners.put(id, listener);

                // if the listener is added after the item buffers, dispatch the event immediately
                if (items.containsKey(id) && items.get(id).isBufferComplete())
                {
                    listener.onComplete(id);
                }
            }
        }

        public static synchronized void removeEventListener(String id) {
            itemListeners.remove(id);
        }

        public static void addEventListener(InstantOnAllBufferingCompleteListener listener) {
            if (!allBufferingCompleteListenerList.contains(listener))
            {
                allBufferingCompleteListenerList.add(listener);
            }
        }

        public static void removeEventListener(InstantOnAllBufferingCompleteListener listener) {
            allBufferingCompleteListenerList.remove(listener);
        }


        public interface InstantOnItemBufferingCompleteListener {
            void onComplete(String id);
        }

        public interface InstantOnAllBufferingCompleteListener {
            void onComplete();
        }

        public enum EventType {
            ITEM_BUFFER_COMPLETE, ALL_ITEMS_BUFFER_COMPLETE
        }

        private static boolean allItemsBuffered()
        {
            if (items.isEmpty()) {
                return false;
            }

            boolean isDone = true;
            for (InstantOnItem item : items.values())
            {
                if (!item.isBufferComplete()) {
                    isDone = false;
                }
            }

            return isDone;
        }


        public static void printDebug(Logger logger)
        {

            String tag = "InstantOnManager";
            logger.e(tag, "InstantOnManager items: ");
            for (InstantOnItem item : items.values())
            {
                logger.e(tag, "\t" + item.toString());
            }
        }
    }
