/**
* @for DOM
* @module dom
*/
var documentElement = Y.config.doc.documentElement,
    Y_DOM = Y.DOM,
    TAG_NAME = 'tagName',
    OWNER_DOCUMENT = 'ownerDocument',
    EMPTY_STRING = '',
    addFeature = Y.Features.add,
    testFeature = Y.Features.test;

Y.mix(Y_DOM, {
    /**
     * Returns the text content of the HTMLElement.
     * @method getText
     * @param {HTMLElement} element The html element.
     * @return {String} The text content of the element (includes text of any descending elements).
     */
    getText: (documentElement.textContent !== undefined) ?
        function(element) {
            var ret = '';
            if (element) {
                ret = element.textContent;
            }
            return ret || '';
        } : function(element) {
            var ret = '';
            if (element) {
                ret = element.innerText || element.nodeValue; // might be a textNode
            }
            return ret || '';
        },

    /**
     * Sets the text content of the HTMLElement.
     * @method setText
     * @param {HTMLElement} element The html element.
     * @param {String} content The content to add.
     */
    setText: (documentElement.textContent !== undefined) ?
        function(element, content) {
            if (element) {
                element.textContent = content;
            }
        } : function(element, content) {
            if ('innerText' in element) {
                element.innerText = content;
            } else if ('nodeValue' in element) {
                element.nodeValue = content;
            }
    },

    CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
        'for': 'htmlFor',
        'class': 'className'
    } : { // w3c
        'htmlFor': 'for',
        'className': 'class'
    },

    /**
     * Provides a normalized attribute interface.
     * @method setAttribute
     * @param {HTMLElement} el The target element for the attribute.
     * @param {String} attr The attribute to set.
     * @param {String} val The value of the attribute.
     */
    setAttribute: function(el, attr, val, ieAttr) {
        if (el && attr && el.setAttribute) {
            attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
            el.setAttribute(attr, val, ieAttr);
        }
        else { Y.log('bad input to setAttribute', 'warn', 'dom'); }
    },


    /**
     * Provides a normalized attribute interface.
     * @method getAttribute
     * @param {HTMLElement} el The target element for the attribute.
     * @param {String} attr The attribute to get.
     * @return {String} The current value of the attribute.
     */
    getAttribute: function(el, attr, ieAttr) {
        ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
        var ret = '';
        if (el && attr && el.getAttribute) {
            attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
            // BUTTON value issue for IE < 8
            ret = (el.tagName === "BUTTON" && attr === 'value') ? Y_DOM.getValue(el) : el.getAttribute(attr, ieAttr);

            if (ret === null) {
                ret = ''; // per DOM spec
            }
        }
        else { Y.log('bad input to getAttribute', 'warn', 'dom'); }
        return ret;
    },

    VALUE_SETTERS: {},

    VALUE_GETTERS: {},

    getValue: function(node) {
        var ret = '', // TODO: return null?
            getter;

        if (node && node[TAG_NAME]) {
            getter = Y_DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];

            if (getter) {
                ret = getter(node);
            } else {
                ret = node.value;
            }
        }

        // workaround for IE8 JSON stringify bug
        // which converts empty string values to null
        if (ret === EMPTY_STRING) {
            ret = EMPTY_STRING; // for real
        }

        return (typeof ret === 'string') ? ret : '';
    },

    setValue: function(node, val) {
        var setter;

        if (node && node[TAG_NAME]) {
            setter = Y_DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
            val = (val === null) ? '' : val;
            if (setter) {
                setter(node, val);
            } else {
                node.value = val;
            }
        }
    },

    creators: {}
});

addFeature('value-set', 'select', {
    test: function() {
        var node = Y.config.doc.createElement('select');
        node.innerHTML = '<option>1</option><option>2</option>';
        node.value = '2';
        return (node.value && node.value === '2');
    }
});

if (!testFeature('value-set', 'select')) {
    Y_DOM.VALUE_SETTERS.select = function(node, val) {
        for (var i = 0, options = node.getElementsByTagName('option'), option;
                option = options[i++];) {
            if (Y_DOM.getValue(option) === val) {
                option.selected = true;
                //Y_DOM.setAttribute(option, 'selected', 'selected');
                break;
            }
        }
    };
}

Y.mix(Y_DOM.VALUE_GETTERS, {
    button: function(node) {
        return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
    }
});

Y.mix(Y_DOM.VALUE_SETTERS, {
    // IE: node.value changes the button text, which should be handled via innerHTML
    button: function(node, val) {
        var attr = node.attributes.value;
        if (!attr) {
            attr = node[OWNER_DOCUMENT].createAttribute('value');
            node.setAttributeNode(attr);
        }

        attr.value = val;
    }
});


Y.mix(Y_DOM.VALUE_GETTERS, {
    option: function(node) {
        var attrs = node.attributes;
        return (attrs.value && attrs.value.specified) ? node.value : node.text;
    },

    select: function(node) {
        var val = node.value,
            options = node.options;

        if (options && options.length) {
            // TODO: implement multipe select
            if (node.multiple) {
                Y.log('multiple select normalization not implemented', 'warn', 'DOM');
            } else if (node.selectedIndex > -1) {
                val = Y_DOM.getValue(options[node.selectedIndex]);
            }
        }

        return val;
    }
});
