/* Pane Browser, Author: Gary Manfredi
 * [This version PRESUMES all genre data has been pre-loaded to identify leafs]
 * 
 * Pass an empty div as rootDiv and call reset on it to create panes and go to top level
 * Example:
 *   var pb = new _PaneBrowser($("#target"));
 *   pb.initialize(function(id){
 *   	alert("User selects genre id:" + id);
 *   });
 */
function _PaneBrowser(rootDiv, callback){
    // create base objects
    this.paneHolder = $("<div id='paneHolder'></div>");
    this.browser = $("<div class='_pb'></div>").append(this.paneHolder).appendTo($(rootDiv));
    this.currentPane = null;
	// this.rootId = 301668; // physical cds;
    this.rootId = 324381011; // mp3s
    this.model = new _PaneModel(this.rootId);
    this.callback = null;
}

// callback on leaf selection
_PaneBrowser.prototype.initialize = function(callback){
    this.callback = callback;
    this.reset();
}

// (re)start browser with base genre
_PaneBrowser.prototype.reset = function(){
    // set default pane
    var me = this;
    this.model.loadGenre(this.rootId, function(data){
        me.cb_createBasePane(data);
    });
}


// select genre (user clicks on leaf)
_PaneBrowser.prototype._select = function(li){
	li = $(li);
    var me = this;
	$("._pb li.active").removeClass("active");
	li.addClass("active");
	var id = li.attr("id");
    this.model.loadGenre(id, function(data){
        me.callback(id);
    });
}


// do "drill down" into genre
_PaneBrowser.prototype._down = function(id){
    var me = this;
    this.model.loadGenre(id, function(data){
        me.cb_slideInLeft(data);
    });
}


// do "back up" action
_PaneBrowser.prototype._up = function(id){
    var me = this;
    this.model.loadGenre(id, function(data){
        me.cb_slideInRight(data);
    });
}


// callback function from model for creating first pane
_PaneBrowser.prototype.cb_createBasePane = function(data){
    // empty panes and start over
    $(".pane", this.paneHolder).remove();
    this.paneCount = 0;
    
    // create base bane
    var aPane = this._createPane(data);
    this.paneHolder.append(aPane);
}


_PaneBrowser.prototype.cb_slideInLeft = function(data){
    // do animation
    var oldPane = this.currentPane;
    var me = this;
    this.paneHolder.append(this._createPane(data)).animate({
        "left": -163
    }, 500, function(){
        me._finishLeftSlide(oldPane);
    });
}


// finish the pane animation
_PaneBrowser.prototype._finishLeftSlide = function(oldPane){
    // hide the blink in resetting this.paneHolder by duplicating and appending the new pane before removing old one
    var extraPane = this.currentPane.clone(true);
    this.paneHolder.append(extraPane);
    
    // now remove old pane and reset paneHolder
    oldPane.remove();
    this.paneHolder.css("left", 0);
    
    // give it time to refresh & remove the extra pane
    setTimeout(function(){
        extraPane.remove();
    }, 100);
    
}


_PaneBrowser.prototype.cb_slideInRight = function(data){
    
    // hide the blink when prepending new pane by appending a duplicate old pane
    var extraPane = this.currentPane.clone(true);
    this.paneHolder.append(extraPane);
    
    // shift left and prepend new pane, thereby hiding the blink
    var oldPane = this.currentPane;
    this.paneHolder.css("left", -163).css("overflow", "hidden").prepend(this._createPane(data));
    
    // remove duplicate pane
    extraPane.remove();
    
    // do animation and remove old pane when done
    this.paneHolder.animate({
        "left": 0
    }, 500, function(){
        oldPane.remove();
    });
}


// create a new pane with data
_PaneBrowser.prototype._createPane = function(data){
    var me = this;
    
    // create new pane with a unique id ("id324324") - id used for back button
    var aPane = $("<ol class='pane'></ol>");
		
	// if at root, don't add back button
    if (data.id == this.rootId) {
		// $("<li class='genreTitle'><strong>All Genres</strong></li>").appendTo(aPane);
		
	} else {
		// not at root, so add back button
		li = $("<li class='back'><strong><a href='javascript:void(0)'>&lt;&lt; back</a></strong></li>").click(function(){
			me._up(data.parent);
		}).hover(function(){
			$(this).addClass("selected");
		}, function(){
			// hover off
			$(this).removeClass("selected");
		});
		aPane.append(li);
		
		// add clickable title
		$("<li class='genreTitle'></li>").attr("id", data.id).html("<a href='javascript:void(0)'>All " + data.name + "</a>").click(function(){
			me._select(this);
		}).hover(function(){
			$(this).addClass("selected");
		}, function(){
			$(this).removeClass("selected");
		}).appendTo(aPane);
	}
    
    // populate it with the children of current genre
    var id;
    for (var i = 0; i < data.children.length; i++) {
        id = data.children[i].id;
        li = $("<li><a href='javascript:void(0)'></a></li>").attr("id", id).hover(function(){
            $(this).addClass("selected");
        }, function(){
            $(this).removeClass("selected");
        });
		
		// define this item as a leaf if no children and set actions
		if (this.model.getGenre(id).children.length == 0) {
			li.addClass("leaf").click(function(){
	            me._select(this);
	        })
		} else {
			li.click(function(){
	            me._down($(this).attr("id"));
	        })
		}
		
        $("a", li).html(data.children[i].name);
        aPane.append(li);
    };
    
    // set this now as the current pane
    this.currentPane = aPane;
    
    return aPane;
}


/*
 * PaneBrowser Model
 */
function _PaneModel(rootId){
    // make me observable
    $.extend(this, new _Observable());
    
    // object to store genre objects.  Uses property name "id" + genre id
    this.genres = {};
    this.currentGenre = {
        id: rootId,
        name: "All Genres"
    }; // for holding id & name
    
    // if we preloaded all genres in global AllGenres, set it here
    if (typeof AllGenres == "object") 
        this.genres = AllGenres;
    
    // callback for views requesting a genre
    this.callback = "";
    
    // hardcoded URL for server query
    this.AWSURL = "/aws";

}


// get or load data for a particular genre
_PaneModel.prototype.loadGenre = function(id, callback, childQuery){
    var genre = this.getGenre(id);
	
	// if no children, then get data from server
    if (!genre.children) {
        this.callback = callback;
        this._serverGenreLookup(id);
    }
    else {
   		// children already loaded so just do callback
	    this.currentGenre.id = genre.id;
	    if (genre.name == "All Styles" || genre.name == "MP3 Albums") genre.name = "All Genres";
	    this.currentGenre.name = genre.name;
	    callback(genre);
	}
    
    // broadcast to any observers
    this.broadcast("genreChange")
}

/*
// UNFINISHED: confirm all genre's children have themselves children arrays, whether empty or populated.
_PaneModel.prototype.confirmChildren = function(genre){

	// collect childless 
	var childless = [];
	
	$.each(genre.children, function(){
		if(this.children == undefined){
			childless.push(this);
		}
	});
	
	// if no childless, then broadcast change
	
	// start processing childless queries
	this.processNextChildQuery(childless, 0);
	
}


_PaneModel.prototype.processNextChildQuery = function(childless, i){
	var me = this;
	if(++i < childless.length){
		var genre = childless[i];
		this.loadGenre(genre.id, function(){
			me.processNextChildQuery(childless, i);
		})
	} else {
		// finished processing all childless so just 
	}
}

*/

_PaneModel.prototype.getCurrentGenre = function(){
    return this.currentGenre;
}

_PaneModel.prototype.setCurrentGenre = function(aGenre){
    this.currentGenre.name = aGenre.name;
	this.currentGenre.id = aGenre.id;
}

// load info for this genre id from server
_PaneModel.prototype._serverGenreLookup = function(id){
    // if local, then just load sample file, else load from server
    var me = this;
    if (document.URL.substr(0, 5) == "file:") {
        $.get("sampleBrowseNodeLookup.xml", function(xml){
            me._parseGenreInfo(id, xml);
        });
    }
    else {
        $.get(me.AWSURL, {
            Operation: "BrowseNodeLookup",
            BrowseNodeId: id,
            Version: "2008-06-26"
        }, function(xml){
            me._parseGenreInfo(id, xml);
        });
    }
}

// finished loading so parse the info into proper storage and do callback
_PaneModel.prototype._parseGenreInfo = function(id, xml){
    // if string, parse to xml (used for testing with local files)
    if (typeof(xml) == "string") 
        xml = this._parseXMLString(xml);
    
    // keep current genre for external queries of current genre
    this.currentGenre = {
        id: id,
        name: $("BrowseNode>Name", xml).eq(0).text()
    };
    
    // store this genre with empty children array
    var children = [];
    this._updateGenre(id, {
        id: id,
        name: this.currentGenre.name,
        children: children
    });
    
    // populate the children array
    var me = this;
    var cid; // for child ids
    $("Children>BrowseNode", xml).each(function(i){
        cid = $("BrowseNodeId", $(this)).eq(0).text();
        // add child id to children array
        children.push({
            "id": cid,
            "name": $("Name", $(this)).eq(0).text()
        });
        
        // update/create stored child genre with the parent id for future "back" commands
        me._updateGenre(cid, {
            parent: id
        });
    });
    
    // do callback
    this.callback(this.getGenre(id));
}


// update stored genre object with new properties.  Create object if non-existent.
_PaneModel.prototype._updateGenre = function(id, props){
    var genre = this.getGenre(id);
    for (var prop in props) { // TODO use jQuery extend
        genre[prop] = props[prop]
    }
}


// retrieve genre object from storage. Create a new one if undefined.
_PaneModel.prototype.getGenre = function(id){
    var genre = this.genres["id" + id];
    if (!genre) {
        genre = {};
        this.genres["id" + id] = genre;
    }
    // name genre by its parent's name if it is called "General"
    if (genre.name == "General") genre.name = this.getGenre(genre.parent).name + " General";
	if (genre.name == "Compilations") genre.name = this.getGenre(genre.parent).name + " Compilations";
    return genre;
}


// in case of local file system testing, parse a string of xml data into proper xml object
_PaneModel.prototype._parseXMLString = function(s){
    var doc;
    if (window.ActiveXObject) {
        doc = new ActiveXObject('Microsoft.XMLDOM');
        doc.async = 'false';
        doc.loadXML(s);
    }
    else {
        doc = (new DOMParser()).parseFromString(s, 'text/xml');
    }
    return doc;
    // return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
}

