var DEBUG = true;

// trace function remains only to inform users of its deprecated status.
var trace = function (msg) {
    alert("trace() was called please use km.trace instead thanks!");
};

(function (exports) {
    var intersectItems = [];
    $(function () {
        // an intersect item =  {$domItem: {}, handler:{}, intersected: false};	
        // width height x y
        function pointIntersectsRect(pointX, pointY, rectX, rectY, rectWidth, rectHeight) {
            // further left than the right side
            var xBellowRight = pointX < (rectX + rectWidth),
                // higher than bottom
                yBellowBottom = pointY < (rectY + rectHeight),
                // further right than the left side.
                xAboveLeft = (pointX > rectX),
                // bellow the top
                yAboveTop = pointY > (rectY);

            if (xBellowRight && yBellowBottom && xAboveLeft && yAboveTop) {
                return true;
            }
            return false;
        }
        // if the mouse is moved
        // Only do this 30 frames per second.!
        $(document).bind('mousemove', function (e) {
			var i = 0,
				item = null,
				offset = null; 
            // for each of the items that could be intersected
            for (i = 0; i < intersectItems.length; i += 1) {
                item = intersectItems[i];
                // if the item isnt intersected already
                offset = item.$domItem.offset();
                if (pointIntersectsRect(e.pageX, e.pageY, offset.left, offset.top, item.width, item.height)) {
                    if (!intersectItems[i].intersected) {
                        intersectItems[i].intersected = true;
                        intersectItems[i].handlerOnEnter();
                    }
                } else {
                    if (intersectItems[i].intersected) {
                        intersectItems[i].intersected = false;
                        intersectItems[i].handlerOnLeave();
                    }
                }
            }
        });
    });

    function nativeArrayContains($initObj, obj) {
        var i = 0;
        // dealing with ordinary array
        for (i = 0; i < $initObj.length; i += 1) {
            if ($initObj[i] === obj) {
                return true;
            }
        }
        return false;
    }

    function objectsWithIdsContains($initObj, obj) {
        var items = $initObj,
            item = obj,
            itemsLength = null,
            i = 0;
        if (items && items[0] !== undefined) {
            itemsLength = items.length;
            for (i = 0; i < itemsLength; i += 1) {
                if (items[i].id === item.id) {
                    return true;
                }
            }
        }
        return false;
    }

    function getObjectsThatMatchFilters($initObj, filters) {
        var itemsThatMatch = [],
            i = 0,
            j = 0,
            prop = null;
        for (j = 0; j < filters.length; j += 1) {
            for (i = 0; i < $initObj.length; i += 1) {
                for (prop in filters[j]) {
                    if (filters[j].hasOwnProperty(prop)) {
                        if ($initObj[i][prop] && $initObj[i][prop] === filters[j][prop]) {
                            itemsThatMatch.push($initObj[i]);
                        }
                    }
                }
            }
        }
        return itemsThatMatch;
    }

    function setCollectionsThatMatch($initObj, objectsToSet) {
        var itemsWereAssigned = false,
            originalArray = $initObj,
            j = 0,
            i = 0;

        // for each of the objects to set
        for (j = 0; j < objectsToSet.length; j += 1) {
            // go through all the items in the original array
            for (i = 0; i < originalArray.length; i += 1) {
                // if the item in the orinal array has the same id as the item to set
                if (originalArray[i].id === objectsToSet[j].id) {
                    // then set it to be the value of the item to set
                    originalArray[i] = objectsToSet[j];
                    itemsWereAssigned = true;
                    break;
                }
            }
        }
        return itemsWereAssigned; // return the now modified array
    }

    // we are going to start listenening for when the cursor intersectins with this dom item

    function addIntersectItem($itemToIntersect, onOver, onOut) {
        var itemLeft = $itemToIntersect.offset().left,
            itemTop = $itemToIntersect.offset().top,
            itemWidth = $itemToIntersect.width(),
            itemHeight = $itemToIntersect.height();
        intersectItems.push({
            $domItem: $itemToIntersect,
            handlerOnEnter: onOver,
            handlerOnLeave: onOut,
            intersected: false,
            width: itemWidth,
            height: itemHeight,
            left: itemLeft,
            top: itemTop
        });
    }

    // Return the public interface to the km util methods
    // These are accessed via a global km object (which is a function)
    window.km = function (initObj) {
        var exports = {};

        exports.show = function () {
            var $toShow = $(initObj),
                i = 0;
            if ($toShow.length) {
                for (i = 0; i < $toShow.length; i += 1) {
                    $toShow[i].style.visibility = "visible";
                }
            }
        };

        exports.hide = function () {
            var $toHide = $(initObj),
                i = 0;
            if ($toHide.length) {
                for (i = 0; i < $toHide.length; i += 1) {
                    $toHide[i].style.visibility = "hidden";
                }
            }
        };

        // change this to exports object
        exports.contains = function (objToContain) {
            if (objToContain.id) {
                return (objectsWithIdsContains(initObj, objToContain));
            } else {
                return nativeArrayContains(initObj, objToContain);
            }
        };
        exports.replace = function (objToReplace) {
            var i = 0;

            if (!initObj) {
                return false;
            }
            // dealing with ordinary array
            for (i = 0; i < initObj.length; i += 1) {
                if (initObj[i] === objToReplace) {
                    initObj[i] = objToReplace;
                    return true;
                }
            }
            return false;
        };

		
		
		function removeItemFromArray(objToRemove, arrayToRemoveThingsFrom) {
			var i = 0;
			for (i = 0; i < arrayToRemoveThingsFrom.length; i += 1) {
				if (arrayToRemoveThingsFrom[i] === objToRemove) {
					arrayToRemoveThingsFrom.splice(i, 1);
					return true;
				}
			}
		}
		
		function removeItemFromArrayById(objToRemove, arrayToRemoveThingsFrom) {
			var i = 0;
			for (i = 0; i < arrayToRemoveThingsFrom.length; i += 1) {
				if (arrayToRemoveThingsFrom[i].id === objToRemove.id) {
					arrayToRemoveThingsFrom.splice(i, 1);
					return true;
				}
			}
		}
		
		exports.removeById = function (objToRemove) {
            var itemToRemoveIndex = 0,
				itemsToRemove = [];
				arrayToRemoveThingsFrom = initObj;
            if (!arrayToRemoveThingsFrom) {
                return false;
            }
			// if the objToRemove is an array 
			if(objToRemove.length && (typeof objToRemove !== 'string') ) {
				// then add each item in the array to the items to remove array.
				itemsToRemove = objToRemove;
			} else {
				// other wise the objToRemove is a single item and should be removed from arrayToRemoveThingsFrom
				itemsToRemove.push(objToRemove);
			}
			// remove each of the items to remove
			for(itemToRemoveIndex = 0; itemToRemoveIndex < itemsToRemove.length; itemToRemoveIndex += 1){
				removeItemFromArrayById(itemsToRemove[itemToRemoveIndex], arrayToRemoveThingsFrom);
			}
			return (itemToRemoveIndex > 0); // return true if things were removed.
        };

        exports.remove = function (objToRemove) {
            var itemToRemoveIndex = 0,
				itemsToRemove = [],
				arrayToRemoveThingsFrom = initObj;
            if (!arrayToRemoveThingsFrom) {
                return false;
            }
			// if the objToRemove is an array 
			if(objToRemove.length && (typeof objToRemove !== 'string') ) {
				// then add each item in the array to the items to remove array.
				itemsToRemove = objToRemove;
			} else {
				// other wise the objToRemove is a single item and should be removed from arrayToRemoveThingsFrom
				itemsToRemove.push(objToRemove);
			}
			// remove each of the items to remove
			for(itemToRemoveIndex = 0; itemToRemoveIndex < itemsToRemove.length; itemToRemoveIndex += 1){
				removeItemFromArray(itemsToRemove[itemToRemoveIndex], arrayToRemoveThingsFrom);
			}
			return (itemToRemoveIndex > 0); // return true if things were removed.
        };

        exports.get = function (filtersFromUser) {
            if (!initObj || !filtersFromUser) {
                return false;
            }

            if (filtersFromUser.length) {
                return getObjectsThatMatchFilters(initObj, filtersFromUser);
            } else {
                return getObjectsThatMatchFilters(initObj, [filtersFromUser]);
            }
        };
        exports.set = function (objectsToSet) {
            // init object is an array.. and object to set is the object to overrite in that array.
            if (!initObj || !objectsToSet) {
                return false;
            }
            if (objectsToSet.length) {
                return setCollectionsThatMatch(initObj, objectsToSet);
            } else {
                return setCollectionsThatMatch(initObj, [objectsToSet]);
            }
        };
        exports.valuesOf = function (propertyName) {
            var arrayToGetValuesFrom = initObj,
                values = [],
                i = 0;
            for (i = 0; i < initObj.length; i += 1) {
                if (initObj[i][propertyName]) {
                    values.push(initObj[i][propertyName]);
                }
            }
            return values;
        };

        exports.setValues = function (toSet) {
            var arrayToSet = initObj,
                property = null,
                value = null,
                i = 0;
            // for each of the properties to set
            for (property in toSet) {
                if (toSet.hasOwnProperty(property)) {
                    value = toSet[property];
                    for (i = 0; i < arrayToSet.length; i += 1) { // for each of the items
                        arrayToSet[i][property] = value;
                    }
                }
            }
            return arrayToSet;
        };

        exports.each = function (functionToCall) {
            var i = 0;
            if (initObj) {
                for (i = 0; i < initObj.length; i += 1) {
                    functionToCall(initObj[i], i);
                }
            }
        };

        exports.mouseIntersects = function (mouseIntersectsEventHandler, mouseLeavesIntersectionEventHander) {
            addIntersectItem(initObj, mouseIntersectsEventHandler, mouseLeavesIntersectionEventHander);
        };

        exports.assert = function (expression, message) {
            if (!expression) {
                throw (message);
            }
        };

		// using the string as a template insert the data.
		// http://uberpwn.wordpress.com/2010/03/17/fat-free-templating-with-barebones-javascript/
        exports.supplant = function(data){
			var text = initObj,
				o = data;
			return text.replace(/{([^{}]*)}/g, function (a, b) {
				var r = o[b];
				return typeof r === 'string' || typeof r === 'number' ? r : a;
			});
		};

        return exports;
    };

    window.km.uniqueNumber = (function () {
        var uniqueNumber = 0,
            returnNewUniqueNumber = function () {
                uniqueNumber += 1;
                return uniqueNumber;
            };
        return returnNewUniqueNumber;
    }());

    // add utility methods to use without passing in a selector
    window.km.trace = function (message) {
        if (DEBUG) {
            //throw new Error("please remove all trace statements");
            try {
				console.log(message);
            } catch (ex) {}
        }
    };

    window.km.createModule = function (moduleInitialiser) {
        var moduleBase = {},
            moduleExports = {},
            moduleEventHandlers = {};
        moduleBase.dispatch = function (eventName, data1, data2, data3, data4) {
			if (typeof moduleEventHandlers[eventName] === "function") {
				moduleEventHandlers[eventName](data1, data2, data3, data4);
			} else {
				km.trace("warning " + eventName + " is not a function!");
			}
        };
		
		moduleBase.disptach = function (eventName) {
			throw new Error("disptatch is not a function, did you mean dispatch ?");
		};
		
		moduleBase.hasHandlerFor = function (eventName) {
			if (typeof moduleEventHandlers[eventName] === "function") {
				return true;
			} else {
				return false;
			}
		};
        moduleExports.on = function (eventName, eventHandler) {
            moduleEventHandlers[eventName] = eventHandler;
        };
        moduleInitialiser(moduleBase, moduleExports);
        return moduleExports;
    };


}({}));

/*
 kmUtils object
 * contains methods for dealing with arrays of media items.
 * contains a method for removing all event listeners on a html element
 * contains the array.contains method
 if((array).contains(mediaItem)){
 alert("media item in aray");
 }
 */
