var DocLoaded = false;
var popupwin;
var PreviewWin;
var CurrentSL = "";
var winFldr;

if (self != top) {
  if(top.location.href.indexOf('reinvigorate') <= 0) {top.location.href = self.location.href;}
}

// On DOM loaded:
document.observe("dom:loaded",
function() {
    session_keepalive();
    init_menu();
    delivery_menu_handler();
    initBasketViewer();
    regularize_panel_heights();
    observe_scrollers();

    if ($('header')) {
        $('header').setStyle({
            'cursor': 'pointer'
        });
        $('header').observe('click',
        function(event) {
            if (Event.element(event).id == 'header' || Event.element(event).id == 'headerInfo' || Event.element(event).hasClassName('logo_wrapper')) {
                window.location.href = '/';
            }
        });
    }
    $$('.drop-shadow').each(function(e) {
        dropShadow(e);
    });
});

// On window complete:
//Event.observe(window, 'load', function(){});

// ###################

function init_menu()
{
  $$('.drawer .more').invoke('observe', 'mouseenter', open_menu);
  $$('.drawer').invoke('observe','mouseleave', close_menu);    
  $$('#leftnav a').invoke('observe', 'click', function(event){
    Event.element(event).blur();
  });
}

function open_menu(event)
{
  close_menu();
  try {
    Event.element(event).up('div')
      .addClassName('open')
        .down('.drawer-content')
          .observe('mouseleave', close_menu);
  }
  catch(e){}
}

function close_menu(event)
{  
  $$('.drawer').reject(function(d) { return d.up('div').hasClassName('more'); }).invoke('removeClassName','open');
}

function delivery_menu_handler()
{
	if ($('externalDeliveryLink')) {
		$('externalDeliveryLink').observe('click', function(event) {
			Event.stop(event);
			var leave = confirm('You are about to be redirected to another Francis Frith website which offers an alternative '+
			                    'range of products for delivery to your location.\n\nWould you like to continue?');
			if (leave) window.location.href = $('externalDeliveryLink').href;
		});
	}
}

 


// ###################


// ###################
function redirect_from_popup_to_sign_in() {
    target = opener ? opener: self;
    target.location.href = target.location.href
    + (target.location.href.indexOf('?') > -1 ? '&': '?')
    + "redirect_popup_to_sign_in=true";
    if (target === opener) setTimeout('self.close();', 250);
}

function observe_scrollers() {
    $$('a.scroll')
    .invoke('observe', 'click', scroll_to)
}

function scroll_to(event) {
    if (event) Event.stop(event);
    link = Event.element(event).href;
    target = link.substring(link.indexOf('#')).replace(/#/, '');
    console.debug(target);
    if (!target) {
        console.error('Target element#ID was not found for scroll_to event');
        return false;
    };
    new Effect.ScrollTo(target, {
        duration: 0.4
    });
}

function reset_scroll() {
    new Effect.ScrollTo('header', {
        duration: 0.4,
        queue: 'end'
    });
}

function pingServer() {
  try{
    new Ajax.Request('/ping/' + (new Date().getTime() * Math.random() * 1000000) + '/', {method:'get'});
  } catch(e){}
}
function session_keepalive() { setInterval('pingServer()', 30000); }

function showPopup(url, width, height) {
    if (popupwin) {
        popupwin.close();
    }
    window.name = "mainwin";
    popupwin = window.open(url, "popup", "resizable=1,statusbar,menubar=no,height=" + height + ",width=" + width + ",scrollbars");
}

function removeContentPadding() {
    document.observe("dom:loaded",
    function() {
        if ($('content')) {
            if ($('contentStripe')) {
                $('content').setStyle({
                    padding: '0px'
                });
                if ($('crumbline')) {
                    $('crumbline').setStyle({
                        paddingLeft: '8px',
                        margin: '0px'
                    });
                }
            }
        }
    });
}

function hideElement(id) {
    var toHide = document.getElementById(id);
    toHide.style.display = "none";
    toHide.style.visibility = "hidden";
}

function showElement(id) {
    var toShow = document.getElementById(id);
    toShow.style.display = "block";
    toShow.style.visibility = "visible";
}

function setCookie(name, value, days) {
    if (days)
    {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        var expires = "; expires=" + date.toGMTString();
    }
    else var expires = "";
    document.cookie = name + "=" + value + expires + "; path=/";
}

function getCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++)
    {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

function eraseCookie(name) {
    setCookie(name, "", -1);
}

function showTimer(target) {
    if ($(target)) $(target).innerHTML = '<div style="font-weight: bold; color:#999; font-size: 9px;"><img src="/images/illustrations/timer.gif" alt="Loading, please wait." style="width:16px; border: 0px none; float: left;" class="timer" /> ...working</div>';
}

function randomizeQstring() {
    return new Date().getTime() * Math.random() * 1000000;
}

function initBasketViewer() {

    if ($('basketViewer')) {

        if ($('basketViewer').up('.dropshadow-container')) $('basketViewer').hide();

        // Watch expansion link:
        $('expandBasketViewer').observe('click', toggleBasketViewer)
        .observe('mouseover',
        function(event) {
            $('expandBasketViewer').addClassName('active')
        })
        .observe('mouseout',
        function(event) {
            $('expandBasketViewer').removeClassName('active')
        });

        // Style content rows:
        alternateRowColoring('basketViewer');
    }

}

function toggleBasketViewer() {
    // Hide the navsearch to prevent it interfering with the viewer:
    if ($('navsearch')) $('navsearch').toggle();

    $('basketViewer').toggle();

    if ($('basketViewer').visible())
    {
        $('expandBasketViewer').down().update('-').addClassName('expanded').removeClassName('collapsed');
    }
    else
    {
        $('expandBasketViewer').down().update('+').addClassName('collapsed').removeClassName('expanded');
    }
    $('expandBasketViewer').blur();
}

function alternateRowColoring(table) {
    $$('#' + table + ' tr:nth-child(odd)').invoke('addClassName', 'odd');
}

function regularize_panel_heights() {
    if ($$('.twocol .panel')) {
        $$('.twocol .panel').each(function(element, index) {
            // Wait for child images to load before applying heights:
            if (element.down('IMG')) {
                new PeriodicalExecuter(function(pe) {
                    if (child_images_loaded(element))
                    {
                        pe.stop();
                        apply_regular_panel_heights(element, index);
                    }
                },
                0.1);
            }
        });
    }
}

function apply_regular_panel_heights(element, index) {
    if (index % 2)
    {
        p1 = element.previous('.panel');
        p2 = element;
        p1_height = p1.getHeight();
        p2_height = p2.getHeight();
        if (p1_height > p2_height)
        {
          template = p1;
          target = p2;
        }
        else
        {
          template = p2;
          target = p1;
        }

        cell = (target.down('.cell').getHeight());
        cell_padding = parseInt(target.down('.cell').getStyle('paddingTop').replace('px', ''));
        cell_padding += parseInt(target.down('.cell').getStyle('paddingBottom').replace('px', ''));
        cell_border = parseInt(target.down('.cell').getStyle('borderTopWidth').replace('px', ''));
        cell_border += parseInt(target.down('.cell').getStyle('borderBottomWidth').replace('px', ''));
        cell_increase = (template.getHeight() - target.getHeight()) - (cell_padding + cell_border);

        target.setStyle({
            'height': template.getHeight() - 3 + 'px'
        });
        target.down('.cell').setStyle({
            'height': (cell + cell_increase) + 'px'
        });

        new Insertion.After(p2, '<div class="clearing"></div>');
    }
}

function child_images_loaded(element) {
    var images_not_loaded = 0;
    element.getElementsBySelector('IMG').each(function(image) {
        if ($(image).loaded) images_not_loaded++;
    });
    return images_not_loaded == 0 ? true: false;
}

// ###################
// Apply and remove drop shadows to given elements:
function dropShadow(element) {
    // Make sure image is loaded before applying DS.
    // Element height is required to correctly size container.
    // Firing this event on DOM-ready enables the PE quickly,
    // and the DS will be applied as each image loads. Golden.
    if (element.nodeName === 'IMG') {
        if (!element.complete) {
            new PeriodicalExecuter(function(pe) {
                if (element.complete) {
                    pe.stop();
                    applyDropShadow(element);
                }
            },
            0.25);
        }
        else {
            applyDropShadow(element);
        }
    }
    else {
        if (element.down('img')) {
            new PeriodicalExecuter(function(pe) {
                if (child_images_loaded(element)) {
                    pe.stop();
                    applyDropShadow(element);
                }
                else { }
            },
            0.25);
        }
        else {
            applyDropShadow(element);
        }
    }
}

function applyDropShadow(element) {
    element = $(element);
    if (element == undefined) element = self;

    //Prevent nested drops:
    if (element.down('.dropshadow-container')) {
        return false;
    }
    if (element.up('.dropshadow-container')) {
        return false;
    }
    //If img is immediately within an A, and agent is IE, apply dropshadow to A tag instead to prevent IE ignoring the link:
    if (element.up().nodeName == "A" && $$('html.ie').length > 0) element = element.up();
    var p = $(element).parentNode;
    var sib = element.next();
    var container = Builder.node('div', {
        className: 'dropshadow-container'
    },
    [
    Builder.node('div', {
        className: 'dropshadow-right'
    }),
    Builder.node('div', {
        className: 'dropshadow-bottom-right'
    }),
    Builder.node('div', {
        className: 'dropshadow-bottom'
    })
    ]);
    p.insertBefore(container, sib);
    $(container).insertBefore(element, container.down('div.dropshadow-bottom-right'));

    container.setStyle({
        'position': element.getStyle('position'),
        'width': parseInt(element.getWidth()) + 8 + 'px',
        'height': parseInt(element.getHeight()) + 7 + 'px',
        'clear': element.getStyle('clear'),
        'float': element.getStyle('float'),
        'marginTop': (element.getStyle('marginTop') ? parseInt(element.getStyle('marginTop')) : 0) + 'px',
        'marginRight': (element.getStyle('marginRight') ? parseInt(element.getStyle('marginRight')) : 0) + 'px',
        'marginBottom': (element.getStyle('marginBottom') ? parseInt(element.getStyle('marginBottom')) : 0) + 'px',
        'marginLeft': (element.getStyle('marginLeft') ? parseInt(element.getStyle('marginLeft')) : 0) + 'px'
    });

    //Remove clearing, float and margin from target-element:
    element.setStyle({
        'position': 'relative',
        'clear': 'none',
        'float': 'right',
        //fixed to the right to prevent white-space between image and right-shadow
        'margin': '0'
    });
    if (element.nodeName == 'IMG') {
        element.setStyle({
            'borderTop': '1px solid #ddd',
            'borderRight': '0px none',
            'borderBottom': '0px none',
            'borderLeft': '1px solid #ddd'
        });
    }
    // Remove padding applied to A tags to allow mouseover border:
    if (element.nodeName == "A") element.setStyle({
        'padding': '0px'
    });

    //Get border widths to build shadow dimensions:
    var top_border_width = element.getStyle('topBorderWidth') ? parseInt(element.getStyle('topBorderWidth')) : 0;
    var right_border_width = element.getStyle('rightBorderWidth') ? parseInt(element.getStyle('rightBorderWidth')) : 0;
    var bottom_border_width = element.getStyle('bottomBorderWidth') ? parseInt(element.getStyle('bottomBorderWidth')) : 0;
    var left_border_width = element.getStyle('leftBorderWidth') ? parseInt(element.getStyle('leftBorderWidth')) : 0;

    shadow_right_height = (parseInt(element.getHeight()) + top_border_width + bottom_border_width);
    shadow_bottom_width = (parseInt(element.getWidth()) + left_border_width + right_border_width);
    if (shadow_right_height > 0) {
        container.down('.dropshadow-right').setStyle({
            'height': shadow_right_height + 'px'
        });
    }

    if (shadow_bottom_width > 0) {
        container.down('.dropshadow-bottom').setStyle({
            'width': shadow_bottom_width + 'px'
        });
    }
}

function removeDropShadow(element) {
    if (element) {
        console.debug(element);
        if (element.up().hasClassName('dropshadow-container')) {
            var p = element.up('div.dropshadow-container');
            Element.remove(p.down('div.dropshadow-right'));
            Element.remove(p.down('div.dropshadow-bottom-right'));
            Element.remove(p.down('div.dropshadow-bottom'));
            element.up(1).insertBefore(element, element.up().next());
            Element.remove(p);
        }
    }

}

// ###################
// ALBUMS + SAVED SEARCHES + WISHLIST
// ###################
function AddToAlbum() {
    var neglist = ""
    var theForm = document.forms[1]
    var ThisElmt

    for (i = 0; i < theForm.elements.length; i++) {
        ThisElmt = theForm.elements[i]
        if ((ThisElmt.type == 'checkbox') && (ThisElmt.checked == true) && (ThisElmt.name == 'addToAlbum')) {
            neglist += ThisElmt.value + ';'
        }
    }
    if (neglist.charAt(neglist.length - 1) == ';') {
        neglist = neglist.substr(0, neglist.length - 1)
    }

    window.status = ""
    if (winFldr) {
        winFldr.close()
    }
    window.name = "mainwin";
    winFldr = window.open("/account/albums/addtoalbum_v2.asp?list=" + neglist + ";&listmode=search", "GetFolder", "statusbar,menubar=no,height=350,width=530,scrollbars=yes")
}

function AddNegToAlbum(neg) {
    if ((neg == '' || neg === undefined) && $F('neg')) neg = $F('neg');
    window.name = "mainwin";
    window.status = ""
    if (winFldr) {
        winFldr.close()
    }
    winFldr = window.open("/account/albums/addtoalbum_v2.asp?list=" + neg + ";&listmode=search", "GetFolder", "statusbar,menubar=no,height=350,width=530,scrollbars=yes")
}

function AddBookCaptionToAlbum(captionid) {
    window.status = ""
    if (winFldr) {
        winFldr.close()
    }
    window.name = "mainwin";
    winFldr = window.open("/account/albums/addtoalbum_v2.asp?list=" + captionid + ";&listmode=bookcaption", "GetFolder", "statusbar,menubar=no,height=350,width=530,scrollbars=yes")
}


function DeselectAll() {
    if (document.all.selimages) {
        for (var x = 0; x < document.all.selimages.length; x++) {
            document.all.selimages[x].checked = false;
        }
    }
}

function SelectAll() {
    for (var x = 0; x < document.all.selimages.length; x++) {
        document.all.selimages[x].checked = true;
    }
}

function CheckAddFromWishList() {
    var FormOK = false
    if (document.frmWL.chkSelect.length) {
        for (var x = 0; x < document.frmWL.chkSelect.length; x++) {
            if (document.frmWL.chkSelect[x].checked == true) {
                FormOK = true
                break;
            }
        }
    } else {
        if (document.frmWL.chkSelect.checked == true) {
            FormOK = true;
        } else {
            FormOK = false;
        }
    }
    if (FormOK == true) {
        return true
    } else {
        alert("You have not selected any Wish List items to add to your basket.")
        return false
    }
}

function RemoveSavedSearch(rid) {
    if (confirm("Are you sure you want to remove this saved search?") == true) {
        document.frmSS.rid.value = rid
        document.frmSS.submit()
    }
}

function selectimg() {}
function deselectimg() {}


function deleteMemory(hascomments, memid, custid, backtopage) {
    var msg = ""
    if (hascomments)
    {
        msg += "This memory has comments attached and will not be deleted automatically.\n"
        msg += "The request for the removal of this memory will be sent to the moderator for approval.\n"
        msg += "You will be notified by email whether or not the memory has been removed.\n\n"
    }
    msg += "Are you sure you want to delete this memory?\n\nThis action cannot be undone."
    if (confirm(msg))
    {
        document.location.href = "/pageloader.asp?page=/account/memories/editmemory.asp&mode=delete&memid=" + memid + "&customerid=" + custid + "&backto=" + backtopage
    }
}

// ###################
// Prevent console debugging errors in browsers without Firebug installed:
if (!window.console || !console.firebug) {
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
    window.console = {};
    for (var i = 0; i < names.length; ++i)
    window.console[names[i]] = function() {}
}
var log = {
    toggle: function() {},
    move: function() {},
    resize: function() {},
    clear: function() {},
    debug: function() {},
    info: function() {},
    warn: function() {},
    error: function() {},
    profile: function() {}
};


// Site-wide message sending functions (used for send messages to memory contributors,etc)
function SendUserAMessage(FromCID, ToCID, ID) {
    var msg = "";
    if ($('_messagesubject').value == '') {
        msg += ' - Subject cannot be blank\n'
    }
    if ($('_messagecontent').value == '') {
        msg += ' - The message cannot be blank\n'
    }
    if ((FromCID == '') || (ToCID == '')) {
        msg += ' - Missing parameters!\n'
    }

    var s = $('_messagesubject').value;
    var m = $('_messagecontent').value;

    params = 's=' + escape(s) + '&m=' + escape(m) + '&f=' + FromCID + '&t=' + ToCID + '&i=' + ID

    if (msg == '') {
        new Ajax.Request('/functions/_sendmessage.asp', {
            parameters: params,
            onComplete: function(returnValue) {
                if (returnValue.responseText == 'ok') {
                    alert('Message Sent!')
                    myLightwindow.deactivate()
                } else if (returnValue.responseText != 'error') {
                    alert('There was an error sending your message - ' + returnValue.responseText)
                } else {
                    alert('There was an error sending your message.')
                }
            }
        })
    } else {
        alert("Error sending your message:\n" + msg);
    }
}

function verifySeal() {
    var bgHeight = '620';
    var bgWidth = '536';
    var url = 'https://seal.godaddy.com:443/verifySeal?sealID=UyKqcJhOfTOuH1Ni97n7D4P8YLOcbOZdVVY4nGpdzG6vXPAokaf8aT1QdX6g';
    window.open(url, 'SealVerfication', 'location=yes,status=yes,resizable=yes,scrollbars=no,width=' + bgWidth + ',height=' + bgHeight);
}


var Autocompleter = { };
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element);
    this.element     = element;
    this.update      = $(update);
    this.hasFocus    = false;
    this.changed     = false;
    this.active      = false;
    this.index       = 0;
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow ||
      function(element, update){
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false,
            offsetTop: element.offsetHeight
          });
        }
        update.show();
      };
    this.options.onHide = this.options.onHide ||
      function(element, update){ update.hide() };

    if(typeof(this.options.tokens) == 'string')
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;

    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix &&
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update,
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },

  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         Event.stop(event);
         return;
      }
     else
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer =
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex)
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },

  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },

  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;
  },

  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ?
          Element.addClassName(this.getEntry(i),"selected") :
          Element.removeClassName(this.getEntry(i),"selected");
      if(this.hasFocus) {
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },

  markPrevious: function() {
   if(this.index > 0) {this.index--;}
   else {
    this.index = this.entryCount-1;
    this.update.scrollTop = this.update.scrollHeight;
   }
   selection = this.getEntry(this.index);
   selection_top = selection.offsetTop;
   if(selection_top < this.update.scrollTop){
    this.update.scrollTop = this.update.scrollTop-selection.offsetHeight;
   }
  },

  markNext: function() {
   if(this.index < this.entryCount-1) {this.index++;}
   else {
    this.index = 0;
    this.update.scrollTop = 0;
   }
   selection = this.getEntry(this.index);
   selection_bottom = selection.offsetTop+selection.offsetHeight;
   if(selection_bottom > this.update.scrollTop+this.update.offsetHeight){
    this.update.scrollTop = this.update.scrollTop+selection.offsetHeight;
   }
  },
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },

  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },

  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = $(selectedElement).select('.' + this.options.select) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');

    var bounds = this.getTokenBounds();
    if (bounds[0] != -1) {
      var newValue = this.element.value.substr(0, bounds[0]);
      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
    } else {
      this.element.value = value;
    }
    this.oldElementValue = this.element.value;
    this.element.focus();

    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount =
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else {
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.update.scrollTop = 0;
      this.index = 0;

      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;
    this.tokenBounds = null;
    if(this.getToken().length>=this.options.minChars) {
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
    this.oldElementValue = this.element.value;
  },

  getToken: function() {
    var bounds = this.getTokenBounds();
    return this.element.value.substring(bounds[0], bounds[1]).strip();
  },

  getTokenBounds: function() {
    if (null != this.tokenBounds) return this.tokenBounds;
    var value = this.element.value;
    if (value.strip().empty()) return [-1, 0];
    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
    var offset = (diff == this.oldElementValue.length ? 1 : 0);
    var prevTokenPos = -1, nextTokenPos = value.length;
    var tp;
    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
      if (tp > prevTokenPos) prevTokenPos = tp;
      tp = value.indexOf(this.options.tokens[index], diff + offset);
      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
    }
    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  }
});

Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  var boundary = Math.min(newS.length, oldS.length);
  for (var index = 0; index < boundary; ++index)
    if (newS[index] != oldS[index])
      return index;
  return boundary;
};

Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    this.startIndicator();

    var entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }
});


// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
};



// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create({
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element);
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
});
