// browser utilities - marcus phillips
//
// a namespace for general browser-related helpers


// this is a convenience wrapper for using jquery's node caching proxy.
// jquery avoids memory leakage issues by setting up a proxy object for any dom node
// provided a dom element or jquery object, this function returns the proxy object to use for safe data storage
// thank you yehuda katz
$.proxy = function(param) {
  var node = $(param)[0];
  var id = $.data(node);
  $.cache[id] = $.cache[id] || {};
  $.cache[id].node = node;
  return $.cache[id];
};

var bu = Object();

bu.log = function(){
  if (self.console) {
    lu.each(arguments, function(which_argument, each_argument){
      console.log(each_argument);
    });
  }
};

bu.relocate = function( destination ){
  if( parent ){ window.parent.location = destination; }
  else        { window.location        = destination; }
};
bu.nocache_uri = function( uri ){
  return bu.update_query_string(uri, {'nocache' : ((new Date()).getTime())});
};
bu.update_query_string = function( uri, new_query_hash ){
  var new_query_string = "";
  var old_query_hash   = {};
  var split_uri = uri.split("?");
  var path      = split_uri[0]  ;
  if( split_uri.length === 2 ){
    old_query_hash = this.parse_query_string(split_uri[1]);
  }
  var combined_query_hash   = jQuery.extend(old_query_hash, new_query_hash);
  var combined_query_string = this.generate_query_string(combined_query_hash);
  if( combined_query_string.length ){
    combined_query_string = "?" + combined_query_string;
  }
  return path + combined_query_string;
};

bu.make_attribute_string = function( attributes ){
  var attribute_string = '';
  lu.each( attributes, function( which_attribute, each_attribute ){
    attribute_string += ' ' + which_attribute + '="' + each_attribute + '"';
  });
  return attribute_string;
};


// query string must be well formed (with no leading question mark and an equals sign for each variable)
bu.parse_query_string = function( query_string ){
  var query_hash = {};
  var query_array = query_string.split("&");
  lu.each(query_array, function(each_var){
    var split_var    = each_var.split("=");
    var name         = split_var[0];
    var value        = split_var[1];
    query_hash[name] = unescape(value);
  });
  return query_hash;
};
// note: this will urlencode values
bu.generate_query_string = function( vars_hash ){
  var vars_array = [];
  lu.each(vars_hash, function(which_var, each_var){
    vars_array.push(which_var + "=" + escape(each_var));
  });
  return vars_array.join("&");
};

bu.append_to_onbeforeunload = function( new_callback ){
    var existing_callback = window.onbeforeunload;
    if( lu.type_of(existing_callback) === 'function' ){
        combined_callback = function(){existing_callback(); new_callback();};
    }
    window.onbeforeunload = combined_callback;
};

bu.browser = Object();
bu.browser.does_support_onbeforeunload = function(){return true;}; //todo: fill out this stub function

// given a set of options and a text input, password input, or textarea, return a like field that contains a hint whenever user content is not resent
// options:
//   hint: the text to show in the field when no user data exists
//   hint_style_class: the class to apply to the input when a hint is present
// return
//   the same dom element that was passed in, but augmented with
//     .scrutenize() - puts the element into a scrutenized state, meaning the hint is not shown.
//        call this before checking my_element.value, or you will be supplied with the hint
//        call scrutenize(false) when you are done scrutenizing the field
bu.hinted = function(options, field){
    options = lu.default_to(options, {});
    options.hint_style_class = lu.default_to(options.hint_style_class, 'bu_hinted_active');
    field = lu.defaulted(field, $('<input type="text"/>'));
    var field_jq = $(field);
    field_jq.addClass('bu_hinted');
    var does_contain_hint         = false;
    var does_contain_user_content = function(){
        return( ! does_contain_hint && field_jq.val() !== '' );
    };
    var show_hint = function( setting ){
        field_jq[     (setting ? 'addClass'   : 'removeClass') ](options.hint_style_class);
        field_jq.val( (setting ? options.hint : ''           ) );
        does_contain_hint = setting;
    };
    var scrutenize = function( under_scrutiny ){
        under_scrutiny = lu.defaulted(under_scrutiny, true);
        if( ! field_jq[0] ){ return false; }
        if( ! does_contain_user_content() ){
            if( under_scrutiny ){ show_hint(false); }
            else                { show_hint(true ); }
        }
    };
    field_jq.focus( function(){scrutenize(true );} );
    field_jq.blur(  function(){scrutenize(false);} );
    if( bu.browser.does_support_onbeforeunload() ){ bu.append_to_onbeforeunload( function(){scrutenize(true);} ); }
    else                                          { $(window).unload(            function(){scrutenize(true);} ); }
    scrutenize(false); //todo: make this check the focus state of the field
    if(field_jq.length){ field_jq[0].scrutenize = scrutenize; }
    return field;
};
// a class-level method that turns on/off the scrutiny of all bu_hinted elements on the page at once - handy before submitting a form
bu.hinted.scrutenize = function( under_scrutiny ){
  $.each( $('.bu_hinted'), function(which, field){
    field.scrutenize(lu.defaulted(under_scrutiny, true));
  });
};

bu.make_attribute_string = function( attributes ){
	var attribute_array = [];
	attribute_string = jQuery.each(attributes, function(which_attribute, each_attribute){
		attribute_array.push( ' '+ which_attribute +'="'+ each_attribute +'"' );
	});
	return attribute_array.join('');
};

bu.limited = function( limit, textarea ){
  var update = function( event ){
      if( (limit < textarea.value.length) && ! lu.in_array( event.keyCode, [8,  37, 38, 39, 40,  46] ) ){
      textarea.value = textarea.value.substring(0, limit);
    }
  };
  $(textarea).keydown( update ).keypress( update ).keyup( update );
  textarea.limit = function(){ return limit; };
  return textarea;
};

bu.limit_textarea = function( textarea, limit ){
  return bu.limited( limit, textarea );
};

bu.window_dimensions = function(){
  //todo: make sure dom is loaded first
  if     ( typeof(window.innerWidth) == 'number'                                                                       ){ return { 'x' : window.innerWidth,                    'y' : window.innerHeight                    }; } //Non-IE
  else if( document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight) ){ return { 'x' : document.documentElement.clientWidth, 'y' : document.documentElement.clientHeight }; } //IE 6+ in 'standards compliant mode'
  else if( document.body            && (document.body.clientWidth            || document.body.clientHeight)            ){ return { 'x' : document.body.clientWidth,            'y' : document.body.clientHeight            }; } //IE 4 compatible
  else                                                                                                                  { return { 'x' : 0,                                    'y' : 0                                     }; }
};

bu.scroll_offset = function(){
  if     ( typeof( window.pageYOffset ) == 'number'                                                                ){ return { 'x' : window.pageXOffset                 , 'y' : window.pageYOffset                 }; } //Netscape compliant
  else if( document.body            && (document.body.scrollLeft            || document.body.scrollTop           ) ){ return { 'x' : document.body.scrollLeft           , 'y' : document.body.scrollTop            }; } //DOM compliant
  else if( document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop) ){ return { 'x' : document.documentElement.scrollLeft, 'y' : document.documentElement.scrollTop }; }//IE6 standards compliant mode
  else                                                                                                              { return { 'x' : 0                                  , 'y' : 0                                  }; }
};

bu.overlay = function(content, options){
  if( typeof(options) == 'undefined' ){ options = {}; }
  var overlay_holder = $('#bu_overlay_holder');
  if( ! overlay_holder.length ){
    overlay_holder = $('<div id="bu_overlay_holder">').css({'display':'none'});
    $('body').prepend(overlay_holder);
  }
  var on_dim = function(){
    var width = lu.defaulted(options.width, 775), padding = 5, margin = 25;
    overlay_holder.css({
      'top'              : (bu.scroll_offset().y     + margin) + 'px',
      'left'             : (/*bu.scroll_offset().x +*/ ((bu.window_dimensions().x - width) /2) ) +'px',
      'width'            : width+'px',
      'position'         : 'absolute',
      'z-index'          : '1000',
      'background-color' : 'white',
      'padding'          : padding+'px'
    });
    overlay_holder.show();
    if(options.on_dim){options.on_dim();}
  };
  $('embed, object, select').css('visibility', 'hidden');
  if( bu.overlay.swap_space ){ bu.overlay.replace(); }
  if( typeof(content) === 'string' ){
    bu.swap_space = content;
    $(content).css({'z-index':'1000', 'position':'absolute'});//note: changing from position:fixed for ie6 compatibility
  }else{
    overlay_holder.empty().append(content);
  }
  $.dimScreen(500, 0.7, on_dim);
};

bu.overlay.swap_space = false;

bu.overlay.clear = function(callback){
  bu.overlay.swap_space = false;
  $('#bu_overlay_holder').hide();
  $('embed, object, select').css('visibility', 'visible');
  $.dimScreenStop(callback);
};


bu.lines = function(strings){ //todo: removing the \r's from here makes strange things happen...
  var concatenation = '';
  if( $.browser.msie ){
      concatenation = strings.join('\r');
  }else{
    $.each(strings, function(which, each){
      concatenation = concatenation + each + '\r';
    });
  }
  return concatenation;
};

var HTMLBlock = function(){
    if($.browser.msie) {
        this.block=Array();
        return;
    }
    this.block='';
};
HTMLBlock.prototype.add = function(html) {
    if($.browser.msie) {
        this.block.push(html);
        return;
    }
    this.block += html;
};
HTMLBlock.prototype.prepend = function(html) {
    if($.browser.msie) {
        this.block.unshift(html);
        return;
    }
    this.block = html + this.block;
};
HTMLBlock.prototype.dump = function() {
    if($.browser.msie) {
        return this.block.join(' ');
    }
    return this.block;
};



// make_selectable turns a dom element into a widget with two states that change when it is clicked
// inputs:
//   element: a dom object to make selectable.  this will be modified
//   on_select: a function that gets run when the item is selected or unselected.  it will be passed the new state of the selectable and the dom element that was toggled
//   preselected: if a truthy value is passed, the item will automatically have selection applied to it upon creation
// output:
//   the dom object passed in as 'element' will be returned, but attached to it will be:
//     selected: a boolean value indicating the items selection state
//     toggle: a function that changes the state to its opposite and invokes the on_select function
//     select: a function that accepts a new state, and if the item is not in that state, calls on_select and sets it to the new one
bu.make_selectable = function(element, on_select, preselected){
  on_select   = lu.default_to(on_select  , function(){} );
  preselected = lu.default_to(preselected, false        );
  element.selected = false; // assumes input element is offered in unselected state
  element.toggle = function(){ element.select(!element.selected); };
  element.select = function(on){
    if(arguments.length < 1){ on = true; }
    if( on != element.selected ){ on_select(on, element); }
    element.selected = Boolean(on);
  };
  $(element).click(element.toggle);
  element.select(preselected);
  return element;
};

/* options may include:
 *   DEPRECATED preselcted: whether all the elements should be selected upon creation
 *     this is no longer allowed, since ie6 will strip the 'checked' state of any checkboxes that get passed to an appendChild call
 *     use at your own risk (http://dev.jquery.com/ticket/769)
 */
bu.selectables = function(items, options){
  options = lu.default_to(options, {});
  // the on_select function will be called when the user is clicked.  regarding arguments, it must take the form of the following function
  // it will be passed
  //   state: whether the element has been turned on or off
  //   element: the dom element for that item
  //   the item used to generate the particular dom manifestation clicked
  options.on_select = lu.defaulted(options.on_select, function(state, element, item){
    $(element).css('background-color', state?'#ddd':'');
  });
  options.element_template = lu.defaulted(options.element_template, function(item){
    return DIV({},item);
  });
  var elements = bu.elements(items, options.element_template);
  jQuery.each(elements, function(which_element, each_element){
    bu.make_selectable(
      each_element,
      function( on, element ){ $(element)[(on?'add':'remove')+'Class']('bu_selected'); options.on_select(on, each_element, each_element.item); },
      options.preselected // unsupported due to http://dev.jquery.com/ticket/769
    );
  });
  return elements;
};

bu.elements = function(items, element_template){
  element_template = lu.defaulted(element_template, function(item){return item;});
  var element, elements = [];
  jQuery.each(items, function(which_item, each_item){
    element = element_template(each_item);
    element.item = each_item;
    elements.push(element);
  });
  return elements;
};

bu.list = function(items, options){
  options = lu.default_to(options, {});
  options.element_template   = lu.default_to(options.element_template  , function(item ){return LI({},item );});
  options.list_template = lu.default_to(options.list_template, function(items){
    var result = UL({});
    $.each(items, function(which, item){
      result.appendChild(DIV({'class':'unnecessary_wrapper'},item));
    });
    return result;
  });
  return options.list_template( bu.elements(items, options.element_template) );
};

// items: a list of objects to be iterated over and turned into selectable dom elements
// on_select_item: is a function to be called when the user selects the element
// element_template: is a function that transforms an object in the items list into a dom element.  it will be passed an individual item
// list_template: is a function that creates a dom element to contain the selectable items.  it will be passed a list of rendered selectable dom elements
bu.selector = function(items, options){
  options = lu.default_to(options, {});
  options = lu.defaults(options, {
    'empty_template' : function(){return DIV({},'ERROR - THIS SELECTOR IS EMPTY!');}
  });
  var selectables = bu.selectables(items, {
    'on_select' : options.on_select,
    'element_template' : options.element_template,
    'preselected' : options.preselected
  });
  var selector = ( items.length ? options.list_template(selectables, {'list_template':options.list_template}) : options.empty_template() );
  selector.selecteds = function(items_only, selection_state){
    if( arguments.length < 1 ){ items_only      = false; }
    if( arguments.length < 2 ){ selection_state = true ; }
    selecteds = [];
    jQuery.each(selectables, function(which_selectable, each_selectable){ // todo: to prevent invalid addres lookups when the user manipulates the dom by other means, change this so that it iterates over the selectables in within the selector node rather than uses its own input list
      if( Boolean(each_selectable.selected) === Boolean(selection_state) ){
        selecteds.push(items_only?each_selectable.item:each_selectable);
      }
    });
    return selecteds;
  };
  selector.select_all = function( new_state ){
    new_state = lu.defaulted(new_state, true);
    jQuery.each(selectables, function(which_selectable, each_selectable){ // todo: to prevent invalid addres lookups when the user manipulates the dom by other means, change this so that it iterates over the selectables in within the selector node rather than uses its own input list
      if( Boolean(each_selectable.selected) !== Boolean(new_state) ){
        each_selectable.select(new_state);
      }
    });
  };
  return selector;
};

bu.popup = function(content, options){
  // options - an object full of configuration parameters, including
  //   top: numeric offset for the top of the popup content
  //     if set to 'center', then the content is centered (with respect to the content's middle, not its top)
  //   on_close:  a callback to be executed when the popup is closed.  it will be passed the circumstances object that you pass into bu.close()
  //   closable_circumstances: an object that will be passed into your on_close callback in the event that the user closes with
  //   autoclose: number of milliseconds before the popup should close automatically.  if set to true, will be defaulted to a short popup duration
  options = lu.defaults(options, {
    'top':50,
    'background-color':'white',
    'closable':false,
    'closable_circumstances':{},
    'on_popup':function(){},
    'on_close':function(){},
    'dim':false,
    'classname':null
  });
  if( options.autoclose === true ){ options.autoclose = 1000; }
  bu.popup.close._on_close = options.on_close;

  if( lu.type_of(options.scroll_y) !== 'undefined' ){ scroll(bu.scroll_offset().x, options.scroll_y); }

  bu.popup._counter += 1;
  var current_counter = bu.popup._counter;
  if( 0 === bu.popup._counter ){
    bu.popup._closer = $(
      DIV(
        {
          'class':'bu-popup-close',
          'style':'float: right; cursor:pointer;',
          'onclick':function( event ){
            options.closable_circumstances.event = event;
            bu.popup.close( options.closable_circumstances );
          }
        },
        IMG({'src':Serdes.make_static_url('/images/slidecom/lightbox/item/close.png'), 'alt':'close'})
      )
    );
    bu.popup._content_holder = $(DIV());
    bu.popup._current = $(DIV({'id':'bu_popup', 'style': 'position:absolute;margin-left:auto;margin-right:auto;'},
      bu.popup._closer[0],
      bu.popup._content_holder[0]
    ));
  }

  if( options.dim ){
    var on_dim = function(){};
    if( options.dim === true ){ options.dim = 0.7; } // options.dim can be a numeric opacity level, or it can be passed as true to indicate default behavior
    var selectors_csv = ['embed', 'object', 'select'].concat(bu.popup.to_hide.selectors).join(', ');
    $(selectors_csv).css('visibility', 'hidden');
    bu.popup._current.find(selectors_csv).css('visibility', 'visible');
    $(content).find(selectors_csv).css('visibility', 'visible');
    $.dimScreen(500, options.dim, on_dim);
  }

  if( bu.popup._content_holder.contents().length ){ bu.popup._stash_contents(); }
  if( options.closable ){ bu.popup._closer.show(); }
  else                  { bu.popup._closer.hide(); }

  bu.popup._content_holder.html(content);
  $('body').append(bu.popup._current);

  bu.popup._current.show();
  if( options.top === 'center' ){
    options.top = bu.window_dimensions().y/2 - bu.popup._current.height()/2;
    if( options.top < 0 ){ options.top = 0; }
  }

  if( bu.popup._current_classname ){
    bu.popup._current.removeClass( bu.popup._current_classname );
    bu.popup._current_classname = null;
  }
  if( options.classname ){
    bu.popup._current.addClass( options.classname );
    bu.popup._current_classname = options.classname;
  }
  var new_css = {
    'z-index': '1000',
    'top':(bu.scroll_offset().y + options.top)+'px',
    'background-color':options['background-color']
  };

  bu.popup._current.css(new_css);
  bu.popup._content_holder.css({
  });


  if( options.padding ){ bu.popup._content_holder.css('padding', options.padding); }

  var width;
  if( options.width ){
    width = options.width;
  }else{
    width = bu.popup._current.width();
  }
  bu.popup._current.css({
    'width' : width+'px',
    'left': (bu.window_dimensions().x/2 - width/2)+'px'
  });

  if( options.on_popup ){ options.on_popup(); }
  if( options.autoclose ){
    setTimeout(function(){
      if( bu.popup._counter === current_counter ){
        bu.popup.close();
      }
    },options.autoclose);
  }
};

bu.popup.to_hide = function( selectors ){
  $.each( selectors, function(which, selector){
    bu.popup.to_hide.selectors.push(selector);
  });
};
bu.popup.to_hide.selectors = [];

bu.popup._counter = -1;
bu.popup._current_classname = null;

bu.popup.close = function( circumstances ){
  if( !bu.popup._current ){ return; }
  bu.popup._current[0].style.width = 'auto';
  bu.popup._stash_contents();
  $(['embed', 'object', 'select'].concat(bu.popup.to_hide.selectors).join(', ')).css('visibility', 'visible');
  $.dimScreenStop(); // NOTE! this line must appear before the call to hide() of #bu_popup, or it will crash IE - possibly because #bu_popup is on a higher z index.
  $('#bu_popup').hide(); // NOTE!! this line must appear after the call to $.dimScreenStop(), or it will crash IE.  Ten Grand is buried there.
  bu.popup.close._on_close( circumstances );
};


bu.popup._stash_contents = function(){
  bu.popup._stashes = $(DIV({'id':'bu_popup_stashes', 'style':'display:none;'}));
  $('body').append(bu.popup._stashes);
  var unique_stash_id = 'bu_popup_stash_'+(bu.popup._counter-1);
  var unique_stash = $( DIV({'id':unique_stash_id}) ).append(
    bu.popup._content_holder.contents()
  );
  bu.popup._stashes.append(unique_stash);
};


bu.strip_tags = function( source ){ return source.replace(/<&#91;^>&#93;*>/g, ""); };


bu.elipsis = function( token, max ){
  return token.length <= max ? token : token.slice(0, Math.max(max-3, 1)).concat('...');
};
