/*
---

name: tip.js

provides: [WA.Components.Tip]

Creates a bubble style tool-tip that appears
on click and hides if the user clicks anywhere
outside of the tip or element

<a id='anchor'>Click Me!</a>
<span class='bContent w200'>This is the tool tip content!</span>

<script type='text/javascript'>
new WA.Components.Tip( $('anchor') );
</script>  
 
...
*/
 
 
// FIXME: many tips have the exact same content
// so enhance this so that we can only include
// the content in the page once and then can share
// it with all tips of the same class
WA.Components.Tip = new Class({    
    Implements: [Events, Options],

    options: {
        // onShow : function() {}
        // onHide : function() {}
        stopPropagation : false, 
        className       : 'y-tip new',
        id              : null,
        content         : null, // optionally pass in a reference to the content element
                                // if not defined, element immediately following the tip link will be used
        height          : null,
        positions       : ['centerBottom', 'centerTop', 'centerRight', 'centerLeft'],
        edges           : ['upperLeft', 'bottomRight', 'upperLeft', 'upperRight'],
        offsets         : [{x:-60,y:0}, {x:62,y:8}, {x:0,y:-44}, {x:0,y:-44}],
        cache           : true
    },

    initialize: function( element, options ) {
        this.showing = false;
        this.element = element;
        this.setOptions(options);
        this.initContent();
        this.initOverlay();
        this.addShowEvents();
        
        this.element.store('tip', this);
    },
    
    initContent : function() {
        // grab the content from the page
        var content  = this.options.content || this.element.getNext();
        
        // if there is a height param set restrict height
        this.setHeight(content);
        
        this.content = content ? content.dispose() : new Element('div', {text:'tool tip'});
        
        // to prevent flickers, the content is originally marked as display:none
        // however now that we have removed the content from the page, we can set display:block
        // as it will be needed whenever the tip displays
        this.content.setStyle('display', 'block');
        // any element marked 'bClose' in the content will close the bubble when clicked
        this.content.getElements('.bClose').addEvent('click', this.hide.bind(this));
        
        // if id is provided add it to the content
        if ( this.options.id ) {
            this.content.set('id', this.options.id);
        }
    },
    
    getContent : function() {
        return this.content;
    },
    
    initOverlay : function() {
        this.carets = $$(
            new Element('div', {'class':'nCarrot'}),
            new Element('div', {'class':'sCarrot'}),
            new Element('div', {'class':'wCarrot'}),
            new Element('div', {'class':'eCarrot'})
        );
        // get an overlay, either from the cache or create a new one
        this.overlay = (this.options.cache && WA.Components.Tip.instances[this.options.className])
            ? WA.Components.Tip.instances[this.options.className]
            : new WA.Components.Overlay( this.options.className, {
                  onBuild : function( overlay ) {
                      // add the caret to the overlay
                      this.carets.inject( overlay );
                  }.bind(this)
              } );
        if ( this.options.cache ) {
            // cache the overlay so other tips of the same className can use it
            WA.Components.Tip.instances[this.options.className] = this.overlay;
        }
    },
    
    isShowing : function() {
        return this.showing;
    },
    
    addShowEvents : function() {
        this.element.addEvent('click', this.onShowEvent.bind(this));
    },
    
    addHideEvents : function() {
        if( !this.hasOwnProperty('boundOnHideEvent') ) {
            this.boundOnHideEvent = this.onHideEvent.bind(this);
        }
        $(document.body).addEvent('mousedown', this.boundOnHideEvent);
    },
    
    removeHideEvents : function() {
        $(document.body).removeEvent('mousedown', this.boundOnHideEvent);
    },
    
    onShowEvent : function( event ) {
        if( this.options.stopPropagation ) {
            event.stopPropagation();
        }
        this.show();
    },
    
    onHideEvent : function( event ) {
        if( this.options.stopPropagation ) {
            event.stopPropagation();
        }
        // check the scope of the event, if the user clicked the tip
        // or the element, we want to keep the bubble open
        var object = Element.getParent( $(event.target), '.' + this.options.className.split(' ').join('.') );
        if( object || this.element == $(event.target) ) {
            return;
        }
        this.hide();
    },

    show : function() {
        if( this.showing ) return;
        this.carets.removeClass('hide');
        // grab the specified width from the class attribute and apply it to the overlay
        var regex = /w([\d]+)/;
        $(this.overlay).setStyle('width', regex.exec( this.content.get('class') )[1] + 'px');
        // fill the overlay with the tip content
        this.overlay.fill(this.content);
        // move the overlay to be relative to the element
        $(this.overlay).autoposition( {
            positions  : this.options.positions,
            edges      : this.options.edges,
            offsets    : this.options.offsets,
            relativeTo : this.element
        } );
        this.overlay.show();
        // if the tip is showing, we need to listen for close events
        this.addHideEvents();
        this.showing = true;
        
        this.fireEvent('show', [this]);
    },
    
    hide : function( event ) {
        if( !this.showing ) return;
        
        this.overlay.hide();
        // grab our content back from the overlay
        this.content = this.content.dispose();
        // now that the tip is hidden we can stop listening for close events
        this.removeHideEvents();
        this.showing = false;
        
        this.fireEvent('hide', [this]);
    },
    
    setHeight : function(content) {
        if( this.options.height && content ) {
            // calculate content height            
            var contentHeight = content.measure(function() {
                return this.getSize().y;
            });
            var maxHeightInt = parseInt(this.options.height);

            // if contentHeight is greater than setMaxHeight make scrolling content
            if( contentHeight > maxHeightInt ) {
                content.setStyles({
                    'max-height': this.options.height,
                    'overflow': 'auto'
                });
            }
        }
    }
});

// Tip instances can be shared by many elements if they specify the same className
// in the tip options.  This keeps our dom and memory from getting too bloated
WA.Components.Tip.instances = {};

