function createForm( options, a_isModal, a_successHandler, a_errorHandler, a_presubmitHandler, a_async )
{
    var _props = {
        element : null,
        isModal : false,
        onSuccess : null,
        onError : null,
        onSubmit : null,
        async : true,
        clearOnSuccess : true,
        onClear : null,
        onRedirect : null,
        onClose : null
    }
   
    if ( typeof(options) == 'object' && ! options.jquery )
    {
        for (var i in options) {
            if (options.hasOwnProperty(i)){
                _props[i] = options[i];
            }
        }
    }
    else
    {
        _props.element = options;
        _props.isModal = a_isModal !== undefined ? a_isModal : _props.isModal;
        _props.onSuccess = a_successHandler;
        _props.onError = a_errorHandler;
        _props.onSubmit = a_presubmitHandler;
        _props.async = a_async !== undefined ? a_async : _props.async;
    }

    if ( ! _props.element || ! _props.element.attr( 'id' ) ) { return; }
    var num = sg.formIndex++;
    var id = _props.element.attr( 'id' );
    var submitting = false;
    var presubmit = true; // false will prevent form from being submitted

    var clearFunction = function() {
        var go = true;
        if ( _props.onClear ) {
            go = _props.onClear();
        }
        if ( go ) 
        {
             $("[id^='"+id+"-input']", _props.element).each( function() {
                var e = $(this);
//                alert('clear '+e.attr('id'));
                var nn = e.get(0).nodeName;
                if ( nn == 'TEXTAREA' ) {
                    e.val('');
                } else if ( nn == 'INPUT' ) {
                    var it = e.attr('type');
                    if ( 'checkbox' == it ) {
                        e.attr( 'checked', false );
                    } else {
                        e.val( '' );
                    }
                } else if ( nn == 'SELECT' ) {
                    e.val( '' );
                }
             } );

             $("[id^='"+id+"-error']").each( function() {
                 $(this).css( 'display', 'none' );
             } );

            if ( _props.isModal ) {
                $.fn.colorbox.resize();
            }
        }
    }

    // hook up the form submit action
    var submitFunction = function() {
        if ( submitting ) return;
        
        // call presubmit
        if ( _props.onSubmit ) {
        	presubmit = _props.onSubmit();
        	if ( ! presubmit ) return;
        }

        submitting = true;
        // hide errors
        $("[id^='"+id+"-error']").each( function() {
            $(this).css( 'display', 'none' );
        } );
        $('#'+id+'-success').css('display','none');
        // show loading
        $('#'+id+'-loading').css( 'display', 'block' );
        if ( _props.isModal ) {
            if( ! $.browser.msie || parseFloat($.browser.version) >= 8 )
            {
                // XXX JQuery has a bug where wrapInner in IE6 & 7 clears
                // checkboxes and radio buttons. Colorbox's resize
                // function calls wrapInner on the CB content in IE6 & 7
                // to estimate the height. For the time being, just 
                // don't resize before submit - it was just for aesthetics
                // do we didn't have a bunch of empty space where error 
                // messages might have been. 
                $.fn.colorbox.resize();
            }
        }

        // submit
        $('#'+id).ajaxSubmit( {
            dataType: 'json',
            async: _props.async,
            success: function( results ) {
             	createFormSuccess(results);
            }, // success
            error:function(xhr, ajaxOptions, thrownError, exception) {
            	// send back the response text and the original parameters
            	var response = xhr.responseText;
                var felmt = $('#'+id);
                var postdata = 'ACTION: '+felmt.attr('action')+
                               ', METHOD: '+felmt.attr('method')+
                               ', PAGE: '+$(location).attr('href')+
                               ', DATA: '+felmt.serialize();
            	$.post(sg.rootpath + '/meta/logerror', { response: response, postdata: postdata } );
            	
            	// stop any loading elements
            	$('#'+id+'-loading').css( 'display', 'none' );
            	
            	// if we can parse json from the response, do it and execute normally
            	// if we can't, alert the user that an error happened
            	if (response.charAt(0) == '{') {
            		var json = response.substring(0, response.lastIndexOf('}') + 1);
            		json = jQuery.trim(json);
            		if (isJsonString( json )) {
            			var cleansedResults = eval('(' + json + ')');
            			createFormSuccess( cleansedResults );
            		}
            	} else {
            		alert( 'Sorry, there was a problem with your request. '+
                           'The error has been reported and we\'ll get it fixed ASAP. '+
                           'In the meantime, please refresh this page and try again.' );
            	}
            	
                submitting = false;
            	return false;
            } // error
        } );
    }; 
    
    var createFormSuccess = function( results ) {
    	//alert(results.returnCode);
        $('#'+id+'-loading').css( 'display', 'none' );
        if ( results.returnCode == 200 ) 
        {
            // success
            if ( _props.onSuccess ) {
                _props.onSuccess( results );
            }

            if ( results.message ) {
                showClientStatus( results.message );
            }
            
            if ( results.alert ) {
            	createSlideAlert( results.alert );
            }
            
            if ( results.data && results.data.javascript ) {
                eval( results.data.javascript );
            }

            if ( ( results.data && ( results.data.keepAlive == true ||
                                     results.data.redirectMode == 'inline' ) ) ||
                 ( results.redirect == 'NONE' ) )
            {
            	var doClose = false;
            } else {
            	var doClose = true;
                if ( _props.onClose ) {
                    doClose = _props.onClose();
                }
            }

            if ( _props.isModal && doClose ) {
                $.fn.colorbox.close();
            }
           
            if ( results.redirect && results.redirect != 'NONE' ) 
            {
                var cont = true;
                if ( _props.onRedirect ) {
                    cont = _props.onRedirect( results );
                }

                if ( cont )
                {
                    if ( results.redirect == 'RELOAD' ) 
                    {
                    	location.reload(true);
                    	//$(location).attr( 'href', $(location).attr('href') );
                    } 
                    else 
                    {
                        if ( results.data && results.data.redirectMode && 
                                results.data.redirectMode == 'inline' )
                        {
                            $.fn.colorbox( {
                                opacity: .6,
                                scrolling: false,
                                href: results.redirect,
                                inline: results.data.inlineHref ? true : false,
                                open: true
                            } );
                        }
                        else
                        {
                            if ( results.redirect.substring(0,1) != '#' ) {
                                $(location).attr( 'href', results.redirect );
                            } else {
                                $(results.redirect).scrollTo();
                            }
                        }
                    }
                }
            } 

            if ( results.inPlaceMessage ) {
                $('#'+id+'-success').html(results.inPlaceMessage);
                $('#'+id+'-success').css('display','block');
            }

            if ( ! ( _props.isModal && doClose ) && _props.clearOnSuccess )
            {
                clearFunction();
            }
        }
        else
        {
        	errorReturn = true;
        	if ( _props.onError ) {
        		errorReturn = _props.onError( results );
        	}

        	if ( errorReturn )
        	{
                // handle error
                $('#'+id+'-error').html( results.message );
                //alert('error, looking for: '+'#'+id+'-error'+ '('+$('#'+id+'-error').length+')');
                $('#'+id+'-error').css( 'display', 'block' );
                for ( field in results.errors ) 
                {
                    var eelmt = $('#'+id+'-error-'+field);
                    if ( eelmt ) {
                        eelmt.html( results.errors[ field ] );
                        eelmt.css( 'display', 'block' );
                    }
                }
                if ( _props.isModal ) {
                    $.fn.colorbox.resize();
                }
        	}
        }
        submitting = false;
    }


    $(_props.element).bind('submit', function() {
        submitFunction();
        return false;
    } );
    
    $('#'+id+'-submit').click( function( e ) {
        submitFunction();
        e.preventDefault();
    } );

    $('.'+id+'-submit').click( function( e ) {
        e.preventDefault();
        submitFunction();
    } );

    // if we are modal, top into the close event
    // to cleanup
    if ( _props.isModal )
    {
        $(_props.element).bind( 'cbox_closed', function() {
            clearFunction();
        } );

        // hook up any cancel action
        $('#'+id+'-cancel').click( function( e ) {
            $.fn.colorbox.close();
            e.preventDefault();
        } );
    }
}

function showClientStatus( message )
{
	createSlideAlert( message );
}

function dateDiff( start, end )
{
    if ( ! start || ! end ) { return null; }
    var diff = Math.ceil( ( end.getTime() - start.getTime() ) / (24*60*60*1000) );
//    alert('diff '+start+'('+sd+') - '+end+' = '+diff);
    return diff;
}

function parseDate( dstr )
{
    if ( ! dstr ) { return null; }
    var parts = dstr.split( '/' );
    if ( ! parts || parts.length != 3 ) { return null; }
    var d = new Date( parts[2], (parseInt(parts[0],10)+12-1)%12, parts[1] );
    return d;
}

function dateToPickString( d )
{
    if ( ! d ) { return null; }
    var mon = d.getMonth()+1;
    var day = d.getDate();
    var year = d.getFullYear();

    var ms = mon < 10 ? '0'+mon : ''+mon;
    var ds = day < 10 ? '0'+day : ''+day;
    var ys = ''+year;

    return ms+'/'+ds+'/'+ys;
}

function getObjectID(elt_id)
{
	if (!elt_id) return false;
	return elt_id.substring(elt_id.lastIndexOf("-") + 1);
}

function IsNumeric(val) 
{
	if (isNaN(parseFloat(val))) {
          return false;
	}
    
	return true
}

function getParam(name, url)
{
	url = url || window.location.href;
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
    var regexS = "[\\?&]"+name+"=([^&#]*)";
    var regex = new RegExp( regexS );
    var results = regex.exec( url );
    if( results == null )
        return "";
    else
    return results[1];
}

function replaceParam(key, val, url)
{
	key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var urlString = url || window.location.href;
    var regexS = "[\\?&]"+key+"=([^&#]*)";
    var regex = new RegExp( regexS );
    var results = regex.exec( window.location.href );
    if( results == null ) {
        return urlString + '&' + key + '=' + val;
    } else {
    	var paramString = results[0];
    	var newParamString = paramString.substring(0, paramString.search('=') + 1) + val;
    	urlString = urlString.replace(paramString, newParamString);
    	return urlString;
    }
}

function roundNumber(num, dec)
{
	var precise = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
	return formatCurrency(precise);
}

function formatCurrency(num)
{
	num = num.toString().replace(/\$|\,/g,'');
	
	if (isNaN(num)) {
		num = "0";
	}
	
	sign = (num == (num = Math.abs(num)));
	num = Math.floor(num*100+0.50000000001);
	cents = num%100;
	num = Math.floor(num/100).toString();
	
	if (cents<10) {
		cents = "0" + cents;
	}
	
	for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++) {
		num = num.substring(0,num.length-(4*i+3))+','+num.substring(num.length-(4*i+3));
	}
	
	return (((sign)?'':'-') + num + '.' + cents);
}

function Left(str, n)
{
	if (n <= 0)
        return "";
    else if (n > String(str).length)
        return str;
    else
        return String(str).substring(0,n);
}

function Right(str, n)
{
    if (n <= 0)
        return "";
    else if (n > String(str).length)
        return str;
    else {
        var iLen = String(str).length;
        return String(str).substring(iLen, iLen - n);
    }
}

function addDays( d, numDays ) 
{
    if ( ! d ) { return null; }
    return new Date( d.getTime() + (numDays*(24*60*60*1000)) );
}

/**
 * Cookie methods
 */
function setCookie(c_name, value, expiredays)
{
	var exdate=new Date();
	exdate.setDate(exdate.getDate()+expiredays);
	document.cookie=c_name+ "=" +escape(value)+";path=/"+
		((expiredays==null) ? "" : ";expires="+exdate.toUTCString());
}

function getCookie(c_name)
{
	if (document.cookie.length>0) {
		c_start=document.cookie.indexOf(c_name + "=");
		if (c_start!=-1) {
			c_start=c_start + c_name.length+1;
			c_end=document.cookie.indexOf(";",c_start);
			if (c_end==-1) c_end=document.cookie.length;
			return unescape(document.cookie.substring(c_start,c_end));
		}
	}
	return "";
}

$(document).ready( function() 
{
    // color box cancel event
    $('.colorbox_cancel').click( function(e) {
        $.fn.colorbox.close();
        e.preventDefault();
    });
    
    // signup redirect modal
    $('.signupRedirect').click( function(e) {
    	if (sg.signupRedirectUrl) {
    		$(location).attr('href', sg.signupRedirectUrl );
    	}
    	e.preventDefault();
    });
    
    // tooltips
    $('.tooltip-link').each(function () {
        // options
        var time = 250;
        var hideDelay = 1000;
        var hideDelayTimer = null;

        // tracker
        var beingShown = false;
        var shown = false;
        
        var trigger = $('.trigger', this);
        var width = $('.offset-width', this).val();
        var height = ($('.offset-height', this).length) ? $('.offset-height', this).val() : 0;
        var popup = $('.tooltip', this).css('left', width + 'px').css('top', height + 'px');
        
        // set the click and mouseout on both element
        $([trigger.get(0), popup.get(0)]).click(function () {
        	// stops the hide event if we move from the trigger to the popup element
        	if (hideDelayTimer) clearTimeout(hideDelayTimer);

        	// don't trigger the animation again if we're being shown, or already visible
        	if (beingShown || shown) {
        		return;
        	} else {
        		popup.css('display', 'block');
        		beingShown = false;
        		shown = true;
        	}
        }).mouseout(function () {
        	// reset the timer if we get fired again - avoids double animations
        	if (hideDelayTimer) clearTimeout(hideDelayTimer);
          
        	// store the timer so that it can be cleared in the mouseover if required
        	hideDelayTimer = setTimeout(function () {
        		hideDelayTimer = null;
        		shown = false;
        		popup.css('display', 'none');
        	}, hideDelay);
        }); // click + mouseout
        
        // set the mouseover for the popup
        $([popup.get(0)]).mouseover(function () {
        	// stops the hide event if we move from the trigger to the popup element
        	if (hideDelayTimer) clearTimeout(hideDelayTimer);

        	// don't trigger the animation again if we're being shown, or already visible
        	if (beingShown || shown) {
        		return;
        	} else {
        		popup.css('display', 'block');
        		beingShown = false;
        		shown = true;
        	}
        }); // mouseover
    }); // each
    
    //$('.tooltip-link').click( function(e) {
    //	width = $(this).width();
    //	tooltip = $(this).find('.tooltip');
    //	tooltip.css({ 'left' : function(){ return width + 17; } });
    //	tooltip.show();
    //});
    
    $('button.follow-link').live('click', function(e) {
    	var url = $(this).find('a').attr('href');
    	window.location = url;
    });
});

$.fn.imagesLoaded = function(callback){
  var elems = this.filter('img'),
      len = elems.length;
      
  elems.bind('load',function(){
      if (--len <= 0){ callback.call(elems,this); }
  }).each(function(){
     // cached images don't fire load sometimes, so we reset src.
     if (this.complete || this.complete === undefined){
        var src = this.src;
        // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
        // data uri bypasses webkit log warning (thx doug jones)
        this.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
        this.src = src;
     }
  });

  return this;
};

/*
 * ScrollToElement 1.0
 * Copyright (c) 2009 Lauri Huovila, Neovica Oy
 *  lauri.huovila@neovica.fi
 *  http://www.neovica.fi
 *  
 * Dual licensed under the MIT and GPL licenses.
 */

(function($) {
    $.scrollToElement = function( $element, speed ) {
        if (!$element || !$element.offset()) {
            return;
        }

        speed = speed || 750;

        $("html, body").animate({
            scrollTop: $element.offset().top,
            scrollLeft: $element.offset().left
        }, speed);
        return $element;
    };

    $.fn.scrollTo = function( speed ) {
        speed = speed || "normal";
        return $.scrollToElement( this, speed );
    };
})(jQuery);

/*
 * Coda bubbles
 */
$(function () {
	createBubbles();
});
function createBubbles()
{
    $('.bubbleInfo').each(function () {
        var distance = 00;
        var time = 250;
        var hideDelay = 500;

        var hideDelayTimer = null;

        var beingShown = false;
        var shown = false;
        var trigger = $('.trigger', this);
        var info = $('.popup', this).css('opacity', 0);


        $([trigger.get(0), info.get(0)]).mouseover(function () {
            if (hideDelayTimer) clearTimeout(hideDelayTimer);
            if (beingShown || shown) {
                // don't trigger the animation again
                return;
            } else {
                // reset position of info box
                beingShown = true;

                info.css({
                    bottom: -5,
                    left: -2,
                    display: 'block'
                }).animate({
                    bottom: '+=' + distance + 'px',
                    opacity: 1
                }, time, 'swing', function() {
                    beingShown = false;
                    shown = true;
                });
            }

            return false;
        }).mouseout(function () {
            if (hideDelayTimer) clearTimeout(hideDelayTimer);
            hideDelayTimer = setTimeout(function () {
                hideDelayTimer = null;
                info.animate({
                    bottom: '+=' + distance + 'px',
                    opacity: 0
                }, time, 'swing', function () {
                    shown = false;
                    info.css('display', 'none');
                });

            }, hideDelay);

            return false;
        });
    });
}

function printStackTrace() {
  var callstack = [];
  var isCallstackPopulated = false;
  try {
    i.dont.exist+=0; //doesn't exist- that's the point
  } catch(e) {
    if (e.stack) { //Firefox
      var lines = e.stack.split('\n');
      for (var i=0, len=lines.length; i<len; i++) {
        if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
          callstack.push(lines[i]);
        }
      }
      //Remove call to printStackTrace()
      callstack.shift();
      isCallstackPopulated = true;
    }
    else if (window.opera && e.message) { //Opera
      var lines = e.message.split('\n');
      for (var i=0, len=lines.length; i<len; i++) {
        if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
          var entry = lines[i];
          //Append next line also since it has the file info
          if (lines[i+1]) {
            entry += " at " + lines[i+1];
            i++;
          }
          callstack.push(entry);
        }
      }
      //Remove call to printStackTrace()
      callstack.shift();
      isCallstackPopulated = true;
    }
  }
  if (!isCallstackPopulated) { //IE and Safari
    var currentFunction = arguments.callee.caller;
    while (currentFunction) {
      var fn = currentFunction.toString();
      var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
      callstack.push(fname);
      currentFunction = currentFunction.caller;
    }
  }
  output(callstack);
}

function output(arr) {
  //Optput however you want
  alert(arr.join('\n\n'));
}

function urlAppend( url, args ) 
{
    var connector = '&';
    if ( url && url.indexOf( '?' ) == -1 ) {
        connector = '?';
    } else if ( ! url ) {
        url = '';
    }
    return url + connector + args;
}

function urlStrip( url, exclude )
{
    var newUrl = url;
    var parts = url.split('?', 2);
    if ( parts.length > 1 && parts[1] )
    {
        var base = parts[0];
        var newArgs = '';
        var first = true;
        var sparts = parts[1].split('&');
        for ( var i = 0; i < sparts.length; i++ )
        {
            var kvparts = sparts[i].split('=', 2);
            if (kvparts && kvparts.length > 0 && kvparts[0] == exclude ) {
                continue;
            }
            else {
                if ( ! first ) newArgs += '&'; else first = false;
                newArgs += kvparts[0];
                if ( kvparts.length > 1 ) newArgs += ( '=' + kvparts[1] );
            }
        }
        if ( newArgs ) {
            newUrl = base + '?' + newArgs;
        } else {
            newUrl = base;
        }
    }
    return newUrl;
}


function urlRootRelative( url )
{
    if ( ! url || url.indexOf( 'http' ) != 0 ) { return url; }
    newUrl = url.substring( url.indexOf( '/', 7 ) );
    //alert('root rel '+url+' = '+newUrl);
    return newUrl;
}

function restoreContext()
{
    $.post( 
        sg.rootpath+'/meta/restorecontextcallback', 
        {},
        function (results) {
            if ( results && 200 == results.returnCode && results.data && results.data.context ) 
            {
                var loc = $(location);
                var rrUrl = urlRootRelative( loc.attr('href') );
                var curUrl = urlStrip( rrUrl, 'cId' );
                if ( curUrl != results.data.context.url ) {
                    //alert('reloading context: current='+curUrl+', new='+results.data.context.url);
                    var url = results.data.context.url;
                    url = urlAppend( url, 'cId='+results.data.context.id );
                    loc.attr('href', url );
                } 
                else if ( results.data.context.modal &&     
                            ( results.data.context.modal.substring( 0, 1 ) == '/' ||
                               results.data.context.modal.substring( 0, 4 ) == 'http' ) ) {
                    showModal( results.data.context.modal );
                }
                else {
                    //alert('raising local context update');
                    $(document).trigger('sg_context_update', [results.data.context.modal, results.data.context.args]);
                }
            }
        },
        'json'
    );
}

function pushContext( context, callback )
{
    if ( context && ( context.url || context.modal ) )
    {
        $.post( 
            sg.rootpath+'/meta/pushcontextcallback', 
            {
                cUrl: context.url,
                cModal: context.modal,
                cArgs: context.args,
                cPost: context.post
            },
            function (results) 
            {
                if (callback) { callback(results); }
            },
            'json'
        );
    }
}

function showModal( name, restore )
{
    //alert('showModal: '+name);
    args = {
        opacity: .6,
        show: true,
        scrolling: false 
    };

    if ( name.substring( 0, 1 ) == '/' || name.substring( 0, 4 ) == 'http' ) {
        args.href = name;
    } else {
        args.href = '#'+name;
        args.inline = true;
    }

    if ( restore ) {
        args.onClosed = restoreContext;
    }
    $.fn.colorbox( args );
}

function isJsonString( str ) {
    try {
    	jQuery.parseJSON(str);
    } catch (e) {
        return false;
    }
    return true;
}

function unparam(p) {
	var params = {};
    var pairs = p.split('&');
    for (var i=0; i<pairs.length; i++) {
        var pair = pairs[i].split('=');
        var accessors = [];
        var name = decodeURIComponent(pair[0]), value = decodeURIComponent(pair[1]);

        var name = name.replace(/\[([^\]]*)\]/g, function(k, acc) { accessors.push(acc); return ""; });
        accessors.unshift(name);
        var o = params;

        for (var j=0; j<accessors.length-1; j++) {
            var acc = accessors[j];
            var nextAcc = accessors[j+1];
            if (!o[acc]) {
                if ((nextAcc == "") || (/^[0-9]+$/.test(nextAcc))) 
                    o[acc] = [];
                else
                    o[acc] = {};
            }
            o = o[acc];
        }
        acc = accessors[accessors.length-1];
        if (acc == "")
            o.push(value);
        else
            o[acc] = value;             
    }
    return params;
}

function parseUrl(url) {
    var a =  document.createElement('a');
    a.href = url;
    return {
        source: url,
        protocol: a.protocol.replace(':',''),
        host: a.hostname,
        port: a.port,
        query: a.search,
        params: (function(){
            var ret = {},
                seg = a.search.replace(/^\?/,'').split('&'),
                len = seg.length, i = 0, s;
            for (;i<len;i++) {
                if (!seg[i]) { continue; }
                s = seg[i].split('=');
                ret[s[0]] = s[1];
            }
            return ret;
        })(),
        file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
        hash: a.hash.replace('#',''),
        path: a.pathname.replace(/^([^\/])/,'/$1'),
        relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
        segments: a.pathname.replace(/^\//,'').split('/')
    };
}

function createSlideAlert( message ) {
	//alert('new slide alert:' + message);
	var $newAlert = $('#alert-slide-master').clone(false).removeAttr('id').addClass('alert-slide');

	$newAlert.find('p').html(message);
	$newAlert.appendTo('#alert-slide-wrapper');
	$newAlert.show('slide', { direction: 'right' }, 400 );
}

