/**
 * Grimoire namespace
 */

if (typeof grimoire == 'undefined') {
    grimoire = {}
}


String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
    return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
    return this.replace(/\s+$/,"");
}

/**
 * Displays a status message in the status area of the page
 * @param <string> msg The message to display
 */
grimoire.display_status = function(msg) {   
    grimoire.display_popdown(msg,'status');
}

/**
 * Displays an error message in the status area of the page
 * @param <string> msg The message to display
 */
grimoire.display_error = function(msg) {
	grimoire.display_popdown(msg,'error');
}

grimoire.display_popdown = function(msg,cls) {
	
	$('.grimmsg').remove();
	
    var el = $('<div style="display:none" class="grimmsg"></div>').appendTo('body')
	   .addClass(cls)
	   .slideDown('slow')
	   .html(msg)
	   .click(function(){$(this).remove()});		
	   
	setTimeout(function(){el.remove()},'10000');
}

/**
 * Displays an HTML alert dialog. Note that unlike js alert this alert returns before the user has dismissed the dialog.
 * @param <string> msg The message to display
 * @param <mixed> options Either an options object (see below) or a function that
 * 			should be called when the dialog is dismissed.
 * 		Possible options are:
 *  		title: Sets the title of the alert dialog
 *  		buttons: An array of buttons to push. The text will give is what's displayed in each button.
 *              Defaults to "OK"
 *          type: Either 'warning' or 'error' or 'info'
 *          onClose: The function to call when the dialog is dismissed
 *  	
 *  @return The alert dialog jquery object.
 */
grimoire.alert = function(msg,options)
{
    if ($('#alert-dialog').length != 0) {
        $('#alert-dialog').dialog('close');
        $('#alert-dialog').remove();
    }

    var dlgOptions = {};
    if (typeof(options) != 'undefined' && typeof(options.buttons) != 'undefined') {
        dlgOptions.buttons = options.buttons;
    }
    else {
        dlgOptions.buttons = {
            "OK":function(){
                $(this).dialog('close');
                if (typeof(options.onClose) == 'function') {
                    options.onClose.apply(this);
                }
                else if (typeof(options) == 'function') {
                    options.apply(this);
                }
            }
        }
    }

    if (typeof(options) != 'undefined' && typeof(options.title) != 'undefined') {
        dlgOptions.title = options.title;
    }
    else {
        dlgOptions.title = 'ScribbleSquid';
    }

    var img = 'dlg-info.png';
    if (typeof(options) != 'undefined' && typeof(options.type) != 'undefined') {
        if (options.type == 'warning'){
            img = 'dlg-exclaim.png';
        }
        else if (options.type == 'error'){
            img = 'dlg-error.png';
        }
    }
    
    dlgOptions.resizable = false;
    
    var dlghtml = '<div id="alert-dialog" class="dialog"><img class="type" src="/static/images/'+img+'"/>'+msg+'</div>';
    return $(dlghtml).grimdlg(dlgOptions);
}

grimoire.toggle_help_text = function(idOfHelpText) {

    var help = $('#'+idOfHelpText);
    if (help.is(':visible')){
        help.hide('fast');
    }
    else {
        help.show('fast');
    }    
}

/**
 *	Returns true if the keycode is one of the characters that yields
 * visible results in a textarea.  So a,b,c.. etc.  But also delete, backspace.
 * But NOT tab since that sends the user to the next focus-able field.
 */
grimoire.is_printable_character = function(keycode) {
    non_printable = [9,13,16,17,18,19,20,27,33,34,35,36,37,38,39,40,45]
    for (var i = 0; i < non_printable.length; i++) {
        if (keycode == non_printable[i]) {
            return false;
        }
    }
    
    return true;
}

/**
 * Displays a simple dialog with a text field.  The options are:
 * 	title: The title of the dialog (required)
 *  message: The message to display before the edit box (required)
 *  textAreaHeight - The height of the text area (if using a text area instead of an edit box) (defaults to 100)
 *  textarea - True, if use a textarea, false for a single line edit box (defaults to false)
 *  onOk - The function to call when the user hits the ok button 
 *  defaultValue - The text to display initially in the edit/textarea box (defaults to nothing)
 *  acceptButton - The text to display in the ok button.  Defaults to "Accept"
 * @param {Object} options
 */
grimoire.query_user_for_text = function(options) {

    var title = options.title ? options.title : '';
    var message = options.message ? options.message : '';
    var textAreaHeight = options.textareaHeight ? options.textareaHeight : 100;
    var textarea = options.textarea ? options.textarea : false;
    var callback = options.onOk ? options.onOk : null;
    var defaultValue = options.defaultValue ? options.defaultValue : '';
    var acceptButton = options.acceptButton ? options.acceptButton : 'Accept';

    if (!callback) {
        return false;
    }

    var dlgOptions = {};
    dlgOptions.buttons = {
        'Accept':function(){
            callback($('#query-input').val());
            $(this).remove();
        },
        'Cancel': function(){
            $(this).remove();
        }
    };

    dlgOptions.modal = true;
    
    if (title) {
        dlgOptions.title = title;
    }
    
    var dlghtml = '<div id="query-dialog" class="dialog">';
    if (message) {
        dlghtml += '<p class="prompt">' + message + '</p>';
    }

    if (textarea) {
        dlghtml += '<textarea class="fluid" style="height:'+textAreaHeight+'px;width:100%" id="query-input">'+defaultValue+'</textarea>';
    }
    else {
        dlghtml += '<input class="large fluid" type="text" value="'+defaultValue+'" id="query-input"/>';
    }
    
    return $(dlghtml).grimdlg(dlgOptions);
   
}

/**
 * Constructs a rounded rect path on the 2d canvas context given.  When the function completes
 * caller must close the path or can add more to it if necessary.  Callers must stroke and fill.
 * @param <Object> ctx The 2d context of the canvas element to make the path in
 * @param <int> x The x coord (relative to the canvas element)
 * @param <int> y The y coord (relative to the canvas element)
 * @param <int> width Width of the rectangle
 * @param <int> height Height of the rectangle
 * @param <int> radius The radius of the corner arcs
 */
grimoire.make_rrect_path = function(ctx,x,y,width,height,radius,fill) {
    ctx.beginPath();
    ctx.moveTo(x,y+radius);
    ctx.lineTo(x,y+height-radius);
    ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
    ctx.lineTo(x+width-radius,y+height);
    ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
    ctx.lineTo(x+width,y+radius);
    ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
    ctx.lineTo(x+radius,y);
    ctx.quadraticCurveTo(x,y,x,y+radius);	
}

/**
 * Constructs a standard marker symbol on the 2d canvas context given.  When the function completes
 * caller must close the path or can add more to it if necessary.  Callers must stroke and fill.
 * @param <Object> ctx The 2d context of the canvas element to make the path in
 * @param <int> x The x coord (relative to the canvas element)
 * @param <int> y The y coord (relative to the canvas element)
 * @param <int> width Width of the area
 * @param <int> height Height of the area
 */
grimoire.make_marker_path = function(ctx, x,y,height,width)
{	
    ctx.beginPath();
    ctx.moveTo(x,y);
    ctx.lineTo(x+width,y);
    ctx.lineTo(x+width,y+(height/2));
    ctx.lineTo(x+(width/2),y+height);
    ctx.lineTo(x,y+(height/2));
    ctx.lineTo(x,y);
    
}

/**
 * Prepares the grimoire standard table with the look and behavior common
 * to all tables of this type in grimoire.
 */
grimoire.prepare_tables = function() {
    $('.standard-table tbody tr:odd').css('background-color','#fafafa');	
}

/**
 * A jquery plugin that wraps the jquery ui dialog adding a feature
 * that lets the user hit the enter to emulate a click of the first button
 * returned.
 * @param {Object} options The options that a dialog normally expects.
 */
$.fn.grimdlg = function(options) {
    return this.each(function() {
        $(this).dialog(options);
		
		_self = this;		
        $(this).keyup(function(e){
			if (!grimoire.focus_in_textarea(e)) {
	            if (e.keyCode == 13 ) {				
	                $(_self).parent().find('button').trigger('click');
					e.stopPropagation();
	            }
			}			
        });
    });
}

/**
 * Determines if the focus is in an textarea element.
 * @return <bool> Returns true if focus is in a textarea. False otherwise
 */
grimoire.focus_in_textarea = function() {
	return ($('textarea:focus').length > 0);
}

/**
 * Adds an anchor element within the selected element that contains the given 
 * text and gives it the given link.  When clicked, it gets the link via AJAX
 * expecting the following JSON payload in return:
 * 	payload.title The title of the help
 *  payload.body The body html of the help
 *  
 * It then displays the help, if received successfully, in a custom help dialog
 * 
 * @param {Object} link
 * @param {Object} helpText
 */
$.fn.helpify = function(link,helpText){
    return this.each(function(){
                
        $('<a help="'+$(this).attr('help')+'" href="javascript:" class="help-bubble">'+helpText+'</a>')	
            .appendTo(this)
            .click(function(e){							
                e.stopPropagation();	
                $('#loading').show();
                $.getJSON(link+'?h='+$(this).attr('help'),function(data){
                    $('#loading').hide();
                    if (data.success) {
                         $('#help').grimdlg({
                            resizable: false,
                            dialogClass:'help',							
                            modal:true,
                            title: data.payload.title,
                            close:function(){$('#help').dialog('destroy');},
                            open:function(){$('#help .help-body').html(data.payload.body);}
                        });
                    }
                })
            });
    });
}

/**
 * A jquery plugin that will turn the given div into a selectable list
 * This expects a top level div with a class of .selectable-list that has 
 * a collection of children each with the class selectable-list-item.  When the user
 * clicks an item the 'selected' class is added or removed depending on whether or 
 * not it was previously selected.  Also, a custom event 'item-selected' is sent from the 
 * top level element.
 */
$.fn.selectify = function(){
    return this.each(function(){
                
        $(this).find('.selectable-list-item').click(function(){
            //mark remove other selections
            $(this).parent().find('.selectable-list-item').removeClass('selected');
            // mark as selected
            $(this).addClass('selected');
            
            $(this).trigger('item-selected',{'selectedid':$(this).attr('id')});
        })
    });
}

/**
 * Verifies that the format of the given string is a valid email address
 * @param <string> email The string to check for format validity
 * @return <bool> Returns true if the email is valid.
 */
grimoire.validate_email = function(email) {
    var regex =  /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
    if (!email.match(regex)) {	
        return false;
    }	
    return true;
}

/**
 * Shows the grimoire standard hovertip.
 * @param id The ID to use for the top level hovertip div
 * @param body The text to display (can include html)
 * @param title The title to display in the hovertip
 * @param footer The footer to dislpay at the bottom (can be empty)
 * @param location Object with x,y fields for where the hovertip should be placed in the document
 */
grimoire.show_hovertip = function(id,body,title,footer,location){
    
    html = "<div class='hovertip' id='" + id +"'><div class='hovertip-title'><div class='title-text'>" + title +
                     "</div><a class='close-hovertip'></a><div style='clear:both'></div></div><div class='copy'>" + body + "</div>";
                     
    if (footer != '') {
        html += '<div class="footer">' + footer + '</div>';
    }
    
    html += '</div>';
        
    $('body').append(html);
        
    $('#'+id).css({
            left: location.x + "px",
            top: location.y + "px"
        }).fadeIn('slow');
        
    // bind to scroll and remove hovertips if the screen is scrolled.  We wait
    //	 a half-second to clear any pending scroll events.
    setTimeout(function(){
        $(window).scroll(function () {
            $('.hovertip').remove();
        })		
    },500);

    $('.close-hovertip').click(function () {
        $('.hovertip').remove();
    });	
}

/**
 * Truncate a string to the given length, breaking at word boundaries and adding an elipsis
 * Author: Andrew Hedges, andrew@hedges.name
 * License: free to use, alter, and redistribute without attribution
 * @param <string> str String to be truncated
 * @param <integer> limit Max length of the string
 * @return string
 */
grimoire.truncate = function(str, limit) {
    var bits, i;
    if (typeof(str) !== 'string') {
        return '';
    }
    bits = str.split('');
    if (bits.length > limit) {
        for (i = bits.length - 1; i > -1; --i) {
            if (i > limit) {
                bits.length = i;
            }
            else if (' ' === bits[i]) {
                bits.length = i;
                break;
            }
        }
        bits.push('...');
    }
    return bits.join('');
};

/**
 * Provides an easier mechanism for opening a new window.  the options
 * object must contain an "href" field.  It can also contain a "name" field
 * to identify the window.  Finally, it can contain any of the window features
 * that the window.open javascript function allows.  All are optional.
 * @param {Object} options See description for what this object should look like
 * @return {Object} Returns the window object that was created
 */
grimoire.newWindow = function(options) {
    
    var features = [];
    if (options.width){
        features.push('width='+options.width);
    }
    if (options.height){
        features.push('height='+options.height);
    }
    if (options.scrollbars){
        features.push('scrollbars='+options.scrollbars);
    }
    if (options.location){
        features.push('location='+options.location);
    }
    if (options.toolbar){
        features.push('toolbar='+options.toolbar);
    }
    if (options.menubar){
        features.push('menubar='+options.menubar);
    }
    if (options.resizable){
        features.push('resizable='+options.resizable);
    }
    
    return window.open(options.href,options.name?options.name:'',features.join(','));
}

grimoire.nl2br = function(text){

    text = escape(text);
    var re_nlchar = null;
	if(text.indexOf('%0D%0A') > -1){
		re_nlchar = /%0D%0A/g ;
	}
    else if(text.indexOf('%0A') > -1){
		re_nlchar = /%0A/g ;
	}
    else if(text.indexOf('%0D') > -1){
		re_nlchar = /%0D/g ;
	}
	return unescape( text.replace(re_nlchar,'<br />') );
}