/**
* DUI: The Digg User Interface Library
*
* Copyright (c) 2008-2009, Digg, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Digg, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @module DUI
* @author Micah Snyder <micah@digg.com>
* @description The Digg User Interface Library
* @version 0.0.4
* @link http://code.google.com/p/digg
*
*/

/* Add Array.prototype.indexOf -- Guess which major browser doesn't support it natively yet? */
[].indexOf || (Array.prototype.indexOf = function(v, n){
    n = (n == null) ? 0 : n; var m = this.length;

    for(var i = n; i < m; i++) {
        if(this[i] == v) return i;
    }

    return -1;
});

(function($) {

/* Create our top-level namespace */
DUI = {
    version: "0.0.4"
};

/**
* @class Class Class creation and management for use with jQuery. Class is a singleton that handles static and dynamic classes, as well as namespaces
*/
DUI.Class = {
    /**
* @var {Array} _dontEnum Internal array of keys to omit when looking through a class' properties. Once the real DontEnum bit is writable we won't have to deal with this.
*/
    _dontEnum: ['_ident', '_dontEnum', 'create', 'namespace', 'ns', 'supers', 'sup', 'each'], //'init' removed for workable this.sup() in child's init() function

    /**
* @function create Make a class! Do work son, do work
* @param {optional Object} methods Any number of objects can be passed in as arguments to be added to the class upon creation
* @param {optional Boolean} static If the last argument is Boolean, it will be treated as the static flag. Defaults to false (dynamic)
*/
    create: function() {
        //Set _this to DUI.Class
        var _this = this;

        //Figure out if we're creating a static or dynamic class
        var s = (arguments.length > 0 && //if we have arguments...
                arguments[arguments.length - 1].constructor == Boolean) ? //...and the last one is Boolean...
                    arguments[arguments.length - 1] : //...then it's the static flag...
                    false; //...otherwise default to a dynamic class

        //Static: Object, dynamic: Function
        var c = s ? {} : function() {
            this.init.apply(this, arguments);
        }

        //All of our classes have these in common
        var methods = {
            _ident: {
                library: "DUI.Class",
                version: "0.0.4",
                dynamic: true
            },

            //_dontEnum should exist in our classes as well
            _dontEnum: this._dontEnum,

            //A basic namespace container to pass objects through
            ns: [],

            //A container to hold one level of overwritten methods
            supers: {},

            //A constructor
            init: function() {},

            //Our namespace function
            namespace:function(ns) {
                //Don't add nothing
                if (!ns) return null;

                //Set _this to the current class, not the DUI.Class lib itself
                var _this = this;

                //Handle ['ns1', 'ns2'... 'nsN'] format
                if(ns.constructor == Array) {
                    //Call namespace normally for each array item...
                    $.each(ns, function() {
                        _this.namespace.apply(_this, [this]);
                    });

                    //...then get out of this call to namespace
                    return;

                //Handle {'ns': contents} format
                } else if(ns.constructor == Object) {
                    //Loop through the object passed to namespace
                    for(var key in ns) {
                        //Only operate on Objects and Functions
                        if([Object, Function].indexOf(ns[key].constructor) > -1) {
                            //In case this.ns has been deleted
                            if(!this.ns) this.ns = [];

                            //Copy the namespace into an array holder
                            this.ns[key] = ns[key];

                            //Apply namespace, this will be caught by the ['ns1', 'ns2'... 'nsN'] format above
                            this.namespace.apply(this, [key]);
                        }
                    }

                    //We're done with namespace for now
                    return;
                }

                //Note: [{'ns': contents}, {'ns2': contents2}... {'nsN': contentsN}] is inherently handled by the above two cases

                var levels = ns.split(".");

                /* Dynamic classes are Functions, so we'll extend their prototype.
Static classes are Objects, so we'll extend them directly */
                var nsobj = this.prototype ? this.prototype : this;

                $.each(levels, function() {
                    /* When adding a namespace check to see, in order:
* 1) Does the ns exist in our ns passthrough object?
* 2) Does the ns already exist in our class
* 3) Does the ns exist as a global var?
* NOTE: Support for this was added so that you can namespace classes
* into other classes, i.e. MyContainer.namespace('MyUtilClass'). this
* behaviour is dangerously greedy though, so it may be removed.
* 4) If none of the above, make a new static class
*/
                    nsobj[this] = _this.ns[this] || nsobj[this] || window[this] || DUI.Class.create(true);

                    /* If our parent and child are both dynamic classes, copy the child out of Parent.prototype and into Parent.
* It seems weird at first, but this allows you to instantiate a dynamic sub-class without instantiating
* its parent, e.g. var baz = new Foo.Bar();
*/
                    if(_this.prototype && DUI.isClass(nsobj[this]) && nsobj[this].prototype) {
                        _this[this] = nsobj[this];
                    }

                    //Remove our temp passthrough if it exists
                    delete _this.ns[this];

                    //Move one level deeper for the next iteration
                     nsobj = nsobj[this];
                });

                //TODO: Do we really need to return this? It's not that useful anymore
                return nsobj;
            },

            /* Create exists inside classes too. neat huh?
* Usage differs slightly: MyClass.create('MySubClass', { myMethod: function() }); */
            create: function() {
                //Turn arguments into a regular Array
                var args = Array.prototype.slice.call(arguments);

                //Pull the name of the new class out
                var name = args.shift();

                //Create a new class with the rest of the arguments
                var temp = DUI.Class.create.apply(DUI.Class, args);

                //Load our new class into the {name: class} format to pass it into namespace()
                var ns = {};
                ns[name] = temp;

                //Put the new class into the current one
                this.namespace(ns);
            },

            //Iterate over a class' members, omitting built-ins
            each: function(cb) {
                if(!$.isFunction(cb)) {
                    throw new Error('DUI.Class.each must be called with a function as its first argument.');
                }

                //Set _this to the current class, not the DUI.Class lib itself
                var _this = this;

                $.each(this, function(key) {
                    if(_this._dontEnum.indexOf(key) != -1) return;

                    cb.apply(this, [key, this]);
                });
            },

            //Call the super of a method
            sup: function() {
                try {
                    var caller = this.sup.caller.name;
                    return this.supers[caller].apply(this, arguments); //added 'return' for getting return from sup()
                } catch(noSuper) {
                    return undefined;   //changed from 'false' to 'undefined' in case when super method not exists
                }
            }
        }

        //Static classes don't need a constructor
        s ? delete methods.init : null;

        //...nor should they be identified as dynamic classes
        s ? methods._ident.dynamic = false : null;

        /* Put default methods into the class before anything else,
         * so that they'll be overwritten by the user-specified ones */
        $.extend(c, methods);

        /* Second copy of methods for dynamic classes: They get our
         * common utils in their class definition AND their prototype */
        if(!s) $.extend(c.prototype, methods);

        //Static: extend the Object, Dynamic: extend the prototype
        var extendee = s ? c : c.prototype;

        //Loop through arguments. If they're the right type, tack them on
        $.each(arguments, function() {
            //Either we're passing in an object full of methods, or the prototype of an existing class
            if(this.constructor == Object || typeof this.init != undefined) {
                /* Here we're going per-property instead of doing $.extend(extendee, this) so that
* we overwrite each property instead of the whole namespace. Also: we omit the 'namespace'
* helper method that DUI.Class tacks on, as there's no point in storing it as a super */
                for(var i in this) { //added 'var' to remove i-binding in to the global scope
                    /* If a property is a function (other than our built-in helpers) and it already exists
* in the class, save it as a super. note that this only saves the last occurrence */
                    if($.isFunction(extendee[i]) && _this._dontEnum.indexOf(i) == -1) {
                        //since Function.name is almost never set for us, do it manually
                        this[i].name = extendee[i].name = i;

                        //throw the existing function into this.supers before it's overwritten
                        extendee.supers[i] = extendee[i];
                    }

                    //Special case! If 'dontEnum' is passed in as an array, add its contents to DUI.Class._dontEnum
                    if(i == 'dontEnum' && this[i].constructor == Array) {
                        extendee._dontEnum = $.merge(extendee._dontEnum, this[i]);
                    }

                    //extend the current property into our class
                    extendee[i] = this[i];
                }
            }
        });

        //Shiny new class, ready to go
        return c;
    }
};

})(jQuery);

//Simple check so see if the object passed in is a DUI Class
DUI.isClass = function(check, type)
{
    type = type || false;

    try {
        if(check._ident.library == 'DUI.Class') {
            if((type == 'dynamic' && !check._ident.dynamic)
               || (type == 'static' && check._ident.dynamic)) {
                return false;
            }

            return true;
        }
    } catch(noIdentUhOh) {
        return false;
    }

    return false;
};
