/*
Spiffdar was originally developed by Steven Gravell. Source-code for the 
original Spiffdar is available for any purpose and without warranty.

Modifications to the original Spiffdar are under the MIT license (LICENSE.txt)
Copyright (c) 2011 Steven Robertson (steve@playnode.com)
*/

var Spiffdar = {

    tracks: {},
    loaded: false,
    delayed_loading: [],
    auth: false,
    delayed_auth: [],
    playing_sid: null,
    playing_qid: null,

    init: function(playdar) {
        $(document).ready(function() {

            $("#playdar_stat").show();

            this.list = $("#list");
            this.addform = $("#add");
            this.savebutton = $("#save");
            this.playdar = playdar;
            this.addform.submit(this.add_callback.bind(this))
            this.savebutton.click(this.save_callback.bind(this))
            this.playdar.client.register_listeners({

                onResults: this.results_handler.bind(this),

                onStat: function(detected) {
                    var text;
                    if (detected) text = "<b style=\"color:green\">Playdar ready</b>";
                    else text = "<b style=\"color:red\">Playdar unavailable</b>: You need <a href=\"http://getplaydar.com/\" target=\"_blank\">Playdar</a> installed and running.";
                    $("#playdar_stat").html(text);
                    this.loaded = true;
                    $.each(this.delayed_loading, function(i, func) { func(); });
                    this.delayed_loading = [];
                }.bind(this),

                onAuth: function() {
                    this.auth = true;
                    $.each(this.delayed_auth, function(i, func) { func(); });
                    this.delayed_auth = [];
                }.bind(this)

            });
        }.bind(this));
    },

    setTitle: function(title) {},

    setAnnotation: function(annotation) {},

    results_handler: function(response, final_answer) {
        this.tracks[response.qid].results_handler(response, final_answer);
    },

    add_callback: function(event) {
        event.stopPropagation();
        var artist = $("#artist").val();
        var track = $("#track").val();
        if (artist == "" || track == "") return;
        $("#artist").val("");
        $("#track").val("");
        $("#artist").focus();
        this.add_track(artist, track);
    },

    save_callback: function(event) {
        event.stopPropagation();
        var serialized = this.serialize();
        if (!serialized) return;
        new Ajax.Request("/save.php", {
            method: "post",
            parameters: {
                spiff: Object.toJSON(serialized)
            },
            onComplete: function(transport) {
                window.location.href = transport.responseText;
            }
        });
    },

    serialize: function() {
        var title = prompt("Give a name to your new playlist", "");
        if (!title) return;
        var serialized = {
            title: title,
            trackList: []
        };
        // TODO: Replace .select and .each with jQuery equivalents.
        this.list.select("li").each(function(li) {
            if (li.id == "listitem_template") return;
            var item = {
                track: li.down(".track").innerHTML.unescapeHTML(),
                creator: li.down(".artist").innerHTML.unescapeHTML()
            };
            serialized.trackList.push(item);
        });
        return serialized;
    },

    delay_loaded: function(func) {
        if (this.loaded) {
            func();
        } else {
            this.delayed_loading.push(func);
        }
    },

    delay_auth: function(func) {
        if (this.auth) {
            func();
        } else {
            this.delayed_auth.push(func);
        }
    },

    get_track: function(qid) {
        // Allowing the id of the element as well as just the qid itself.
        if (qid.indexOf("qid_") == 0) qid = qid.substr(4);
        return this.tracks[qid];
    },

    add_track: function(artist, track) {
        this.delay_loaded(function() {
            if ($('#emptylist')) $('#emptylist').hide();
            if ($('#loading')) $('#loading').hide();
            var qid = Playdar.Util.generate_uuid();
            var row = this.new_row(qid, artist, track);
            var spiffTrack = new SpiffdarTrack(qid, row, this);
            this.tracks[qid] = spiffTrack;
            this.delay_auth(function() { this.resolve(spiffTrack); }.bind(this));
            return spiffTrack;
        }.bind(this));
    },

    /**
     * resolved based on a fifo queue
     */
    resolution_queue: [],
    processing_resolution_queue: 0,
    resolution_queue_size: 100,

    resolve: function(spiffTrack) {
        this.resolution_queue.push(spiffTrack);
        this.ping_resolution_queue();
    },

    ping_resolution_queue: function() {
        if (this.processing_resolution_queue < this.resolution_queue_size) {
            var upto = this.resolution_queue_size - this.processing_resolution_queue;
            for (var i = 1; i <= upto; i++) {
                var spiffTrack = this.resolution_queue.shift();
                if (!spiffTrack) break;
                this.processing_resolution_queue++;
                spiffTrack.resolve(this.track_resolution_done.bind(this));
            }
        }
    },

    track_resolution_done: function() {
        this.processing_resolution_queue--;
        this.ping_resolution_queue();
    },

    new_row: function(qid, artist, track) {
        var template = $("#listitem_template");
        var row = template.clone(true);
        row.attr("id", "qid_" + qid);
        this.list.append(row);
        row.find(".artist").html(artist);
        row.find(".track").html(track);
        if (Object.keys(this.tracks).length % 2 == 0) {
            row.addClass("odd");
        }
        return row;
    },

    play: function(track) {
        if (!track) {
            track = this.tracks.get(this.tracks.keys().pop());
            if (!track) return
        }
        if (!track.isResolved) {
            var nextElement = track.element.next();
            if (!nextElement) return;//end of list
            var next = this.get_track(nextElement.id);
            this.play(next);
            return;
        }
        if (track.qid != this.playing_qid && this.playing_qid) {
            //note change to sid here
            this.playdar.player.play_stream(this.playing_sid);
        }

        if (this.playing_qid && this.playing_qid == track.qid || !track.isResolved) {
            this.playing_sid = null;
            this.playing_qid = null;
        } else {
            this.playing_sid = track.sid;
            this.playing_qid = track.qid;
        }
        this.playdar.player.play_stream(track.sid);//does play and pause atm
    }
};

var SpiffdarTrack = Class.extend(
{
    playing: false,
    isResolved: false,
    resolutions: [],
    resolved_callback: null,
    source_count: 0,

    init: function(qid, element, spiffdar) {
        this.element = element;
        this.qid = qid;
        this.artist = this.element.find(".artist").html();
        this.track = this.element.find(".track").html();
        this.spiffdar = spiffdar;
        this.playdar = spiffdar.playdar; // for convenience
    },

    resolve: function(callback) {
        this.resolved_callback = callback;
        this.spiffdar.playdar.client.resolve(this.artist, this.track, "", this.qid);
    },

    results_handler: function(response, final_answer) {
        if (final_answer) {
            var first = response.results[0];
            if (first) this.resolved(first);
            else this.not_resolved();
        }
        this.add_resolutions(response.results);
    },

    add_resolutions: function(results) {
        
        // Isolate the additional results.
        var diff = $.each(results, function(i, item) {
            return !$.inArray(item, this.resolutions);
        }.bind(this));
        
        // Add the new results.
        this.resolutions = this.resolutions.concat(diff);
        
        // Increment the count if there were new results added.
        if (diff.length > 0) {
            this.increment_source_count(diff.length);
        }
    },

    increment_source_count: function(count) {
        var orig = this.source_count;
        this.source_count += count;
        if (this.source_count > 1) {
            var sc = this.element.find(".sourceCount");
            sc.html('(' + this.source_count + ')');
            if (orig < 2) {
                sc.click(this.callback_source_count.bind(this));
            }
        }
    },

    callback_source_count: function(event) {
        event.stopPropagation();
        //todo: don't regenerate this every time?
        this.hide_resolutions();
        this.resolution_options = $("<ul class=\"resolutions\"></ul>");
        $.each(this.resolutions, function(i, result) {
            var track = $("<span class=\"res_track\">" + result.track + "</span><span>, </span>");
            var artist = $("<span class=\"res_artist\">" + result.artist + "</span><span>, </span>");
            var sourceInfo = Playdar.Util.mmss(result.duration);
            sourceInfo += ", Source: " + result["source"];
            if (result.bitrate > 0) sourceInfo += ", " + result.bitrate + "kbps";
            sourceInfo = $("<span>" + sourceInfo + "</span>");
            var a = $("<a id=\"sid_" + result.sid + "\"></a>");
            a.append(track);
            a.append(artist);
            a.append(sourceInfo);
            //todo: refactor into a source class
            a.click(this.change_source.bind(this));
            var li = $("<li class=\"" + (result.sid == this.sid ? "selected" : "") + "\">");
            li.append(a);
            this.resolution_options.append(li);
        }.bind(this));
        this.element.find(".resolvedInfo").append(this.resolution_options);
        this.last_options_callback = this.hide_resolutions.bind(this);
        $("body").click(this.last_options_callback);
    },

    change_source: function(event) {
        event.stopPropagation(); // TODO: Check if this is required and whether to keep event arg.
        var sid = $(event.currentTarget).attr("id").substr(4);
        var result;
        $.each(this.resolutions, function(i, item) {
            if (item.sid == sid) result = item;
        });
        var playing = this.playing;
        //playdar does some other checks we don't want to kill
        if (playing) {
            this.spiffdar.play(this);//pause
        }
        //this kills playdar atm due to it's currently playing checks
        //this.sound.destruct();
        //this.register_stream(result);
        this.resolved(result, true);
        this.hide_resolutions();
        if (playing) {
            this.spiffdar.play(this);
        }
    },

    hide_resolutions: function() {
        if (this.resolution_options) {
            this.resolution_options.remove();
            this.resolution_options = null;
        }
        if (this.last_options_callback) {
            $("body").unbind("click", this.last_options_callback);
            this.last_options_callback = null;
        }
        this.element.removeClass("hover");
    },

    not_resolved: function() {
        if (this.resolved_callback) {
            this.resolved_callback();
        }
    },

    resolved: function(result, nobind) {
        //when we resolve other times against other results
        //we don't want to rebind these events, todo: move this
        //somewhere else?
        if (!nobind) {
            this.element.click(this.click_callback.bind(this));
            this.element.mouseover(this.mouseover_callback.bind(this));
            this.element.mouseout(this.mouseout_callback.bind(this));
        }
        this.isResolved = true;
        this.element.addClass("resolved");
        this.element.find(".time").html(Playdar.Util.mmss(result.duration));
        this.element.find(".artist").html(result.artist);
        this.element.find(".track").html(result.track);
        this.element.find(".source").html(result["source"]);
        //todo: move to the point of playback instead of resolutions
        this.register_stream(result);
        if (this.resolved_callback) {
            this.resolved_callback();
        }
    },

    register_stream: function(result) {
        this.sid = result.sid;
        this.sound = this.playdar.player.register_stream(result, {
            onfinish: this.notification_finished.bind(this),
            onpause: this.notification_paused.bind(this),
            onplay: this.notification_played.bind(this),
            onresume: this.notification_played.bind(this),
            onstreamfailure: this.notification_streamfailure.bind(this)
        });
    },

    click_callback: function(event) {
        event.stopPropagation();
        this.spiffdar.play(this);
        this.hide_resolutions();
    },

    mouseover_callback: function(event) {
        event.stopPropagation();
        this.element.addClass("hover");
    },

    mouseout_callback: function(event) {
        event.stopPropagation();
        this.element.removeClass("hover");
    },

    notification_paused: function() {
        this.element.removeClass("playing");
        this.element.addClass("paused");
        this.playing = false;
    },

    notification_played: function() {
        this.element.removeClass("paused");
        this.element.addClass("playing");
        this.element.removeClass("streamfailure");
        this.playing = true;
    },

    notification_streamfailure: function() {
        var playing = this.playing;
        var nextResolution = null;
        var currentResolution = null;
        // TODO: Replace .each with jQuery equivalent.
        this.resolutions.each(function(r) {
            if (r.sid == this.sid) {
                currentResolution = r;
            } else if (currentResolution && !nextResolution) {
                nextResolution = r;
            }
        }.bind(this));
        if (nextResolution && playing) {
            //next resolution
            this.resolved(nextResolution, true);
            this.spiffdar.play(this);
        } else {
            this.element.addClass("streamfailure");
            //the whileplaying event next gets hit again
            //by soundmanager, so we need to remake it all :(
            this.sound.destruct();
            this.register_stream(currentResolution);
            if (playing) {
                this.notification_finished();
            }
        }
    },

    notification_finished: function() {
        this.notification_paused();//todo: stopped
        var qid = this.spiffdar.playing_qid;
        this.spiffdar.playing_qid = null;
        this.spiffdar.playing_sid = null;
        var thisElement = this.element;
        var loopMax = 1000; // In case of accidental infinite loop.
        var loopCount = 0;
        while (loopCount++ < loopMax) {
            
            // Check if there's any more items in the list.
            var nextElement = thisElement.next();
            if (!nextElement) {
                this.playing = false;
                break; // Reached the end of the list.
            }
        
            // Check if the next item has been resolved, otherwise skip it.
            var nextId = nextElement.attr('id').substr(4); // qid.indexOf("qid_") == 0
            var nextIsResolved = this.spiffdar.tracks[nextId]['isResolved'];
            if (this.spiffdar.tracks[nextId]['isResolved'] != true) {
                thisElement = nextElement;
                continue;
            }

            var next = this.spiffdar.get_track(nextElement.attr('id'));
            this.spiffdar.play(next);
            break;
        }
    }
});

