// requires
//      playlist.js
//      async.js (for loading remote playlists)
//      json2.js (for parsing remote JSon playlists)

// TODO: add proper error messages.

/**
 * Global function required by the YouTube player.
 */
function onYouTubePlayerReady() {
    debug("onYouTubePlayerReady()");
    PlaylistHelper.__onYouTubePlayerReady();
}

PlaylistHelper = {
    version: "0.5.0",
    apiVersion: "0.5", // FIXME document
    
    userTimerCallback: null,
    userErrorCallback: null,
    allowResize: false,

    // normal (5): 320x240
    // high (18): 480x360
    // hd (22): 1280 x 720
    playerWidth: 200,//425,
    playerHeight: 177,//356,
    
    /**
     * 
     */
    addPlayer: function (divId) {
        var p = PlaylistHelper.getPlayer(divId);
        if (p === null) {
            p = new PlaylistHelper.__plClass(divId);
            p.onErrorListener = "PlaylistHelper." + p.divId + '_onErrorListener';
            PlaylistHelper[p.divId + "_onErrorListener"] = function (err) {
                debug("onError(" + p.divId + ")");
                PlaylistHelper.__onYTError(p, err);
            };
            p.onStateChangeListener = "PlaylistHelper." + p.divId + '_onStateChangeListener';
            PlaylistHelper[p.divId + "_onStateChangeListener"] = function (state) {
                debug("onStateChange(" + p.divId + ")");
                PlaylistHelper.__onYTStateChange(p, state);
            };
            var index = PlaylistHelper.__players.length;
            PlaylistHelper.__players[index] = p;
        }
        return p;
    },
    

    /**
     * 
     */
    getPlayer: function (divId) {
        for (var i = 0; i < PlaylistHelper.__players.length; i++) {
            if (PlaylistHelper.__players[i] &&
                    PlaylistHelper.__players[i].divId === divId) {
                return PlaylistHelper.__players[i];
            }
        }
        return null;
    },
    

    /**
     * FIXME improve documentation to note that it doesn't create all
     * players.
     *
     * @deprecated
     */
    createPlayer: function (divId) {
        var ret = PlaylistHelper.addPlayer(divId);
        ret.create();
        return ret;
    },
    
    
    /**
     * @deprecated
     */
    createPlayers: function () {
        for (var i = 0; i < PlaylistHelper.__players.length; i++) {
            var p = PlaylistHelper.__players[i];
            if (PlaylistHelper.__is(p)) {
                p.create();
            }
        }
    },


    /**
     * @deprecated use loadRemotePlaylistJSon instead
     */
    loadPlaylistJSon: function (p, url, onLoadCallback) {
        // URL must return a JSon formatted data structure, which contains
        // the data item "videos", as an array of playlist video objects.
        
        var plLoader = {
            p: p,
            callback: onLoadCallback,
            update: async.createUpdate(url,
                "PlaylistHelper.__loadPlaylistJSonCallback", true)
        };
        plLoader.update();
    },
    
    __loadPlaylistJSonCallback: function (response, plLoader) {
        var success = false;
        if (response.error) {
            PlaylistHelper.__err(0, plLoader.p,
                "Problem loading playlist: " + response.error);
        } else if (PlaylistHelper.__isa(response.json, 'videos')) {
            if (PlaylistHelper.__isa(response.json, 'title')) {
                plLoader.p.title = response.json.title;
            }
            var vidList = response.json.videos;
            plLoader.p.addVideos(vidList);
            success = true;
        } else {
            PlaylistHelper.__err(0, plLoader.p,
                "Invalid JSon response: " + response.text);
        }
        if (plLoader.callback) {
            plLoader.callback(success);
        }
    },
    
    /**
     * Expected format for the XML document:
     *   <playlist title="title">
     *       <video title="My Title" vId="YT ID" start="0" end="-1" />
     *   </playlist>
     * Note that every attribute in the "video" tag is added to the video
     * object.
     *
     * @deprecated use loadRemotePlaylistXML instead
     */
    loadPlaylistXML: function (p, url, onLoadCallback) {
        debug("Loading playlist XML " + url);
        var plLoader = {
            p: p,
            callback: onLoadCallback,
            update: async.createUpdate(url,
                "PlaylistHelper.__loadPlaylistXMLCallback", false)
        };
        plLoader.update();
    },
    
    __loadPlaylistXMLCallback: function (response, plLoader) {
        debug("Retrieved the playlist");
        var success = false;
        if (response.error) {
            PlaylistHelper.__err(0, plLoader.p,
                "Problem loading playlist: " + response.error);
        } else if (response.xml) {
            plLoader.p.loadPlaylistXML(response.xml);
            success = true;
        } else {
            PlaylistHelper.__err(0, plLoader.p,
                "Invalid XML response: " + response.text);
        }
        if (plLoader.callback) {
            plLoader.callback(success);
        }
    },
    

    /**
     * @deprecated this will be removed in the future
     */
    addVideos: function (p, videoList) {
        p.addVideos(videoList);
    },
    
    __onYouTubePlayerReady: function () {
        for (var i = 0; i < PlaylistHelper.__players.length; i++) {
            var p = PlaylistHelper.__players[i];
            if (PlaylistHelper.__isa(p, 'create')) {
                debug("Getting player for " + p.divId);
                var ytplayer = document.getElementById(p.playerId);
                if (ytplayer && typeof ytplayer !== "undefined") {
                    debug("-- player ready for " + p.divId + ": " + ytplayer);
                    p.player = ytplayer;
                    if(p.divId == "ytapiplayer1")
                    {
                        p.player.mute(); 
                    }
                    PlaylistHelper.__addListeners(p.divId);
                }
            }
        }
    },
    
    __addListeners: function (divId) {
        for (var i = 0; i < PlaylistHelper.__players.length; i++) {
            var p = PlaylistHelper.__players[i];
            if (p && p.divId === divId) {
                if (p.player) {
                    debug("-- add listeners for " + p.divId);
                    p.player.addEventListener("onError", p.onErrorListener);
                    p.player.addEventListener("onStateChange", p.onStateChangeListener);
                    
                    p.playlist = new PlaylistObject(p.player);
                    p.playlist.allowResize = p.allowResize;
                    p.playlist.callback = p.callback;
                    if (p.initialVideos) {
                        p.playlist.addAll(p.initialVideos);
                    }
                    p.playlist.autostart = p.autostart;
                    debug("Playlist for " + divId + " ready");
                    p.playlist.ready();
                    debug("Done with ready");
                    
                    // continuous timer thread - millisecond times
                    if (! PlaylistHelper.__registeredTimer) {
                        debug("Setting timer");
                        PlaylistHelper.__registeredTimer = true;
                        setInterval("PlaylistHelper.__playerCheckTimer()", 250);
                    }
                } else {
                    // try again, hoping that it's properly loaded by then
                    setTimeout("PlaylistHelper.__addListeners('" + divId + "')", 100);
                }
                
                break;
            }
        }
    },
    
    __onYTError: function (p, err) {
        p = PlaylistHelper.__getPlayer(p);
        if (PlaylistHelper.__isnt(p)) {
            return;
        }
        
        if (err === 100) {
            PlaylistHelper.__err(err, p,
                "The video is not available: " + p.playlist.getCurrentVideo().vId);
        } else
        if (err === 101) {
            PlaylistHelper.__err(err, p,
                "The video cannot be embedded: " + p.playlist.getCurrentVideo().vId);
        } else {
            PlaylistHelper.__err(err, p,
                "Unexpected error in video " + p.playlist.getCurrentVideo().vId +
                ": " + err);
        }
    },
    
    __onYTStateChange: function (p, state) {
        p = PlaylistHelper.__getPlayer(p);
        if (PlaylistHelper.__isnt(p)) {
            return;
        }
        
        //debug("ytOnStateChange: " +p.divId+ " state " +state);
        p.playlist.onStateChange(state);
    },
    
    __playerCheckTimer: function () {
        for (var i = 0; i < PlaylistHelper.__players.length; i++) {
            if (PlaylistHelper.__players[i] &&
                    PlaylistHelper.__players[i].playlist) {
                PlaylistHelper.__players[i].playlist.onTimer();
            }
        }
        if (PlaylistHelper.userTimerCallback) {
            PlaylistHelper.userTimerCallback();
        }
    },
    
    
    __getPlayer: function (p) {
        if (typeof p === "number") {
            p = PlaylistHelper.__players[p];
        } else
        if (typeof p === "string") {
            var f = null;
            for (var i = 0; i < PlaylistHelper.__players.length; i++) {
                if (PlaylistHelper.__players[i].divId === p) {
                    f = PlaylistHelper.__players[i];
                    break;
                }
            }
            p = f;
        }
        if (PlaylistHelper.__isnta(p, 'playlist')) {
            return null;
        }
        return p;
    },
    
    
    __players: [],
    __registeredTimer: false,
    
    
    __is: function (v) {
        return v && typeof v !== "undefined";
    },
    __isnt: function (v) {
        return ! PlaylistHelper.__is(v);
    },
    __isa: function () {
        var argList = PlaylistHelper.__isa.arguments;
        if (PlaylistHelper.__is(argList) && argList.length > 0) {
            var x = argList[0];
            if (PlaylistHelper.__isnt(x)) {
                return false;
            }
            for (var i = 1; i < argList.length; i++) {
                if (PlaylistHelper.__isnt(x[argList[i]])) {
                    return false;
                }
                x = x[argList[i]];
            }
            return true;
        } else {
            return false;
        }
    },
    __isnta: function () {
        return ! PlaylistHelper.__isa(
            PlaylistHelper.__isnta.arguments);
    },
    
    __err: function (errCode, playObj, msg) {
        if (PlaylistHelper.userErrorCallback) {
            PlaylistHelper.userErrorCallback(errCode, playObj, msg);
        } else {
            //alert(msg);
        }
    },
    
    __warn: function (errCode, playObj, msg) {
        if (PlaylistHelper.userErrorCallback) {
            PlaylistHelper.userErrorCallback(errCode, playObj, msg);
        } else {
            debug(msg);
        }
    }
};

// ------------------------------------------------------------------------
// Class methods for __plClass

/**
 * FIXME fully document.
 * This is the main helper class for making the implementation of a
 * player easier.
 */
PlaylistHelper.__plClass = function (divId) {
    this.divId = divId;
    this.playerId = "ytplayer_" + divId;
    this.created = false;
    this.player = null;
    this.playlist = null;
    this.title = "";
    
    // User writable
    this.width = PlaylistHelper.playerWidth;
    this.height = PlaylistHelper.playerHeight;
    this.initialVideos = null;
    this.callback = null;
    this.autostart = true;
    this.allowResize = PlaylistHelper.allowResize;
};


PlaylistHelper.__plClass.prototype.__isInit = function () {
    return (PlaylistHelper.__is(this.playlist) &&
        PlaylistHelper.__is(this.player));
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.addVideos = function (videoList) {
    if (PlaylistHelper.__is(videoList)) {
        if (PlaylistHelper.__is(this.playlist)) {
            // playlist already created
            this.playlist.addAll(videoList);
        } else
        if (PlaylistHelper.__is(this.initialVideos)) {
            // no playlist created yet, but there are initial videos
            for (var i = 0; i < videoList.length; i++) {
                this.initialVideos[this.initialVideos.length] = videoList[i];
            }
        } else {
            // no playlist created yet, and no initial videos
            this.initialVideos = videoList;
        }
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.addVideo = function (video) {
    if (PlaylistHelper.__is(video)) {
        var vv = [];
        vv[0] = v;
        this.addVideos(vv);
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.insertVideoAt = function (index, video) {
    if (PlaylistHelper.__is(video)) {
        if (PlaylistHelper.__is(this.playlist)) {
            this.playlist.insert(index, video);
        } else
        if (PlaylistHelper.__is(this.initialVideos)) {
            if (index < 0) {
                index = 0;
            }
            if (index > this.initialVideos.length) {
                index = this.initialVideos.length;
            }
            for (var i = this.initialVideos.length; --i >= index;) {
                this.initialVideos[i + 1] = this.initialVideos[i];
            }
            this.initialVideos[index] = video;
        } else {
            this.initialVideos = [ video ];
        }
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.removeVideoAt = function (index) {
    if (PlaylistHelper.__is(this.playlist)) {
        this.playlist.remove(index);
    } else
    if (PlaylistHelper.__is(this.initialVideos)) {
        if (index >= 0 && index < this.initialVideos.length) {
            for (var i = index; i < this.initialVideos.length - 1; i++) {
                this.initialVideos[i] = this.initialVideos[i + 1];
            }
            this.initialVideos[this.initialVideos.length - 1] = null;
            this.initialVideos.length = this.initialVideos.length - 1;
        }
    }
    // else there's nothing to do
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.removeAllVideos = function () {
    if (PlaylistHelper.__is(this.playlist)) {
        this.playlist.clear();
    } else
    if (PlaylistHelper.__is(this.initialVideos)) {
        this.initialVideos = null;
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.setNextVideoIndex = function (index) {
    if (PlaylistHelper.__is(this.playlist)) {
        this.playlist.setNextVideo(index);
    }
};

/**
 * FIXME document
 */
PlaylistHelper.__plClass.prototype.getState = function () {
    var ret = null;
    if (PlaylistHelper.__is(this.playlist)) {
        ret = this.playlist.getState();
    } else {
        ret = {
            ready: false,
            player: "not playing",
            playlist: "not initialized"
        };
    }
    if (PlaylistHelper.__isnt(this.player)) {
        ret.player = "not initialized";
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.embed = function () {
    if (! this.created) {
        var params = { allowScriptAccess: "always", userAgentButton: "x" };
        var atts = { id: this.playerId };
        swfobject.embedSWF(
            // hd=1: support for high-definition videos
            "http://www.youtube.com/apiplayer?enablejsapi=1&hd=1&playerapiid=" + this.playerId,
            this.divId, "" + this.width, "" + this.height,
            "8", null, null, params, atts);
        this.created = true;
    }
};

/**
 * @deprecated use embed instead
 */
PlaylistHelper.__plClass.prototype.create = function () {
    this.embed();
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.getVideos = function () {
    var ret = null;
    if (PlaylistHelper.__is(this.playlist)) {
        // playlist already created, and this returns a copy of the
        // list.
        ret = this.playlist.getVideos();
    } else
    if (PlaylistHelper.__is(this.initialVideos)) {
        // no playlist created yet, but there are initial videos
        ret = [];
        for (var i = 0; i < this.initialVideos.length; i++) {
            if (this.initialVideos[i]) {
                ret[ret.length] = this.initialVideos[i];
            }
        }
    } else {
        // no playlist created yet, and no initial videos
        ret = [];
    }
    return ret;
};

/**
 * FIXME document
 */
PlaylistHelper.__plClass.prototype.play = function () {
    if (this.__isInit()) {
        if (this.playlist.playerstate === Playlist_YT_Paused ||
                this.playlist.playerstate === Playlist_YT_Buffering ||
                this.playlist.playerstate === Playlist_YT_Cued) {
            this.player.playVideo();
        }
    }
};

/**
 * FIXME document
 */
PlaylistHelper.__plClass.prototype.pause = function () {
    if (this.__isInit()) {
        if (this.playlist.playerstate === Playlist_YT_Playing) {
            this.player.pauseVideo();
        }
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.nextVideo = function (autostart) {
    if (PlaylistHelper.__is(this.playlist)) {
        if (this.playlist.hasMore()) {
            this.playlist.loadNextVideo(autostart);
        }
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.prevVideo = function (autostart) {
    if (PlaylistHelper.__is(this.playlist)) {
        var idx = this.playlist.getCurrentVideoIndex();
        if (idx > 0) {
            idx = idx - 1;
        } else {
            idx = 0;
        }
        this.playlist.setNextVideo(idx);
        this.playlist.loadNextVideo(autostart);
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.loadPlaylistJSon = function (text) {
    var val = JSON.parse(text);
    if (PlaylistHelper.__isa(val, 'title')) {
        this.title = val.title;
    }
    if (PlaylistHelper.__isa(val, 'videos')) {
        this.addVideos(val.videos);
    }
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.loadRemotePlaylistJSon = function (url, callback) {
    PlaylistHelper.loadPlaylistJSon(this, url, callback);
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.loadPlaylistXMLString = function (text) {
    var xmlDoc = null;
    if (window.DOMParser) {
        var parser = new DOMParser();
        xmlDoc = parser.parseFromString(text, "text/xml");
    } else { // Internet Explorer
        xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
        xmlDoc.async = "false";
        xmlDoc.loadXML(text);
    }
    return this.loadPlaylistXML(xmlDoc);
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.loadPlaylistXML = function (text) {
    var playlistTags = text.getElementsByTagName("playlist");
    if (! playlistTags || playlistTags.length !== 1) {
        PlaylistHelper.__err(0, this, "No playlist tag in XML");
        return;
    }
    if (PlaylistHelper.__isa(playlistTags[0], attributes, 'title')) {
        this.title = playlistTags[0].attributes.title;
    }
    var videoTags = playlistTags[0].getElementsByTagName("video");
    var vidList = [];
    for (var i = 0; i < videoTags.length; i++) {
        var attr = videoTags[i].attributes;
        var vid = {};
        for (var j = 0; j < attr.length; j++) {
            vid[attr[j].name] = attr[j].value;
        }
        if (PlaylistHelper.__isa(vid, 'vId')) {
            if (PlaylistHelper.__isa(vid, 'start')) {
                vid.start = parseFloat(vid.start);
            }
            if (PlaylistHelper.__isa(vid, 'end')) {
                vid.end = parseFloat(vid.end);
            }
            if (PlaylistHelper.__isa(vid, 'q')) {
                vid.quality = vid.q;
            }
            vidList[vidList.length] = vid;
        } else {
            PlaylistHelper.__warn(0, this, "Invalid video tag " + i);
        }
    }
    this.addVideos(vidList);
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.loadRemotePlaylistXML = function (url, callback) {
    PlaylistHelper.loadPlaylistXML(this, url, callback);
};


/**
 * Retrieve the currently loaded video object, as passed into the playlist.
 *
 * @return the current video object, or null if none is active.
 */
PlaylistHelper.__plClass.prototype.getCurrentVideo = function () {
    if (PlaylistHelper.__is(this.playlist)) {
        return this.playlist.getCurrentVideo();
    }
    return null;
};

/**
 * @return -1 if at the beginning of the playlist, size if at the end of the
 *  playlist, or the actual index.
 */
PlaylistHelper.__plClass.prototype.getCurrentVideoIndex = function () {
    if (PlaylistHelper.__is(this.playlist)) {
        return this.playlist.getCurrentVideoIndex();
    }
    return -1;
};

/**
 * 
 */
PlaylistHelper.__plClass.prototype.getCurrentVideoInfo = function () {
    var vid = this.getCurrentVideo();
    if (this.__isInit() && vid) {
        var duration = this.player.getDuration();
        var start = 0;
        var end = duration;
        var q = 'std';
        if (PlaylistHelper.__isa(vid, 'start')) {
            start = vid.start;
            if (start < 0) {
                start = 0;
            }
            if (start > duration) {
                start = duration;
            }
        }
        if (PlaylistHelper.__isa(vid, 'end')) {
            end = vid.end;
            if (end < start) {
                end = start;
            }
            if (end > duration) {
                end = duration;
            }
        }
        if (PlaylistHelper.__isa(vid, 'quality')) {
            if (vid.quality == 'hd' || vid.quality == 'hq') {
                q = vid.quality;
            }
        }
        var ret = {
            bytesTotal: this.player.getVideoBytesTotal(),
            bytesLoaded: this.player.getVideoBytesLoaded(),
            absDuration: duration,
            absCurrentTime: this.player.getCurrentTime(),
            duration: end - start,
            start: start,
            end: end,
            currentTime: this.player.getCurrentTime() - start,
            index: this.getCurrentVideoIndex(),
            quality: q,
            width: this.player.width,
            height: this.player.height
        };
        for (var i in vid) {
            if (PlaylistHelper.__isnta(ret, i)) {
                ret[i] = vid[i];
            }
        }
        return ret;
    } else {
        return null;
    }
};

/**
 * FIXME document
 */
PlaylistHelper.__plClass.prototype.absSeekTo = function (seconds, autostart) {
    if (this.__isInit()) {
        var vid = this.getCurrentVideo();
        if (PlaylistHelper.__is(vid)) {
            if (PlaylistHelper.__isa(vid, 'end') && seconds > vid.end && vid.end >= 0) {
                this.player.loadNextVideo(autostart);
                return;
            }
            if (PlaylistHelper.__isa(vid, 'start') && seconds < vid.start) {
                seconds = vid.start;
            }
            this.player.seekTo(seconds, autostart);
        }
    }
};

/**
 * FIXME document
 */
PlaylistHelper.__plClass.prototype.relSeekTo = function (seconds, autostart) {
    var info = this.getCurrentVideoInfo();
    if (info) {
        if (seconds < 0) {
            seconds = 0;
        }
        if (info.duration > seconds) {
            this.player.loadNextVideo(autostart);
            return;
        }
        this.player.seekTo(seconds + info.start, autostart);
    }
};
