/*
    Microdata Polyfill
    v0.01

    Implementation of http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#microdata

    microdata consists of a group of name-value pairs.
    The groups are called items, and each name-value pair is a property.
    Items and properties are represented by regular elements

    var cats = document.getItems("http://example.com/feline");
*/

;(function(getItems, $) {

    if(getItems) return;

    function createElementItem(item, type) {
        /*
            5.1.6: Each item is represented in the DOM by the element on which
            the relevant itemscope attribute is found. These elements have
            their element.itemScope IDL attribute set to true.

            The type of items can be obtained using the element.itemType IDL
            attribute on the element with the itemscope attribute.
        */
        item.itemScope = true;
        item.itemType = type;

        /*
            5.1.6: Once an element representing an item has been obtained,
            its properties can be extracted using the properties IDL attribute.
        */
        item.properties = getItemProperties(item, {});
        $(item).data("properties", item.properties);

        return item;
    }

    function getItemProperties(el, properties) {
        var $el = $(el);
        $el.find("*[itemprop]")
            .filter(function(){
                // Filter out properties of nested data
                return $(this).parents("*[itemtype]:first")[0] === $el[0];
            })
            .each(function() {
                var property = this.getAttribute("itemprop"),
                    scope = this.getAttribute("itemscope"),
                    key;
                if(scope !== "") {
                    addProperty(properties, property, getContentValue(this));
                    getItemProperties(this, properties);
                    return true;
                }

                // It's a new microdata object...
                var itemType = this.getAttribute("itemtype");
                if(!itemType) throw "Don't know how to handle - needs to be 'no item type'.";

                var newItem = createElementItem(this, itemType);
                key = addProperty(properties, property, newItem.properties);
                /*
                    5.1.6: The PropertyNodeList object can be used to obtain all the values
                    at once using its getValues method, which returns an array of all the values.
                */
                key.getValues = function() {
                    return key;
                };

                /*
                    5.1.6: It's also possible to get a list of all the property names using the
                    object's "names" IDL attribute.
                */
                // TODO.
            });
        return properties;
    }

    function addProperty(props, key, val) {
        props[key] = props[key] || [];
        props[key].push(val);
        return props[key];
    }

    function getContentValue(el) {
        var $el = $(el);
        /*
        If the element is a meta element
        The value is the value of the element's content attribute, if any, or the empty string if there is no such attribute.
        */
        if($el.attr("content")) return $el.attr("content") || "";

        /*
        If the element is an audio, embed, iframe, img, source, track, or video element
        The value is the absolute URL that results from resolving the value of the element's src attribute relative to the element at the time the attribute is set, or the empty string if there is no such attribute or if resolving it results in an error.

        If the element is an a, area, or link element
        The value is the absolute URL that results from resolving the value of the element's href attribute relative to the element at the time the attribute is set, or the empty string if there is no such attribute or if resolving it results in an error.

        If the element is an object element
        The value is the absolute URL that results from resolving the value of the element's data attribute relative to the element at the time the attribute is set, or the empty string if there is no such attribute or if resolving it results in an error.

        If the element is a time element with a datetime attribute
        The value is the value of the element's datetime attribute.

        Otherwise
        The value is the element's textContent.
        */
        return $el.text();
    }


    /*
        The document.getItems(typeNames) method takes an optional string that
        contains an unordered set of unique space-separated tokens that are
        case-sensitive, representing types. When called, the method must
        return a live NodeList object containing all the elements in the
        document, in tree order, that are each top-level microdata items
        with a type equal to one of the types specified in that argument,
        having obtained the types by splitting the string on spaces.
        If there are no tokens specified in the argument, or if the
        argument is missing, then the method must return a NodeList
        containing all the top-level microdata items in the document.
        When the method is invoked on a Document object again with the
        same argument, the user agent may return the same object as the
        object returned by the earlier call. In other cases, a new NodeList
    */
    getItems = window.document.getItems = function(itemType, el) {
        if(itemType && typeof itemType !== "string"){
          el = itemType;
          itemType = "";
        }
        var allItems = $("*[itemtype]", el || document),
            topLevel = allItems.filter(function(){
                    return $(this).parents("*[itemtype]").length === 0;
                })
                .each(function(idx) {
                    createElementItem(this, this.getAttribute("itemType"));
                });
        if(!itemType || !topLevel.length){
            return topLevel;
        }
        // Filter the top level items
        var itemTypes = itemType.split(" "),
            filteredItems = [];
        $.each(itemTypes, function(idx, itemName){
            $.each(topLevel, function(idx2, item) {
                if(itemName === item.itemType) {
                    filteredItems.push(item);
                }
            });
        });
        return filteredItems;
    };

    function dispatchMicrodata(handlers, doc) {
        var microdata = document.getItems(doc);
        if(!microdata || !handlers) return;
        $.each(microdata, function() {
            var format = $(this).attr("itemType");
            handlers[format] && handlers[format](this);
            !handlers[format] && console.log("No handler for " + format);
        });
    }
    
    this.Microdata = {
        dispatch: dispatchMicrodata,
        getItems: document.getItems,
        handlers: {}
    };
})(document.getItems, jQuery);
