'use strict';

/*@
Ox.isArguments <f> Tests if a value is an arguments "array"
    (value) -> <b> True if the value is an arguments "array"
    value <*> Any value
    > Ox.isArguments((function() { return arguments; }()))
    true
@*/
Ox.isArguments = /MSIE/.test(navigator.userAgent) ? function(val) {
    return !!(val && Object.hasOwnProperty.call(val, 'callee'));
} : function(val) {
    return !!(val && val.toString() == '[object Arguments]');
}

/*@
Ox.isArray <f> Tests if a value is an array
    (value) -> <b> True if the value is a date
    value <*> Any value
    > Ox.isArray([])
    true
    > Ox.isArray((function() { return arguments; }()))
    false
    > Ox.isArray({0: 0, length: 1})
    false
@*/
Ox.isArray = function(val) {
    return val instanceof Array;
}

/*@
Ox.isBoolean <f> Tests if a value is boolean
    (value) -> <b> True if the value is boolean
    value <*> Any value
    > Ox.isBoolean(false)
    true
@*/
Ox.isBoolean = function(val) {
    return typeof val == 'boolean';
};

/*@
Ox.isDate <f> Tests if a value is a date
    (value) -> <b> True if the value is a date
    value <*> Any value
    > Ox.isDate(new Date())
    true
@*/
Ox.isDate = function(val) {
    return val instanceof Date;
};

/*@
Ox.isElement <f> Tests if a value is a DOM element
    (value) -> <b> True if the value is a DOM element
    value <*> Any value
    > Ox.isElement(document.createElement())
    true
@*/
Ox.isElement = function(val) {
    return !!(val && val.nodeType == 1);
};

/*@
Ox.isEqual <function> Returns true if two values are equal
    > Ox.isEqual((function() { return arguments; }()), (function() { return arguments; }()))
    true
    > Ox.isEqual([1, 2, 3], [1, 2, 3])
    true
    > Ox.isEqual([1, 2, 3], [3, 2, 1])
    false
    > Ox.isEqual(false, false)
    true
    > Ox.isEqual(new Date(0), new Date(0))
    true
    > Ox.isEqual(new Date(0), new Date(1))
    false
    > Ox.isEqual(document.createElement('a'), document.createElement('a'))
    true
    > Ox.isEqual(document.createElement('a'), document.createElement('b'))
    false
    > Ox.isEqual(function(a) { return a; }, function(a) { return a; })
    true
    > Ox.isEqual(function(a) { return a; }, function(b) { return b; })
    false
    > Ox.isEqual(Infinity, Infinity)
    true
    > Ox.isEqual(-Infinity, Infinity)
    false
    > Ox.isEqual(NaN, NaN)
    false
    > Ox.isEqual(0, 0)
    true
    > Ox.isEqual({}, {})
    true
    > Ox.isEqual({a: 1, b: 2, c: 3}, {c: 3, b: 2, a: 1})
    true
    > Ox.isEqual({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}})
    true
    > Ox.isEqual(/ /, / /)
    true
    > Ox.isEqual(/ /g, / /i)
    false
    > Ox.isEqual('', '')
    true
    > Ox.isEqual(void 0, void 0)
    true
@*/
Ox.isEqual = function(a, b) {
    var isEqual = false, type = Ox.typeOf(a);
    if (a === b) {
        isEqual = true;
    } else if (type == Ox.typeOf(b)) {
        if (a == b) {
            isEqual = true;
        } else if (type == 'date') {
            isEqual = a.getTime() == b.getTime();
        } else if (type == 'element') {
            isEqual = a.isEqualNode(b);
        } else if (type == 'function') {
            // fixme: this doesn't do it
            // isEqual = a.toString() == b.toString();
        } else if (type == 'regexp') {
            isEqual = a.global == b.global && a.ignore == b.ignore
                && a.multiline == b.multiline && a.source == b.source;
        } else if (
            ['arguments', 'array', 'object'].indexOf(type) > -1
            && Ox.len(a) == Ox.len(b)
        ) {
            isEqual = true;
            Ox.forEach(a, function(v, k) {
                isEqual = Ox.isEqual(v, b[k]);
                return isEqual;
            });
        }
    }
    return isEqual;
};

/*@
Ox.isFunction <f> Tests if a value is a function
    (value) -> <b> True if the value is a function
    value <*> Any value
    > Ox.isFunction(function() {})
    true
    > Ox.isFunction(/ /)
    false
@*/
Ox.isFunction = function(val) {
    return typeof val == 'function' && !Ox.isRegExp(val);
};

/*@
Ox.isInfinity <f> Tests if a value is infinite
    (value) -> <b> True if the value is infinite
    value <*> Any value
    > Ox.isInfinity(Infinity)
    true
    > Ox.isInfinity(-Infinity)
    true
    > Ox.isInfinity(NaN)
    false
@*/
Ox.isInfinity = function(val) {
    return typeof val == 'number' && !isFinite(val) && !Ox.isNaN(val);
};

/*@
Ox.isNaN <f> Tests if a value is "Not a Number"
    (value) -> <b> True if the value is "Not a Number"
    value <*> Any value
    > Ox.isNaN(NaN)
    true
@*/

Ox.isNaN = function(val) {
    return val !== val;
}

/*@
Ox.isNull <f> Tests if a value is <code>null</code>
    (value) -> <b> True if the value is <code>null</null>
    value <*> Any value
    > Ox.isNull(null)
    true
@*/
Ox.isNull = function(val) {
    return val === null;
};

/*@
Ox.isNumber <f> Tests if a value is a number
    (value) -> <b> True if the value is a number
    value <*> Any value
    > Ox.isNumber(0)
    true
    > Ox.isNumber(Infinity)
    false
    > Ox.isNumber(-Infinity)
    false
    > Ox.isNumber(NaN)
    false
@*/

Ox.isNumber = function(val) {
    return typeof val == 'number' && isFinite(val);
};

/*@
Ox.isObject <f> Tests if a value is a an object
    (value) -> <b> True if the value is an object
    value <*> Any value
    > Ox.isObject({})
    true
    > Ox.isObject([])
    false
    > Ox.isObject(new Date())
    false
    > Ox.isObject(null)
    false
    > Ox.isObject(/ /)
    false
@*/

Ox.isObject = function(val) {
    return typeof val == 'object' && !Ox.isArguments(val)
        && !Ox.isArray(val) && !Ox.isDate(val)
        && !Ox.isNull(val) && !Ox.isRegExp(val);
};

/*@
Ox.isRegExp <f> Tests if a value is a regular expression
    (value) -> <b> True if the value is a regular expression
    value <*> Any value
    > Ox.isRegExp(/ /)
    true
@*/

Ox.isRegExp = function(val) {
    return val instanceof RegExp;
};

/*@
Ox.isString <f> Tests if a value is a string
    (value) -> <b> True if the value is a string
    value <*> Any value
    > Ox.isString('')
    true
@*/

Ox.isString = function(val) {
    return typeof val == 'string';
};

/*@
Ox.isUndefined <f> Tests if a value is undefined
    (value) -> <b> True if the value is undefined
    value <*> Any value
    > Ox.isUndefined()
    true
@*/

Ox.isUndefined = function(val) {
    // fixme: val === void 0 is also nice
    return typeof val == 'undefined';
};

/*@
Ox.typeOf <f> Returns the type of a value
    (value) -> <s> type
    value <*> Any value
    # Examples
    > (function() { return Ox.typeOf(arguments); }())
    "arguments"
    > Ox.typeOf([])
    "array"
    > Ox.typeOf(false)
    "boolean"
    > Ox.typeOf(new Date())
    "date"
    > Ox.typeOf(document.createElement())
    "element"
    > Ox.typeOf(function() {})
    "function"
    > Ox.typeOf(Infinity)
    "infinity"
    > Ox.typeOf(NaN)
    "nan"
    > Ox.typeOf(null)
    "null"
    > Ox.typeOf(0)
    "number"
    > Ox.typeOf({})
    "object"
    > Ox.typeOf(/ /)
    "regexp"
    > Ox.typeOf('')
    "string"
    > Ox.typeOf()
    "undefined"
@*/

Ox.typeOf = function(val) {
    var ret;
    Ox.forEach(Ox.TYPES, function(type) {
        if (Ox['is' + type](val)) {
            ret = type.toLowerCase();
            return false;
        }
    });
    return ret;
};