/**
    @script         QuickDegreeFinder.jquery.js
    @author         Eric Clemmons <eclemmons@collegedegrees.com>
    @framework      jQuery 1.3.2+
    @description    Provide functional, objectified & parameterized API that
                    allows customization of existing & dynamic HTML
                    QuickDegreeFinder forms.
                    
    @examples
                    ## Functional
                    
                    new QuickDegreeFinder().populateDegrees("#degrees")
                                           .populateCategories("#categories")
                                           .populateSubjects("#subjects")
                                           .setDefaultDegree("Master's")
                                           .setDefaultCategory("Business")
                                           .limitSubjects("Criminal Justice", "Law", "Paralegal")
                                     
                    ## Objectified
                    
                    new QuickDegreeFinder().populate({
                        'degrees':      "#degrees",
                        'categories':   "#categories",
                        'subjects':     "#subjects"
                    }).setDefaults({
                        'degree':       "Master's",
                        'category':     "Business"
                    });
                    
                    ## Parameterized
                    
                    new QuickDegreeFinder().populate("#degrees", "#categories", "#subjects");
                                           .setDefaults("Master's", "Business");
                    
                    ## Other functions
                    
                    QuickDegreeFinder.loadTemplate('/wp-includes/qdf.html')
                                     .loadStyles('/wp-includes/qdf.css')
                                     .onReady(function() {
                                        jQuery("#degrees").hide();
                                     });
*/

;

var QuickDegreeFinder = function(settings) {
    
    // The structure of the defaults, elements, & their filters
    var _getStruct = function(degrees, categories, subjects) {
        return {
            'degrees':      degrees,
            'categories':   categories,
            'subjects':     subjects
        };
    };
    
    var _options = {
        dataUrl:    'https://search.collegedegrees.com/feeds/searchwidget/edudirectsearchoptions',
        defaults:   _getStruct(),
        elements:   _getStruct(),
        excludes:   _getStruct(),
        limits:     _getStruct(),
        onData:     function() {},
        onReady:    function() {}
    };
    
    jQuery.extend(_options, settings);
    
    this.exclude = function(excludes, categories, subjects) {
        if (typeof excludes !== 'object') {
            var excludes = _getStruct(excludes, categories, subjects);
        };
        
        this.setExcludes(excludes);
    };
    
    this.excludeDegrees = function(degrees) {
        this.exclude({ 'degrees': arguments });
        
        return this;
    };
    
    this.excludeCategories = function(categories) {
        this.exclude({ 'categories': arguments });
        
        return this;
    };
    
    this.excludeSubjects = function(subjects) {
        this.exclude({ 'subjects': arguments });
        
        return this;
    };
    
    this.getData = function(key) {
        var data = QuickDegreeFinder.data;
        
        return key ? data[key] : data;
    };
    
    this.getDefaults = function(type) {
        return this.getOptions('defaults', type);
    };
    
    this.getElements = function(type) {
        return this.getOptions('elements', type);
    };
    
    this.getExcludes = function(type) {
        return this.getOptions('excludes', type);
    };
    
    this.getLimits = function(type) {
        return this.getOptions('limits', type);
    };
    
    this.getOptions = function(parent, type) {
        var data = _options[parent];
        
        return type
        ? data[type]
        : data;
    };
    
    this._init = function() {
        this._initDegrees();
        this._initDefaults();
        
        _options.onReady.call(this, _options);
        
        return this;
    };
    
    this._initDefaults = function(selectType) {
        for (var type in this.getDefaults()) {
            // Skip other types if we only want to set the default for 'categories'
            if (selectType && type !== selectType) {
                continue;
            };
            
            var value = this.getDefaults(type);
            
            if (value) {
                var value = value.replace(/(\'|\")/g, '\\$1');
                var selects = this.getElements(type);
                
                var selector = 'option:contains("' + value + '"), option[value="' + value + '"]';
                
                try {
                    jQuery(selector, selects).attr('selected', 'selected')
                                             .trigger('change');
                } catch(e) {
                    // Couldn't find the element, and we need to fail gracefully
                };
            };
        };
    };
    
    this._initDegrees = function() {
        this._initSelect(
            this.getElements('degrees'),
            this.getData('degrees'),
            this._initCategories
        );
        this._removeExcludes('degrees');
    };
    
    this._initCategories = function(event) {
        var degreeId = jQuery(event.currentTarget).val();
        var categories = {};
        
        jQuery.grep(this.getData('degrees')[degreeId].categories, jQuery.proxy(function(id) {
            categories[id] = this.getData('categories')[id];
        }, this));
        
        var index = jQuery.inArray(event.currentTarget, this.getElements('degrees'));
        var select = this.getElements('categories').eq(index);
        
        this._initSelect(
            select,
            categories,
            this._initSubjects
        );
        this._removeExcludes('categories');
        this._sortSelect(select);
        
        this._initDefaults('categories');
    };
    
    this._initSubjects = function(event) {
        var categoryId = jQuery(event.currentTarget).val();
        var subjects = this.getData('categories')[categoryId].subjects;
        
        var index = jQuery.inArray(event.currentTarget, this.getElements('categories'));
        
        var degreeId = this.getElements('degrees').eq(index).val();
        
        jQuery.each(subjects, function(i, subject) {
            var isAvailable = jQuery.inArray(parseInt(degreeId, 10), subject.degree_levels) >= 0;
            
            if (!isAvailable) {
                delete subjects[i];
            };
        });
        
        var select = this.getElements('subjects').eq(index);
        
        this._initSelect(select, subjects);
        this._removeExcludes('subjects');
        this._sortSelect(select);
        
        this._initDefaults('subjects');
    };
    
    this._initSelect = function(select, data, callback) {
        var previous = select.find('option:selected');
        
        select.removeAttr('disabled').find('option:gt(0)').remove();
        
        for (var id in data) {
            // The data may have been excluded
            if (data[id]) {
                select.append('<option value="' + data[id].id + '">' + data[id].name + '</option>');
            };
        };
        
        select.find('option:contains("' + previous.text() + '")')
              .attr('selected', 'selected');
        
        select.change( jQuery.proxy(callback, this) );
    };
    
    this.limit = function(degrees, categories, subjects) {
        var limits = (typeof degrees !== 'object')
                   ? _getStruct(degrees, categories, subjects)
                   : degrees;
        
        this.setLimits(degrees);
        
        return this;
    };
    
    this.limitDegrees = function(degrees) {
        return this.limit({ 'degrees': arguments });
    };
    
    this.limitCategories = function(categories) {
        return this.limit({ 'categories': arguments });
    };
    
    this.limitSubjects = function(subjects) {
        return this.limit({ 'subjects': arguments });
    };
    
    this.loadData = function(url) {
        // jQuery appends =TIMESTAMP to script URLs for caching,
        // which results in a 404.
        var script = document.createElement('script');
        script.src = url || this.getOptions('dataUrl');
        document.getElementsByTagName('head')[0].appendChild(script);
        
        return this;
    };
    
    this.loadStyles = function(url) {
        jQuery('head').append('<link rel="stylesheet" href="' + url + '" />');
        
        return this;
    };
    
    this.loadTemplate = function(url) {
        return this;
    };
    
    this.onData = function(callback) {
        _options.onData = callback;
        
        return this;
    };
    
    this.onReady = function(callback) {
        _options.onReady = callback;
        
        return this;
    };
    
    /**
        Populate specified elements with appropriate data.
        
        @param  Object|String   Key/Value pair of degree -> selector; categories -> selector
                                or subjects -> selector; Or degree selector.
        @param  String          (Optional) Category selector
        @param  String          (Optional) Subjects selector
        @return Object          this
    */
    
    this.populate = function(elements, cElements, sElements) {
        if (typeof elements !== 'object') {
            var elements = _getStruct(elements, cElements, sElements);
        };
        
        this.setElements(elements);
        
        return this;
    };
    
    this.populateDegrees = function(element) {
        return this.populate({ 'degrees': element});
    };
    
    this.populateCategories = function(element) {
        return this.populate({ 'categories': element});
    };
    
    this.populateSubjects = function(element) {
        return this.populate({ 'subjects': element});
    };
    
    this._removeExcludes = function(type) {
        var selects = this.getElements(type);
        
        var limits = this.getLimits(type);
        
        if (limits) {
            var limited = [];
            jQuery.each(limits, function(i, limit) {
                limited = jQuery.merge(limited, selects.find('option:contains("' + limit + '")'));
            });
            
            selects.each(function(i, select) {
                var options = jQuery(select).find('option:gt(0)');
                
                options.not(limited).remove();
            });
        };
        
        var excludes = this.getExcludes(type);
        
        if (!excludes) {
            return;
        };
        
        jQuery.each(excludes, function(i, exclude) {
            selects.find('option:contains("' + exclude + '")').remove();
        });
    };
    
    this.setData = function(json) {
        // Data has to be set outside of the instance,
        // otherwise we'll continually reload it.
        QuickDegreeFinder.data = jQuery.extend(QuickDegreeFinder.data, json);
        
        _options.onData.call(this, _options);
        
        this._init();
        
        return this;
    };
    
    /**
        Set default values for specified elements to specified value
        
        @param  Object|String   Key/value pair of degree -> default; category -> default
                                or subject -> default; Or degree default.
        @param  String          (Optional) Category default
        @param  String          (Optional) Subject default
    */
    
    this.setDefaults = function(defaults, cDefault, sDefault) {
        if (typeof defaults !== 'object') {
            var defaults = _getStruct(defaults, cDefault, sDefault);
        };
        
        jQuery.extend(_options.defaults, defaults);
        
        return this;
    };
    
    this.setDefaultDegree = function(value) {
        return this.setDefaults({ degrees: value });
    };
    
    this.setDefaultCategory = function(value) {
        return this.setDefaults({ categories: value });
    };
    
    this.setDefaultSubject = function(value) {
        return this.setDefaults({ subjects: value });
    };
    
    this.setElements = function(elements) {
        jQuery.extend(_options.elements, elements);
        
        for (var i in _options.elements) {
            _options.elements[i] = jQuery(_options.elements[i]).attr('disabled', 'disabled');
        };
        
        return this;
    };
    
    this.setExcludes = function(excludes) {
        jQuery.extend(_options.excludes, excludes);
        
        return this;
    };
    
    this.setLimits = function(limits) {
        jQuery.extend(_options.limits, limits);
    };
    
    this._sortSelect = function(select) {
        var options = jQuery.makeArray(select.find('option'));
        
        var sorted = options.sort(function(a, b) {
            return (jQuery(a).text() > jQuery(b).text()) ? 1 : -1;
        });
        
        select.append(jQuery(sorted))
              .attr('selectedIndex', 0);
        
        return select;
    };
    
    // Load data if it does not already exist
    if (!QuickDegreeFinder.data) {
        this.loadData();
    };
    
    // Until a new backend is setup, we have to alias this function
    edudirectsearchoptions = jQuery.proxy(function(json) {
        var keys = ['degree_levels', 'categories'];
        var data = {};

        for (var key in keys) {
            var name = keys[key];
            data[name] = {};
    
            // Make arrays associative objects
            for (var i in json[name]) {
                var value = json[name][i];
                data[name][value.id] = value;
            };
        };

        this.setData({
            'degrees':      data.degree_levels,
            'categories':   data.categories
        });
    }, this);
    
    return {
        exclude:                this.exclude,
        excludeDegrees:         this.excludeDegrees,
        excludeCategories:      this.excludeCategories,
        excludeSubjects:        this.excludeSubjects,
        getData:                this.getData,
        getDefaults:            this.getDefaults,
        getExcludes:            this.getExcludes,
        getLimits:              this.getLimits,
        getOptions:             this.getOptions,
        limit:                  this.limit,
        limitDegrees:           this.limitDegrees,
        limitCategories:        this.limitCategories,
        limitSubjects:          this.limitSubjects,
        loadData:               this.loadData,
        loadStyles:             this.loadStyles,
        loadTemplate:           this.loadTemplate,
        onData:                 this.onData,
        onReady:                this.onReady,
        populate:               this.populate,
        populateDegrees:        this.populateDegrees,
        populateCategories:     this.populateCategories,
        populateSubjects:       this.populateSubjects,
        setData:                this.setData,
        setDefaults:            this.setDefaults,
        setDefaultDegree:       this.setDefaultDegree,
        setDefaultCategory:     this.setDefaultCategory,
        setDefaultSubject:      this.setDefaultSubject,
        setElements:            this.setElements,
        setExcludes:            this.setExcludes,
        setLimits:              this.setLimits
    };
};

QuickDegreeFinder.data = null;
