(function () {
    
    var W = Allthat.namespace('Allthat.Widget'); // get link to the Allthat.Widget package (or create empty package if not yet exist)
    
    /**
     * @class Popup
     * @module Allthat
     * Class which should handle popup windows
     */
    W.Popup = new Class({
        Implements : [Options, Events],
        options : {
            toggleSelector : null,
            closeSelector : null,
            targetId : null,
            toggleElement : null,
            closeElement : null,
            popupElement : null,
        	clicked : null,
        	topOffset : null,
        	leftOffset : null,
        	left : null, // thus will be used to set top/left dimensions of the popup DOM element event if relativeOffset option set to true
        	top : null, 
        	group : null,
        	childElement : null,
        	relativeOffset : true,
        	execute : $empty,
        	bindEventListenerOnTheBody : false,
        	/**
        	 * css class which have 'display: none;' property
        	 */
        	hideClass : 'hide',
        	/**
        	 * Should we hide popup if user clicks around the window (not on the toggle and popup)? 
        	 * Default - true
        	 */
        	clickClose : true,
        	body : $$('body')[0],
        	visible : false
        	/**
        	 * We should assign to that value method hideClickClosePopup bound to this
        	 * Actually, we don't need this line of code (we should override it later in initialize method)
        	 * So I left it for documentation (minifier will remove it from production code)
        	 */
        	// boundHideClickClosePopup : $empty,
        	// boundToggle : $empty,
        	// boundHide : $empty
        },
        
        initialize : function (options) {
            this.setOptions(options);
            
            // initialize references to the DOMS:
            this.toggleElement = $$(this.options.toggleSelector);
            
    		this.closeElement = $$(this.options.closeSelector);
    		
    		this.childElement = $(this.options.child);
    		this.popupElement = $(this.options.targetId);
    		            
            // push this instance of the popup class into popup hash
            if (this.options.group) {
    			W.Popup.GROUPS[this.options.group] = W.Popup.GROUPS[this.options.group] || [];
    			W.Popup.GROUPS[this.options.group].push(this);
    		}
    		
    		// remember scoped versions of some function (we need this to workaround with scope problems)
    		this.boundHideClickClosePopup = this.hideClickClosePopup.bind(this);
    		this.boundToggle = this.toggle.bind(this);
    		this.boundHide = this.hide.bind(this);
    		
    		// initialize event listeners on the close/toggle selectors:
    		if (this.options.bindEventListenerOnTheBody) {
    		  this.options.body.addEvent('mouseup', this.boundToggle);
    		  this.options.body.addEvent('mouseup', this.boundHide);
    		} else {
    		  this.toggleElement.addEvent('mouseup', this.boundToggle);
    		  this.closeElement.addEvent('mouseup', this.boundHide);
    		}
    		
    		
    		
    		this.fireEvent('initialize', [this.popupElement]);
        },
        
        /**
         * Toggle popup window. If user are hidden - show it.
         * @method toggle
         * params event {Event|Element} event which user clicks
         */
        toggle : function (event) {
            // we can receive either event and element object
    	    var clicked = ($type(event) === 'event') ? $(event.target) : event;
            
            if (!event) { return; }
    
            /**
             * There was strange bug with MooTools getParent function, 
             * So that I wrote my own implementation. 
             * That function checks all DOM Nodes from provided (el) till Document itself
             * On every iteration it pass el to the callback function and check if it returns true - and does same.
             * Otherwise, it returns true. 
             * It is quite flexible and fast way to define is current element inside given or not
             */
            function top (el, callback) {
                var theParentNode = el;
                
                while (theParentNode && theParentNode.nodeName && theParentNode.nodeName.toLowerCase() != 'body') {
                    
                    if (callback(theParentNode)) { return true; }
                    theParentNode = theParentNode.parentNode;
                }
                
                return false;
            }
            
            var isUserClicksOnToggleElement = $$(this.options.toggleSelector).some(function (el) {
                return top (clicked, function (m) { return m == el; });
            });
            
                        
            if (!isUserClicksOnToggleElement) { return; }
            
            // if (!(clicked.match(this.options.toggleSelector) || $A($$(this.options.toggleSelector)).some(function (el) { return el.hasChild(clicked); }))) { return; }
            
            // if user clicks on the other target then last time, show popup
            if (!isUserClicksOnToggleElement && (this.clicked && this.clicked != clicked)) {                
    			this.clicked = clicked;
    			this.show(event);
    			return;
    		}
	
    	    if (!this.options.visible) {
    	        this.clicked = clicked;
        		return this.show(event);
    	    } else {
    	        this.clicked = clicked;
        		return this.hide();
    	    }
        },
        
        /**
         * Show popup window. Bind neccessary event listeners (like clickCloseGroups and all that)
         * @method show
         * @param event {Event|Element} click event or event target
         * @return {Boolean}
         */
        show : function (event) {
            this.hideOtherPopups();

    		// we can receive either event and element object
            var clicked = ($type(event) === 'event') ? $(event.target) : event;
            if (!event) { return; }
            
            // bind clickCloseGroup listener to the mouseup event
            if (this.options.clickClose) {
                W.Popup.CLICK_CLOSE_POPUP[this.options.targetId] = this;
                this.options.body.addEvent('mouseup', this.boundHideClickClosePopup);
            }
            
            // define popup coordinates
            if (this.options.leftOffset || this.options.topOffset || this.options.left || this.options.top) {
                var position = (this.options.relativeOffset) ? clicked.getPosition() : { x : 0, y : 0 },
                    dimensions = clicked.getSize();

                this.popupElement.setStyles({
                    // use predifeined left option event if relative offset setted to true
                    //left : (this.options.left) ? this.options.left : (position.x - dimensions.x / 2 + this.options.leftOffset + 'px'),
                    // same as above
                    top : (this.options.top) ? this.options.top : (position.y + dimensions.y + this.options.topOffset + 'px')
                });
            }
            
            // show it
    		this.popupElement.removeClass(this.options.hideClass);
    		
    		// add this popup instance to the open popups group
    		W.Popup.OPEN_POPUPS[this.options.targetId] = this;
    		
    		// call execute function (default is $empty)
    		this.options.execute();
    		
    		this.options.visible = true;
    		
    		this.fireEvent('show');

            // prevent default browser behavior - I think that # symbols on the address bar are odd
            if (event.preventDefault) { event.preventDefault(); }

            // to prevent default browser behavior in ie6 and minor browsers
            return false;
        },
        /**
         * Hide popup, remove this instance of the Popup class from all groups (if any)
         * @method hide
         * @param e {Event} DOM event
         */
        hide : function (event) {
            var clicked = ($type(event) === 'event') ? $(event.target) : event;

            // figure out is we clicked on the toggle or close DOM element
    	    if (!!clicked && !(clicked.match(this.options.toggleSelector) || (new Element($$(this.options.toggleSelector)[0])).hasChild(clicked) || clicked.match(this.options.closeSelector) || (new Element($$(this.options.closeSelector)[0])).hasChild(clicked))) { return; }

    	    if (this.options.toggleSelector == '.wishlist .addto_list'){  
        		$(this.options.uniqueId).set('text','+ Add to my List');
    	    }
	    
            this.options.body.removeEvent('mouseup', this.boundHideClickClosePopup);
    		this.popupElement.addClass(this.options.hideClass);
    		
    		delete W.Popup.CLICK_CLOSE_POPUP[this.options.targetId];
    		delete W.Popup.OPEN_POPUPS[this.options.targetId];
    		
    		this.options.visible = false;
    		
	        this.fireEvent(this.options.hideClass);
	                 
    	 	if (event && event.preventDefault) { event.preventDefault(); }
    		return false;
        },
        /**
         * Hide current popup if user clicks somewhere around the window (not inside popup)
         * @method hideClickClosePopup
         * @param e {Event} mouseup event
         */
        hideClickClosePopup : function (e) {
            var el = $(e.target);
            if (el != this.popupElement && el != this.clicked && !this.popupElement.hasChild(el)
                  && (!this.childElement || !this.childElement.hasChild(el))) {
                
                this.hide();
            }
        },
        /**
         * Hide other popups from same group
         * @method hideOtherPopups
         */
        hideOtherPopups : function () {
            $each(W.Popup.OPEN_POPUPS, function (popup, id) {
                if (popup != this && this.options.group && popup.options.group == this.options.group) {
    				popup.hide();
    			}
            }, this);
        },
        
        /**
         * Destroy event listeners, dom references and object itself
         * @method destroy
         */
        destroy : function () {
            if (this.options.bindEventListenerOnTheBody) {
                this.options.body.removeEvent('mouseup', this.boundToggle);
                this.options.body.removeEvent('mouseup', this.boundHide);
            } else {
                this.toggleElement.removeEvent('mouseup', this.boundToggle);
                this.closeElement.removeEvent('mouseup', this.boundHide);
            }
            // we don't need to worry about memory leaks - mootools has already worried about it for us
        }
    });
    
    W.Popup.GROUPS = {}; // same as "Allthat.popup_groups" at the previous version
    W.Popup.OPEN_POPUPS = {};
    W.Popup.POPUPS = {};
    W.Popup.CLICK_CLOSE_POPUP = {};
    
    W.Popup.create = function (clicked, options, force) {
        var UID = options.uniqueId;
	
        if (!W.Popup.POPUPS[UID]) {	  
    	    /* Need to destroy previous popup object */
            $each(W.Popup.POPUPS, function(obj){ obj.destroy(); });
    		W.Popup.POPUPS = {};
    		
    	    var popup = new W.Popup(options);
    	    W.Popup.POPUPS[UID] = popup;
    	    popup.toggle(clicked);
    	    return false;
    	} else if (force) {
    	    W.Popup.POPUPS[UID].toggle(clicked);
	    } 

    };
    
    W.Popup.remove = function (uid) {
        var popup = W.Popup.POPUPS[uid];
        
        if (!popup) { return; }
        // hide popup
        popup.hide();
        // remove it from groups
        if (popup.options.group) { W.Popup.GROUPS[popup.options.group].erase(popup); }
       // remove it from POPUPS hash
        delete this.POPUPS[uid];
        // remove object itself
		popup.destroy();
		delete popup;
    };
})();