/**
 * @brief Column Browser
 * @author Andrew Holloway aholloway@mintel.com
 * @date 2009-11-24
 * @version 1.0.0
 * @requires prototype.js Uses several objects and extensions provided by Prototype.
 */

/**
 * @class Browser
 * @brief This object is the base for the browser object. All columns, and sub-
 * sequently, all column items, exist within this container object. Also,
 * any custom event functions refer to this as 'this' (the base of all the
 * objects within it).
 *
 */
var Browser = Class.create({
    /**
     * @member Browser
     * @constructor Browser
     * @brief Initialize the Browser Object with the following fields
     * @argument title {string} the title of the widget on-screen
     * @argument browserContainer {string} the display element where the widget will be loaded
     * @argument autoUpdate {bool} determines whether or not the display is updated automatically
     *              or when requested.
     *
     * @returns nothing (this is called automatically)
     */
    initialize: function(title, browserContainer, autoUpdate) {
        this.browserTitle = title;
        this.containerId = browserContainer;
        this.updateOnChange = autoUpdate;
        this.columns = [];
    },
    /**
     * @member Browser
     * @brief Add a column object to the browser.
     * @argument columnToAdd {Column} 
     *
     * @returns {int} the count of columns in the browser object
     */
    addColumn: function(columnToAdd) {
        var count = this.columns.push(columnToAdd);
        if (this.autoUpdate) this.updateColumns();
        return count;
    },
    /**
     * @member Browser
     * @brief Gets the values from the columns (that have one) as a hash-type object.
     *
     * @returns {object} hash of all column values
     */
    getColumnValues: function() {
        var i = 0,
            params = {};
        
        for(; i < this.columns.length; i++) {
            if (this.columns[i].value != null) {
                if (!params.hasOwnProperty(this.columns[i].columnContainerId)) {
                    params[this.columns[i].columnContainerId] = 0;
                }
                params[this.columns[i].columnContainerId] = this.columns[i].value;
            }
        }
        
        return params;
    },
    /**
     * @member Browser
     * @brief Draws the browser to the screen, in the specified container. It calls
     * update on all dependent items by simply updating each column.
     *
     * @returns nothing
     */
    draw: function () {
        var container = $(this.containerId);
            container.addClassName("browser");
            container.update();
            
        var h2 = new Element("h2", {"class": "title"});
            h2.update(this.browserTitle);
        
        container.appendChild(h2);
        
        var columnContainer = new Element("ul", {"id": this.containerId+"_columnContainer"});
        
        for(var i = 0; i < this.columns.length; i++) {
            columnContainer.appendChild(this.columns[i].render());
            if (this.columns[i].columnContainerClass == "last") {
                var breaker = new Element("br");
                    breaker.setStyle({clear: "both"});
                    
                columnContainer.appendChild(breaker);
            }
        }
        
        container.appendChild(columnContainer);
        this.updateColumns();
        
    },
    /**
     * @member Browser
     * @brief This updates the columns separate from the rendering phase, which handles
     * other parts as well as updating the columns.
     *
     * @returns nothing
     */
    updateColumns: function () {
        for(var i = 0; i < this.columns.length; i++) {
            this.columns[i].updateList();
            this.columns[i].updateComplete();
        }
    }
});

/**
 * @class Column
 * 
 * @brief This object is for the columns, and should be inside the Browser object.
 * Columns have state and labeling information, and lists of items inside them.
 */
var Column = Class.create({
    /**
     * @member Column
     * @brief Initialize the column with the following details
     * @constructor
     * @argument label {string} the label for the particular column
     * @argument columnContainerId {string} a coded name for the column (no spaces, special characters, etc.)
     * @argument state {string} determines whether the column is enabled or disabled to start
     * @argument position {string} is this column supposed to be the first or last column (or both?)
     *
     * @returns nothing
     */
    initialize: function(label, columnContainerId, state, position) {
        /// draw the default column with no items except a default message if specified
        this.value = null;
        
        this.enabled = ((state=="enabled") ? "enabled-list":"disabled-list");
        this.label = label;
        this.columnContainerId = columnContainerId;
        
        if (typeof position != "undefined")
            this.columnContainerClass = position;
        else
            this.columnContainerClass = "";
        
        this.items = [];
        
    },
    /**
     * @member Column
     * @brief change the label for a given column
     * @argument newLabel the label to replace the existing one
     *
     * @returns {string} the old label
     */
    setLabel: function(newLabel) {
        var oldLabel = this.label;
        this.label = newLabel;
        return oldLabel;
    },
    /**
     * @member Column
     * @brief Add an item to the given column
     * @argument itemToAdd {ColumnItem} the item being added to the Column
     *
     * @returns {int} the number of items in the column
     */
    addItem: function(itemToAdd) {
        itemToAdd.machineName = this.columnContainerId.toLowerCase().replace(" ", "_") + "__" + itemToAdd.machineName;
        return this.items.push(itemToAdd);
    },
    /**
     * @member Column
     * @brief Remove all items from a Column's list of items
     *
     * @returns {int} the number of items in the Column (which should be zero)
     */
    clearItems: function() {
        this.items = [];
        this.value = null;
        return this.items.length;
    },
    /**
     * @member Column
     * @brief Set the Column to be enabled
     *
     * @returns nothing
     */
    enable: function() {
        this.enabled = "enabled-list";
    },
    /**
     * @member Column
     * @brief Set the Column to be disabled
     *
     * @returns nothing
     */
    disable: function() {
        this.enabled = "disabled-list";
    },
    /** 
     * @member Column
     * @brief Draws the shell around the list that will be updated. should have the DOM
     * structure like the following:
     *  <li class="${this.enabled} list">
     *      <h3>${this.label}</h3>
     *      <ul id="${this.columnContainerId}" class="${this.columnContainerClass}">
     *          ...<!-- ColumnItems for this Column -->
     *      </ul>
     *  </li>
     *
     *  @returns {DOMElement} the outer list item where the contents will be
     */
    render: function() {
        var outerLi = new Element("li", {"class": this.enabled});
        var h3      = new Element("h3").update(this.label);
        var innerUl = new Element("ul", {"id": this.columnContainerId, "class": this.columnContainerClass});
        
        outerLi.appendChild(h3);
        outerLi.appendChild(innerUl);
        
        return outerLi;
    },
    /**
     * @member Column
     * This will update the content of the Column's list of ColumnItems. While
     * processing, it will set a loading class, which can be styled appropriately.
     *
     * @returns nothing
     */
    updateList: function() {
        var myUl = $(this.columnContainerId),
            i = 0;
            myUl.update();
        
        // reset value
        this.value = null;
        
        // Set the label
        $$('#'+this.columnContainerId)[0].previous("h3").update(this.label);
        
        // Set the UL to have the loading class
        myUl.addClassName("browser-loading");
        
        // Update the enabled/disabled state here
        var outerLi = $(this.columnContainerId).up("li");
            outerLi.removeClassName("disabled-list");
            outerLi.removeClassName("enabled-list");
            outerLi.addClassName(this.enabled);
        
        for (; i < this.items.length; i++) {
            var freshlyAddedElement = myUl.appendChild(this.items[i].render());
            
            // By binding to the current item, we can make 'this' refer to the proper
            // column and items. do this to set the selected state, and any other
            // functions to run.
            freshlyAddedElement.observe("click", (function(event) {
                // unselect all items, and keep the currently selected one
                var clicked = Event.element(event);
                for (var i =0; i < this.items.length; i++) {
                    
                    if (clicked.id == this.items[i].machineName) {
                        this.items[i].selected = true;
                        this.value = this.items[i].value;
                        
                        // This tricky bit is almose equivalent to the commented line below
                        // it binds the function to 'this' then immediately runs the result
                        // with the event as a parameter
                        (this.items[i].eventHandler.bind(this))(event);
                    } else
                        this.items[i].selected = false;
                        
                    this.items[i].updateItem();
                }
                
            }).bindAsEventListener(this));
            
        }
        
    },
    /**
     * @member Column
     * This removes the 'browser-loading' class from the specified column. Paired
     * with the updateList method, which sets the loading class, this can allow
     * AJAX requests to be run asynchronously, and remove the loading class state
     * after it has actually completed.
     *
     * @returns nothing
     */
    updateComplete: function () {
        var myUl = $(this.columnContainerId);
        // Set the UL to have the loading class
        myUl.removeClassName("browser-loading");
        
    }
});

/**
 * @class ColumnItem
 * @brief these items fit inside the columns and contain label, value and function
 * information. When clicked, column items perform various actions, depending
 * on the users' requirements.
 */
var ColumnItem = Class.create({
    /**
     * @member ColumnItem
     * @brief Initializes the ColumnItem with the following information
     * @argument name {string} the name of the item in the Column
     * @argument value {string} the id (or value) associated with the particular ColumnItem
     * @argument isPlaceholder {string} determines if the ColumnItem should have content, or is just around for show.
     * @argument eventHandler {string} custom method to run when the ColumnItem is accessed
     *
     * @returns nothing
     */
    initialize: function(name, value, isPlaceholder, eventHandler) {
        this.name = name;
        
        // Don't store and  use this name
        // It's internal and may get mangled along the way.
        this.machineName = name.toLowerCase().replace(" ", "_") +"__"+value;
        
        this.value = value;
        this.description = "";
        this.isEmpty = isPlaceholder;
        this.selected = false;
        this.eventHandler = eventHandler;
    },
    /**
     * @member ColumnItem
     * @brief This addes description text to the ColumnItem. It can be of any length,
     * or an empty string.
     *
     * @argument descriptionText {string} The text used to describe the column item.
     *
     * @returns nothing
     */
    setDescription: function(descriptionText) {
        this.description = descriptionText;
    },
    /**
     * @member ColumnItem
     * @brief This draws the ColumnItem
     *
     * @returns {DOMElement} the DOM structure for the new ColumnItem
     */
    render: function() {
        var newLi = new Element("li", {"id": this.machineName, "title": this.description});
        if (this.isEmpty) newLi.addClassName("empty");
        newLi.update(this.name);
        (this.selected) ? newLi.addClassName("picked") : newLi.removeClassName("picked");
        
        return newLi;
    },
    /**
     * @member ColumnItem
     * @brief Find, then update, the ColumnItem by its generated machineName
     *
     * @returns nothing
     */
    updateItem: function() {
        var newLi = $(this.machineName);
        (this.selected) ? newLi.addClassName("picked") : newLi.removeClassName("picked");
        
    },
    /**
     * @member ColumnItem
     * @brief Method to run right after a ColumnItem is clicked. Can be set to do nothing
     * (function(){}) or any number of things.
     *
     * @argument event {Event} The event attached to the action being performed.
     *
     * The scope is the parent widget object ('this'). The event refers to the
     * item clicked.
     */
    eventHandler: function(event) {
        /// overwrite this function so that the individual items can run
        alert("Specify a function, which takes 'event' as parameter' for this item");
    }
});
