/// <reference path="../ScriptReference/jquery-1.7.1-vsdoc.js"/>
/// <reference path="Mediasite.Core.js"/>
/// <reference path="Mediasite.Player.Core.js"/>
/// <reference path="Mediasite.Player.Controller.js"/>
/// <reference path="Mediasite.MediaPlayer.Model.js"/>
/// <reference path="Mediasite.Player.Model.js"/>


Mediasite.namespace("Player");
Mediasite.Player.API = (function () {
    var module = new (function MediasitePlayerPresentationAPI() { })();
    var self = module;

    var _eventBundle = new Mediasite.Player.EventBundle();
    var _mediaPlayerModel, _playerModel, _playerView, _pastSessionPlayedSegments;
    var _idGenerator = new UniqueTemporaryIdGenerator(0);

    var _playerState = {
        ready: false,
        playstate: 'undefined' // TODO: access properties directly from model
    };

    // #region Initialization
    module._registerModels = function (mediaPlayerModel, playerModel, playerView, pastSessionPlayedSegments) {
        // Should only be called from Mediasite.Player.Controller
        if (_mediaPlayerModel && _playerModel && _playerView) return; // already initialized?
        if (!(mediaPlayerModel && playerModel && playerView)) return; // sanity check params

        _mediaPlayerModel = mediaPlayerModel;
        _playerModel = playerModel;
        _playerView = playerView;
        _pastSessionPlayedSegments = cloneSegmentsArray(pastSessionPlayedSegments);

        addPassthroughEventHandlers();
    };

    function cloneSegmentsArray(input) {
        var i;
        var output = [];

        if (input && input.length > 0) {
            for (i = 0; i < input.length; i += 1) {
                output[i] = { start: input[i].start, end: input[i].end };
            }
        }

        return output;
    };

    function addPassthroughEventHandlers() {
        // Subscribe to presentation events in order to fire events for API subscribers
        _mediaPlayerModel.AddEventHandler({
            MediaError: function (error) {
                fireErrorEvent(Mediasite.Player.Error.Code.MediaNotSupported);
            },
            MediaLoaded: function (mediaInfo) {
                if (!_playerState.ready) {
                    _playerState.ready = true;
                    _eventBundle.fire("ready");
                } else {
                    var durationSeconds = transformTime(mediaInfo.Duration);
                    _eventBundle.fire("durationchanged", { duration: durationSeconds });
                }
            },
            StateChanged: function (state) {
                if (_playerState.ready) {
                    var time = module.getCurrentTime();
                    _eventBundle.fire("currenttimechanged", { currentTime: time });
                }
                _playerState.playState = Mediasite.Player.MediaState.GetStringValue(state);
                _eventBundle.fire("playstatechanged", { playState: _playerState.playState });
            },
            VolumeChanged: function (volume) {
                var isMuted = _mediaPlayerModel.IsMuted() == true;
                if (isMuted) {
                    volume = Math.round(_mediaPlayerModel.GetVolume());
                }

                _eventBundle.fire("volumechanged", { volume: volume, isMuted: isMuted });
            },
            PlaybackRateChanged: function (rateInfo) {
                var currentRate = rateInfo.Current;
                _eventBundle.fire("playbackratechanged", { currentRate: currentRate });
            },
            PositionChanged: function (position) {
                var time = transformTime(position);
                _eventBundle.fire("currenttimechanged", { currentTime: time });
            }
        });

        _playerModel.AddEventHandler({
            SlideChange: function (slide) {
                var slideApiModel = transformSlide(slide, true);
                _eventBundle.fire("slidechanged", slideApiModel);
            },
            CaptionChange: function (captions) {
                var i;
                var apiCaptions = [];
                for (i = 0; i < captions.length; i += 1) {
                    apiCaptions.push(transformCaption(captions[i]));
                }

                _eventBundle.fire("captionchanged", { captions: apiCaptions });
            },
            ChapterChange: function (chapter) {
                var chapterApiModel = transformChapter(chapter, true);
                _eventBundle.fire("chapterchanged", chapterApiModel);
            },
            TimedEventReached: function (timedEvent) {
                var timedEventApiModel = transformTimedEvent(timedEvent, true);
                _eventBundle.fire("timedeventreached", timedEventApiModel);
            },
            TimedEventListChanged: function () {
                _eventBundle.fire("timedeventlistchanged");
            },
            AddNewSlide: function (slide) {
                var slideApiModel = transformSlide(slide, true);
                _eventBundle.fire("slideadded", slideApiModel);
            },
            LiveStatusChanged: function (presentationStatus) {
                _eventBundle.fire("livestatuschanged", { liveStatus: presentationStatus });
            },
            PlayerStateChanged: function (playerState) {
                _eventBundle.fire("playerstatechanged", transformPlayerState(playerState));
            },
            VisibleStreamsChanged: function (streamTypes) {
                // Copy array so that API consumers don't receive a reference to the internal array.
                var streamTypeArray = streamTypes.slice(0);
                _eventBundle.fire("visiblestreamschanged", { streamTypes: streamTypeArray });
            },
            MediaError: function (errorMessage) {
                fireErrorEvent(Mediasite.Player.Error.Code.MediaError, errorMessage);
            }
        });
    }
    // #endregion

    // #region Private utility functions 
    function transformTime(position) {
        return position / 1000;
    }

    function transformChapter(chapter, asEventData) {
        if (!chapter) return;

        var time = transformTime(chapter.Time);
        var title = chapter.Text;

        if (asEventData === true) {
            return {
                chapterTime: time,
                chapterTitle: title
            };
        } else {
            return {
                time: time,
                title: title
            };
        }
    }

    function transformTimedEvent(timedEvent, asEventData) {
        if (!timedEvent) return;

        var id = timedEvent.id;
        var time = timedEvent.time;
        var payload = timedEvent.payload;
        var type = timedEvent.type;

        if (asEventData === true) {
            return {
                timedEventId: id,
                timedEventTime: time,
                timedEventPayload: payload,
                timedEventType: type
            };
        } else {
            return {
                id: id,
                time: time,
                payload: payload,
                type: type
            };
        }
    }

    function transformLink(link) {
        if (!link) return;
        return {
            title: link.Description,
            url: link.Url
        };
    }

    function transformSlide(slide, asEventData) {
        if (!slide) return;

        var time = transformTime(slide.Time);
        var title = slide.Text;
        var description = slide.Description;
        var url = slide.GetSlideUrl && slide.GetSlideUrl();
        var streamType = slide.StreamType;

        if (asEventData === true) {
            return {
                slideTime: time,
                slideTitle: title,
                slideDescription: description,
                slideUrl: url,
                slideStreamType: streamType
            };
        } else {
            return {
                time: time,
                title: title,
                description: description,
                url: url,
                streamType: streamType
            };
        }

    }

    function transformCaption(caption) {
        if (!caption) return;
        var time = transformTime(caption.Time);
        var endTime = transformTime(caption.EndTime)
        var text = caption.Text;

        return {
            time: time,
            endTime: endTime,
            text: text
        };
    }

    function transformPlayerState(playerState) {
        if (!playerState) {
            return;
        }

        return {
            state: playerState.State,
            isLive: playerState.IsLive
        };
    }

    function compareTime(a, b) {
        return (a.time - b.time);
    }

    function compareTimeThenEndTime(a, b) {
        var timeComparison = a.time - b.time;
        if (timeComparison === 0) {
            return (a.endTime - b.endTime);
        } else {
            return timeComparison;
        }
    }
    // #endregion

    // #region Public API methods
    module.addHandler = function (event, handler) {
        /// <returns type="void" />
        _eventBundle.addHandler(event, handler);
    };

    module.removeHandler = function (event, handler) {
        /// <returns type="void" />
        _eventBundle.removeHandler(event, handler);
    };

    module.play = function () {
        /// <returns type="void" />
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.Play();
    };

    module.pause = function () {
        /// <returns type="void" />
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.Pause();
    };

    module.stop = function () {
        /// <returns type="void" />
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.Stop();
    };

    module.seekTo = function (seconds) {
        /// <param name="seconds" type="Number">Position to seek to (in seconds)</param>
        /// <returns type="void" />
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.SetPosition(seconds * 1000);
    };

    module.getCurrentTime = function () {
        /// <returns type="Number">Current position in presentation (in seconds)</returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        var position = transformTime(_mediaPlayerModel.GetPosition());
        return position;
    };

    module.getCurrentChapter = function () {
        var position = _mediaPlayerModel.GetPosition();
        var chapterModel = _playerModel.GetChapterAt(position);
        if (!chapterModel) return;

        var chapter = transformChapter(chapterModel);
        return chapter;
    };

    module.getCurrentSlide = function (streamType) {
        var position = _mediaPlayerModel.GetPosition();
        var modelSlide = _playerModel.GetSlideAt(position, streamType);
        if (!modelSlide) return;

        var slide = transformSlide(modelSlide);
        return slide;
    };

    module.getCurrentCaptions = function () {
        var captions;
        var i;
        var position = _mediaPlayerModel.GetPosition();
        var captionsModel = _playerModel.GetCaptionsAt(position);
        if (!captionsModel) {
            return [];
        }

        captions = $.map(captionsModel, transformCaption);
        captions.sort(compareTimeThenEndTime);

        return captions;
    };

    module.getChapters = function () {
        var chaptersModel = _playerModel.GetChapters();
        if (!chaptersModel || chaptersModel.length == 0) return [];

        var chapters = $.map(chaptersModel, transformChapter);
        chapters.sort(compareTime);

        return chapters;
    };

    module.getSlides = function (streamType) {
        var slidesModel = _playerModel.GetSlides(streamType);
        if (!slidesModel || slidesModel.length == 0) return [];

        var slides = $.map(slidesModel, transformSlide);
        slides.sort(compareTime);
        return slides;
    };

    module.getAllSlides = function () {
        var slidesModel = _playerModel.GetAllSlides();
        if (!slidesModel || slidesModel.length == 0) return [];

        var slides = $.map(slidesModel, transformSlide);
        slides.sort(compareTime);
        return slides;
    };


    module.getCaptions = function () {
        var captionsModel = _playerModel.GetCaptions();
        if (!captionsModel || captionsModel.length == 0) return [];

        var captions = $.map(captionsModel, transformCaption);
        captions.sort(compareTime);
        return captions;
    };

    module.getDuration = function () {
        /// <returns type="Number">Duration of presentation (in seconds)</returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        var duration = transformTime(_mediaPlayerModel.GetDuration());
        return duration;
    };

    module.getPlayState = function () {
        /// <returns type="MediasitePlayer.PlayState">Current state of the player</returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        return _playerState.playState; // TODO: access directly from model
    };

    module.getPlayerState = function () {
        return transformPlayerState(_playerModel.GetPlayerState());
    };

    module.getVolume = function () {
        /// <returns type="Number">Current volume (0-100)</returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        return _mediaPlayerModel.GetVolume();
    };

    module.setVolume = function (volume) {
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.SetVolume(volume);
    };


    module.mute = function () {
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.Mute();
    };

    module.unMute = function () {
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        _mediaPlayerModel.UnMute();
    };

    module.isMuted = function () {
        /// <returns type="bool"></returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        return _mediaPlayerModel.IsMuted();
    };

    module.getPlaybackRate = function () {
        /// <returns type="bool"></returns>
        if (!_playerState.ready) {
            fireErrorEvent(Mediasite.Player.Error.Code.PlayerNotReady);
            return;
        }

        return _mediaPlayerModel.GetPlaybackRate();
    };

    module.getLiveStatus = function () {
        var liveStatus = _playerModel.GetLiveStatus();

        return liveStatus;
    };

    module.isActivated = function () {
        return true;
    };

    module.isReady = function () {
        return _playerState.ready;
    };

    module.setLayoutOptions = function (options) {
        _playerView.LoadNewOptions(options);
    };

    module.getLinks = function () {
        var linksModel = _playerModel.GetLinks();
        if (!linksModel || linksModel.length == 0) return [];

        var links = $.map(linksModel, transformLink);

        return links;
    };

    module.getPollsUri = function () {
        return _playerModel.GetPollsUri();
    };

    module.getAllStreams = function () {
        var array = _playerModel.GetStreamsMetadata();
        return array.slice(0);
    };


    module.getVisibleStreamTypes = function () {
        var array = _playerModel.GetVisibleStreamTypes() || [];
        return array.slice(0); // Return a copy... side-effects and all that.
    };

    module.setVisibleStreamTypes = function (orderedTypes) {
        orderedTypes = orderedTypes.slice(0); // Copy the array to prevent side effects from later external manipulation.

        _playerModel.SetVisibleStreamTypes(orderedTypes);
    };


    module.getTimedEvents = function (eventType) {
        var eventTypeIsIrrelevant = (typeof eventType === 'undefined');
        var timedEventsModel = _playerModel.GetTimedEvents();
        if (!timedEventsModel || timedEventsModel.length == 0) return [];

        var timedEvents = $.map(timedEventsModel, function (te, index) {
            if (eventTypeIsIrrelevant || te.type === eventType) {
                return transformTimedEvent(te);
            } else {
                return;
            }
        });

        timedEvents.sort(compareTime);

        return timedEvents;
    };

    module.addTimedEvent = function (time, type, payload) {
        var id = _idGenerator.generate();
        self.unsafeAddTimedEvent(time, type, payload, id);
        return id;
    };

    module.removeTimedEvent = function (id) {
        _playerModel.RemoveTimedEvent(id);
    };


    // Adds a timed event with the specified id. 
    // This is considered unsafe because id conflicts could occur. 
    // WARNING: This is not intended for use on the public API.
    module.unsafeAddTimedEvent = function (time, type, payload, id) {
        var te = { id: id, time: time, type: type, payload: payload };
        _playerModel.AddTimedEvent(te);
    };


    module.setTimedEventMarkerOptions = function (timedEventId, markerOptions) {
        _playerModel.SetTimedEventMarkerOptions(timedEventId, markerOptions);
    };

    module.clearTimedEventMarkerOptions = function (timedEventId) {
        _playerModel.ClearTimedEventMarkerOptions(timedEventId);
    };



    module.getPastSessionPlayedSegments = function () {
        return cloneSegmentsArray(_pastSessionPlayedSegments);
    };


    function fireErrorEvent(errorCode, details) {
        var data = {
            errorCode: errorCode
        };
        if (typeof details != "undefined") {
            data.details = details;
        }
        _eventBundle.fire("error", data);
    }

    function UniqueTemporaryIdGenerator(idScope) {
        var self = this;

        this.idScope = idScope;
        this.idCounter = 0;

        this.generate = function () {
            self.idCounter += 1;

            return ['TEMP', self.idScope, self.idCounter].join('-');
        };
    }

    return module;
})();
