/*-----------------------------------------------------------------------------
tip Class

version:    1.1
author:     iq
libs:       mootools 1.2

notes: In this class you will notice that we target elements by walking through
the dom quite a bit and never saving them to a variable.  This is intentional
to adapt to how the Tip class works because it creates and destroys elements
quite frequently.
-----------------------------------------------------------------------------*/
tip = new Class({
        Implements: [Options, Events, Class.Occlude],
        options: {
            container: '',
            content: '',
            contentClass: '',
            scroll: false,
            drag: false,
            title: null,
            maxHeight: null,
            deBug: false,
            onHide : null,
            onShow : null
        },
        initialize: function(options){
            this.setOptions(options);
            this.tip_instance = {};
            this.haveCloseEvents = false;    
            this.container = ( this.options.container ) ? $(this.options.container) : null;
            this.content = ( this.options.content ) ? $(this.options.content) : null;
            this.drag = ( this.options.drag ) ? this.options.drag : null;
            this.title = this.options.title;
            this.maxHeight = ( this.options.maxHeight ) ? this.options.maxHeight : null ;
            this.contentClass = this.options.contentClass;
            this.titleCreated = false;
            this.titleText = ( this.title ) ? this.title : '';
            this.bubble = null;
            this.caret = null;
            this.sw = null;
            this.scroll = this.options.scroll;
            this.caretPos = ( this.options.caretPos ) ? this.options.caretPos : 'n';
            this.isToolTip = ( this.options.isToolTip ) ? true : false;
            this.first = true;
            // safari does not trigger mouseup/click on select elements so we have to use mouseup
            this.triggerEvent = ( this.options.triggerEvent )
                ? this.options.triggerEvent
                : ( this.isToolTip )
                    ? 'mouseover'
                    : ( Browser.Engine.webkit && $(this.container).get('tag') == 'select' )
                        ? 'mousedown'
                        : 'click';
		    
            // we need to store a reference to the method created by 'bind'
            // so that we can remove the event handler later in closeAllBubbles
            this.closer = this.closeAllBubbles.bind(this);
            
            // use occlude class to check and make
            // sure object isn't already instantiated
            if ( this.occlude('tip', this.container) ) {
                return this.occluded;
            }
            
            // store instance reference in element
            this.container.store('tip', this);
            
            
            // add the tip
            this.add();
            this.trigger();
        },
        trigger: function() {
            var instance = this;
            var xo = -46;
            var yo = 28;
            var content = this.content;
            var trigger = this.container;
                        
            // check for what size tip needed
            var alpha = new RegExp(/[a-z]/gi);
            var className = $chk(content) ? content.getProperty('class') : '';
            
            var individualClasses = className.split(' ');
            
            if( className != 'bContent') {
                var specs = individualClasses[1].split('|');
            }
            
            // default these if not present in bContent class property
            var width = ( className != 'bContent') ? specs[0].replace(alpha, '') : 250;
            var xOffset = ( className != 'bContent') ? specs[1].replace(alpha, '') : 0;
            xOffset = xOffset.toInt();
            var yOffset = ( className != 'bContent') ? specs[2].replace(alpha, '') : 0;
            yOffset = yOffset.toInt();
            
            // caret position
            this.caretPos = ( className != 'bContent') ? specs[3] : 'n';
            
            // west caret starting position
            if( this.caretPos == 'w' ) {
                xo = 10;
                yo = -24;                
            } else if( this.caretPos == 'nr' ) {
                xo = -319 - ( width - 377 );
                yo = 28;
            } else if( this.caretPos == 'sr' ) {
                xo = -319 - ( width - 377 );
                yo = 11;
            }
            
            var currentWidth = this.currentWidth;
            
            // reposition and resize
            this.repositionAndResize = function() {
                this.addCloseEvents();
                
                var pageCoords = trigger.getPosition();    
                var bookScrollsYPosition = 0;
                var x = pageCoords.x + xOffset + xo;
                                
                if(instance.scroll && Browser.Engine.trident) {
                    var bookScroll = $('bookScrollWrap');
                    var bookScrolls = bookScroll.getScroll();
                    bookScrollsYPosition = bookScrolls.y;
                }
                
                var y = pageCoords.y + yOffset + bookScrollsYPosition + yo;
                
                // apply width first for height calculation purposes
                content.getParent('.y-tip').setStyles({
                    width: width + 'px'
                })
                
                // if a southern right caret
                if( instance.caretPos == 'sr' ) {
                    var bSize = content.getParent('.y-tip').getSize();
                    y -= bSize.y; 
                }
                
                content.getParent('.y-tip').setStyles({
                    top: y + 'px',
                    left : x + 'px'
                })
                
                // if maxHeight set
                if( this.maxHeight ) {
                    this.applyHeight();
                }
            }.bind(this);
            
            
            // if target is a input box
            if( trigger.get('tag') == 'input' ) {
                trigger.addEvent('focus', function() {
                
                    var pageCoords = trigger.getPosition();    
                    var bookScrollsYPosition = 0;
                    var x = pageCoords.x + xOffset + xo;
                                    
                    if(instance.scroll && Browser.Engine.trident) {
                        var bookScroll = $('bookScrollWrap');
                        var bookScrolls = bookScroll.getScroll();
                        bookScrollsYPosition = bookScrolls.y;
                       }
                       
                       var y = pageCoords.y + yOffset + bookScrollsYPosition + yo;
                    
                    content.getParent().getParent().getParent().setStyles({
                        width: width + 'px',
                        top: y + 'px',
                        left : x + 'px'
                    })
                });
            }
        },
        add: function() {
            var instance    = this;
            var trigger     = this.container;
            var content     = this.content;
            var bCloseLinks = content.getElements('.bClose');
            var buttons     = content.getElements('.sButton');
            
            trigger.setStyle('cursor','pointer');
            
            // setting content to be grabbed by Tips class
            trigger.store('tip:text', content);
            
            // removing title content for this class
            trigger.store('tip:title', ''); 

			// store trigger title data
			this.triggerTitle = ( trigger.get('title') ) ? trigger.get('title') : '';

            // set up our tip and give it a very long hide delay
            // so we can manipulate the styles on and off via click event
			this.tip_instance = new Tips(trigger, {
                className: 'y-tip',
                fixed: true,
                hideDelay: ( 24 * 60 * 60 ),
                showDelay: 0,
				title: '',
                onShow: function() {
                    content.getParent('.y-tip').setStyle('display','none');
                },
                onHide: function() {
                    if ( content.getParent('.y-tip') ) {
                        content.getParent('.y-tip').setStyle('display','none');
                    }
                }
            });
            
			// set trigger title back after tip instantiation
			if( this.triggerTitle != '' ) {
				trigger.set('title', this.triggerTitle);
			}

            trigger.addEvent(this.triggerEvent, function(event) {
                if( this.first ) {
                    setTimeout( function() {
                        this.updateStyles( event );
                        this.repositionAndResize( event );
                    }.bind(this), 1 );
                } else {
                    this.updateStyles( event );
                    this.repositionAndResize( event );
                }
            }.bind(this));
            
            // giving any buttons in the content a click event to close bubble
            if( buttons ) {
                buttons.addEvent('click', function(e) {
                    this.closeAllBubbles();
                }.bind(this));
            }
            
            // giving any inputs blur close events
            if( this.options.killOnBlur !== false 
                && trigger.get('tag') == 'input' 
                && trigger.getProperty('type') == 'text' ) {
                
                trigger.addEvent('blur', function() {
                    this.closeAllBubbles();
                }.bind(this));
            }
            
            // giving any buttons in the content a click event to close bubble
            if( bCloseLinks ) {
                bCloseLinks.each(function(link) {
                    link.addEvent('click', function() {
                        this.closeAllBubbles();
                        try { link.focus(); }
                        catch(e) { /* let it silently die */ }
                                                 
                        return false;
                    }.bind(this));
                }.bind(this));
            }            
        },
        updateStyles: function( event ) {
            this.closeAllBubbles();
            this.addCloseEvents();
            // extend target
            var target = $(event.target);
            
            if( this.isToolTip ) {
                var killerHandler = function(e) {
                    this.container.removeEvent('mouseout', killerHandler)
                    this.closeAllBubbles();
                }.bind( this );
                this.container.addEvent('mouseout', killerHandler);
            }
            
            if(this.first) {
                this.buildBubble();
                this.first = false;
            }
            
            if( this.container !== target ) {
                if( this.content.getStyle('display') == 'block' ) {
                    this.content.setStyle('display','none');
                    this.content.getParent().getParent().getParent().setStyle('display','none');
                    this.content.getParent().getParent().getParent().setStyle('visibility','hidden');
                    
                    // I'm doing this in order to fix the hover repositioning 
                    // that tips does when you dont have enough viewport space for the tip
                    this.tip_instance.attach(trigger);
                } 
            }
            
            // if trigger equals the event target or content is currently hidden
            if( ( this.content.getStyle('display') == 'none' ) || ( this.container === target ) ) {
            
                this.fireEvent('show');
            
                this.content.setStyle('display','block');
                this.content.getParent().getParent().getParent().setStyle('display','block');
                this.content.getParent().getParent().getParent().setStyle('visibility','visible');
                
                // I'm doing this in order to fix the hover repositioning 
                // that tips does when you dont have enough viewport space for the tip 
                this.tip_instance.detach(this.container);
                
                if( this.scroll ) {
                    // giving body a scroll event to close bubble
                    $('bookScrollWrap').addEvent('scroll', function() {
                        this.closeAllBubbles();
                        $('bookScrollWrap').removeEvents('scroll');
                    }.bind(this));
                }
            }
        
            // return false to the link so url isn't executed
            // should be a link to a page version of the action
            if( this.container.get('tag') == 'a' ) return false;
            return true;
        },
        applyHeight: function() { 
                    
            // calculate content height
            var contentHeight = this.content.getParent().getSize().y;
            var maxHeightInt = this.maxHeight.toInt();
            
            // if contentHeight is greater than setMaxHeight make scrolling content
            if( contentHeight > maxHeightInt ) {
                this.content.setStyles({
                    'height': this.maxHeight,
                    'overflow': 'auto'
                });
            } else {
                this.content.setStyles({
                    'height': 'auto',
                    'overflow': 'visible'
                });
            }
        }, 
        buildBubble: function(caretPos) {
        
            // tip divs
            var text = this.content.getParent();
            var tipDiv = this.content.getParent().getParent();
            var top = this.content.getParent().getParent().getPrevious();
            var bottom = this.content.getParent().getParent().getNext();
            this.bubble = this.content.getParent().getParent().getParent();
            
            // containerize content container
            text.addClass('container');
            tipDiv.addClass('container');
            
            // elements that need to be injected for bubble layout
            var nw = new Element('div', {
                'class': 'nw',
                'html': "<div class='n'></div>"
            });
        
            // caret position
            if( this.caretPos == 'w' ) {
                this.caret = new Element('div', {
                    'class': 'wCarrot'
                });
            } else if( this.caretPos == 'nr' ) {
                this.caret = new Element('div', {
                    'class': 'nrCarrot'
                });
            } else if( this.caretPos == 'sr' ) {
                this.caret = new Element('div', {
                    'class': 'srCarrot'
                });
            } else {
                this.caret = new Element('div', {
                    'class': 'nCarrot'
                });
                this.caret.left = '36px';
            }
            
            // create markup structure we need for a more versatile bubble
            this.caret.inject(this.bubble);
            nw.inject(top);
            
            // add element to create south west and south images for bubble 
            // container
            if( this.sw == null ) {
                this.sw = new Element('div', {
                    'class': 'sw',
                    'html': "<div class='s'></div>"
                });
            
                this.sw.inject(bottom);
            }
            
            // make this bubble draggable
            if( this.drag ) {
                this.freeBubble();
            }
            
            // add a content container class to add external styling
            if( this.contentClass ) {
                this.content.addClass( this.contentClass );
            }
            
        },
        
        freeBubble: function() {

            var caret = this.caret;
            
            this.handle = new Element('div', {
                'class': 'bHandle'
            });
            
            this.shim = new Element('div', {
                'class': 'bShim'
            });
            
            // giving drag bubble close event
            if( this.drag ) {
                this.shim.addEvent('click', function() {
                    this.closeAllBubbles();
                }.bind(this));
            }
            
            // place the handle inside the bubble container
            this.shim.inject(this.bubble);
            this.handle.inject(this.bubble);
            
            var bubble_instance = this;
            
            // create a drab object for the bubble
            this.drag = new Drag(this.bubble, {
                snap: 0,
                handle: this.handle,
                onStart:function() {
                    caret.dispose();
                    
                    bubble_instance.freeBubbleTitle();
                }
            });
        },
        
        freeBubbleTitle: function() {
            // if title is set and the element has not been created yet
            if( this.title && !this.titleCreated ) {
            
                this.title = new Element('div', {
                    'html': '<p>' + this.titleText + '</p>',
                    'class': 'bTitle',
                    'styles':  {
                        // left: this.caret.left
                    }
                });

                // place element into the bubble
                this.title.inject(this.bubble);
                
                // element created, don't create again
                this.titleCreated = true;
            } else if ( this.title && this.titleCreated ) {
                this.title.inject(this.bubble);
            }
        },
        
        closeAllBubbles: function(e, instance) {

            // if triggered by an event
            if( e ) {
                var element = $(e.target);
                // check the target, if the click was from within a bubble, keep the bubble open
                // element *may* be an <object> or <embed> tag which IE does not support extending with mootools methods
                // therefore we have to use Element.getParent/hasClass rather than e.getParent
                var object = Element.getParent( element, '.y-tip' );
                if( object || Element.hasClass( element, 'bLink' ) ) {
                    return;
                }
            }
                
            $$('div.y-tip').setStyle('display','none');
        
            // if there are added tip events to the page close tips and remove events
            if( this.haveCloseEvents ) {
                $$('body').removeEvent('mousedown', this.closer);
                window.removeEvent('resize', this.closer);
                 
                // close events have been removed
                // set this in the bubble object
                this.haveCloseEvents = false; 
                  
                // fire hide events
                this.fireEvent('hide');
                
                // place caret back
                if( this.caret ) {
                    // place the caret inside the bubble
                    this.caret.inject(this.bubble);
                }
                
                // if we haven't assigned a title don't dispose of it
                if( this.titleCreated ) {
                        this.title.dispose();
                }
            }
        },

        addCloseEvents: function() {// when a bubble is shown, add listeners for events that will trigger a close
            var instance = this;
        
            if( !this.haveCloseEvents ) {
                this.haveCloseEvents = true;
                // kill tips on mousedowns in the body
                $$('body').addEvent('mousedown', this.closer);
                // making resize close bubbles
                window.addEvent('resize', this.closer);
            }
        }
});

