if(typeof(LJH) == 'undefined')
  var LJH = new Object();

// Controller Class for environment
// --------------------------------
LJH.Control = {
  domLoaded      : false,
  lvl0Scripts    : new Array(),
  lvl2Scripts    : new Array(),
  loadScripts    : new Array(),
  loadStart      : null,
  init           : function(){
                     if(arguments.length > 0)
                       var loadLevel = arguments[0];
                     else
                       loadLevel = 1;

                     if(!this.domLoaded){
                       this.loadStart    = new Date();
                     
                       for(var i = 0; i < this.lvl0Scripts.length; i++){
                         if(typeof(this.lvl0Scripts[i]) == 'function'){
                           this.lvl0Scripts[i]();
                         }
                       }                       
                     }
                     if(loadLevel > 0){
                       this.domLoaded = true;

                       for(var i = 0; i < this.loadScripts.length; i++){
                         if(typeof(this.loadScripts[i]) == 'function'){
                           this.loadScripts[i]();
                         }
                       }
                       if(LJH.StartTime && $("error_pane")){
                         var time = new Date();
                         $("error_pane").innerHTML += "\nLOAD TIME: " + parseFloat((time.getTime() - LJH.StartTime) / 1000) + " Sec\n";
                         $("error_pane").innerHTML += "LOAD SCRIPTS TIME: " + parseFloat((time.getTime() - this.loadStart.getTime()) / 1000) + " Sec\n";
                       }
                       window.setTimeout(this.initDeferred.bind(this), 1000); //run deferred scripts 1 second after load
                     }
                     this.domLoaded = true;
                   },
  initDeferred   : function(){
                     for(var i = 0; i < this.lvl2Scripts.length; i++){
                       if(typeof(this.lvl2Scripts[i]) == 'function')
                         this.lvl2Scripts[i]();
                     }                       
                   },
  listSetup      : function(id){
                     var list       = document.createElement("div");
                     list.className = "list_dialog";
                   },
  positionSlider : function(){
                     if(this.domLoaded){
                       var pos = Position.cumulativeOffset($('slider_spacer'));
                       var x   = pos[0];
                       var y   = pos[1];
                       $('slider_position').style.left = (x - 1) + "px";
                       $('slider_position').style.top = (y) + "px";
                     }
                   },
  openPDF        : function(href){
                     window.open(href, "");
                   },
  checkEnter     : function(func){
                     return function(){
                       if(arguments.length > 0){
                         var code = Event.getKeyCode(arguments[0]);
                         if(code == Event.KEY_RETURN){
                           func(arguments[0]);
                         }
                       }    
                     }
                   },
  clearDefault:    function(field){
                     field = $(field);
                     field.setAttribute('default', 1);
                     field.observe('focus', function(){ 
                       if(field.getAttribute('default') == 1){
                         field.setAttribute('default', 0);
                         field.value = '';
                         if(typeof field.defaultAction == 'function')
                           field.defaultAction();
                       }
                     });
                   }
};

Event.observe(window, "DOMContentLoaded", LJH.Control.init.bind(LJH.Control, 0));
Event.observe(window, 'load', function(){ LJH.Control.init(); });

// for Internet Explorer (using conditional comments)
/*@cc_on @*/
/*@if (@_win32)
document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
  if (this.readyState == "complete") {
    LJH.Control.init(0); // call the onload handler
  }
};
/*@end @*/
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

// Environment Settings
// --------------------
/* Environment Object */
if(!Env)
  var Env = new Object();
  
Env.is_opera   = (navigator.userAgent.indexOf("Opera") >= 0);
Env.is_safari  = ((navigator.appVersion.indexOf("Konqueror") >= 0) || 
                          (navigator.appVersion.indexOf("Safari") >= 0));
Env.is_mozilla = (!Env.is_safari && navigator.userAgent.indexOf("Gecko") >= 0);
Env.is_ie      = (document.all && !Env.is_opera);

Env.scrollTop   = function(){ return (document.documentElement) ? document.documentElement.scrollTop : document.body.scrollTop; };
Env.scrollLeft  = function(){ return (document.documentElement) ? document.documentElement.scrollLeft : document.body.scrollLeft; };

Env.windowHeight = function(){ return (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight); };
Env.windowWidth  = function(){ return (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth); };

Env.FPS = 30;

/* Prototype Extensions */
var $$$ = function(selector){
  var result = $$(selector);
  return (result.length > 0) ? result[0] : null
};

var $_ = $$$;

Object.equals = function(o1, o2){
  for(var i in o1){
    if(o2[i] != o1[i])
      return false;
  }
  for(var i in o2){
    if(o2[i] != o1[i])
      return false;
  }
  return true;
};

/* Regular Expressions */
LJH.re          = new Object();
LJH.re.mail     = /^[a-z0-9][^\(\)\<\>\@\,\;\:\\\"\[\]]*\@[a-z0-9\.\-]*\.[a-z]{2,4}$/i;
LJH.re.blank    = /^\s*$/;
LJH.re.postcode = /^\d{4,4}$/;

/* String Extensions */
LJH.trim   = function(string){ return string.replace(/^\s+|\s+$/g, ''); };
String.prototype.trim = function() { return LJH.trim(this); };

LJH.ObjectClone  = function(toClone){
                     var clone = new toClone.constructor();
                     for(var property in toClone){
                       if(typeof toClone[property] == 'object'){
                         clone[property] = LJH.ObjectClone(toClone[property]);
                       } else {
                         clone[property] = toClone[property];
                       }
                     }
                     return clone;
                   };

Event.getKeyCode = function(e) { return (e.charCode) ? e.charCode : e.keyCode; };

LJH.AddSelectOption = function(element, node){
                        if(Env.is_ie)
                          element.add(node);
                        else
                          element.add(node, null);                     
                      };
                      
LJH.ClearSelectList = function(element){ 
                        var count = element.options.length - 1;
                        for(var i = count; i >= 0; i--)
                          element.options[i] = null;
                      };
                      
LJH.NewSelectOption = function(value, text){
                        var node   = document.createElement('option');
                        node.value = value;
                        node.text  = text;
                        return node;
                      };

LJH.Cookie          = new Object();
LJH.Cookie.get      = function(name){
                        var find    = new RegExp('^(.*; )?'+name+'=([^;]*)(;|$)');
                        var matches = document.cookie.match(find);
                        if(matches)
                          return matches[2];
                        return "";
                      };
                      
LJH.Cookie.set      = function(name, value){ // accepts name, value, expires (in seconds)
                        var cookieString = name+'='+value+'; ';
                        var expires;
                        if(arguments.length > 2){
                          var date       = new Date();
                          date.setTime(date.getTime() + (arguments[2] * 1000)); // expires in seconds
                          cookieString  += "expires="+date.toGMTString()+"; ";
                        }
                        cookieString += "path=/";
                        
                        document.cookie = cookieString;
                      };
                      
LJH.Cookie.kill     = function(name){
                        LJH.Cookie.set(name, "", -1*24*60*60);
                      };

LJH.NumberFormat    = function(num){
                        var decimals  = (arguments.length > 1 && !isNaN(parseInt(arguments[1]))) ? arguments[1] : 0;
                        var seperator = (arguments.length > 2) ? arguments[2].toString() : ",";
                        
                        num = num.toFixed(decimals);
                        
                        while(num.indexOf(seperator) > 3 || (num.indexOf(seperator) == -1 && num.indexOf(".") > 3)){
                          var index = (num.indexOf(seperator) != -1) ? num.indexOf(seperator) : num.indexOf(".");
                          num       = num.substr(0, index - 3) + seperator + num.substr(index - 3);
                        }
                        return num;
                      };

LJH.MyLJHControls = new Object();
Object.extend(LJH.MyLJHControls, {
  checkLogin:   function(){
                  var login = LJH.Cookie.get('MyLJHLogin');
                  if(login)
                    return true;
                  else
                    return false;
                },
  shortlist:    function(propertyType, propertyCode){
                  if(this.checkLogin()){
                    new Ajax.Request('/callbacks/shortlist.php', 
                                     { method:     'post',
                                       parameters: 'propertyType='+propertyType+'&propertyCode='+propertyCode+'&x='+(new Date()).getTime(),
                                       onComplete: this.shortlisted.bind(this, propertyType, propertyCode) });
                  } else {
                    window.location.href = "/myljhooker.php?act=shortlist&propertyType="+propertyType+"&propertyCode="+propertyCode;
                  }
                },
  shortlisted:  function(propertyType, propertyCode, request){
                  var response;
                  try {
                    response = eval("(" + request.responseText + ")");
                  } catch(e){
                    response = null;
                  }

                  if(response && response.returnCode == 0){
                    alert("The property has been added to your shortlist.");
                  } else if(response && response.returnCode == -6){
                    window.location.href = "/myljhooker.php?act=shortlist&propertyType="+propertyType+"&propertyCode="+propertyCode;
                  } else if(response){
                    alert(response.errorMsg);
                  } else {
                    window.location.href = "/myljhooker.php?act=shortlist&propertyType="+propertyType+"&propertyCode="+propertyCode;
                  }
                }
});

/* Events Handling For Objects */
LJH.EventsBase = {
  fireEvent:            function(){
                          var args  = $A(arguments);
                          var event = args.shift();
                          if(this.events[event]){
                            return this.events[event].all(function(value, index){ return value.apply(null, args); });
                          }
                          return true;
                        },
  registerEvent:        function(event, handler){
                          if(this.events[event])
                            this.events[event].push(handler);
                        }
};

LJH.Observable = Class.create({
  evtinit:        function(){
                    if(!this._observers)
                      this._observers = { };
                  },
  observe:        function(evt, callback){
                    this.evtinit();
                    if(!this._observers[evt])
                      this._observers[evt] = new Array();
                    
                    this._observers[evt].push(callback);
                  },
  stopObserving:  function(evt, callback){
                    this.evtinit();
                    if(this._observers[evt] && this._observers[evt].length > 0){
                      if(!callback)
                        this._observers[evt] = new Array();
                      else
                        this._observers[evt] = this._observers[evt].reject(function(observer){ return (observer == callback); });
                    }
                  },
  fire:           function(evt){
                    this.evtinit();
                    var args = $A(arguments); args.shift();
                    var t = this;
                    if(this._observers[evt] && this._observers[evt].length > 0)
                      this._observers[evt].each(function(callback){ callback.apply(t, args); });
                  }
});

LJH.Destructable = Class.create({
  desinit:        function(){
                    if(!this._allocations)
                      this._allocations = [ ];
                  },
  createObserver: function(element, event, callback){
                    this.desinit();
                    this._allocations.push({ element: element, event: event, callback: callback});
                    element.observe(event, callback);
                  },
  clearObserver:  function(element, event){
                    this.desinit();
                    this._allocations.each(function(alloc){
                      if(alloc.element == element && alloc.event == event)
                        element.stopObserving(alloc.event, callback);
                    });
                  },
  clearObservers: function(element){
                    this.desinit();
                    this._allocations.each(function(alloc){
                      if(alloc.element == element)
                        element.stopObserving(alloc.event, callback);
                    });
                  },
  flushObservers: function(){
                    this.desinit();
                    this._allocations.each(function(alloc){
                      alloc.element.stopObserving(alloc.event, alloc.callback);
                    });
                  }
});

LJH.Timer = {
  times:  { },
  start:  function(key){
            if(!this.times[key])
              this.times[key]     = { time: 0, start: 0 };
              
            this.times[key].start = (new Date()).getTime();
          },
  stop:   function(key){
            if(this.times[key])
              this.times[key].time += (new Date()).getTime() - this.times[key].start;
          },
  get:    function(key){
            var reset = (arguments.length > 1) ? arguments[1] : false;
            var time  = null;
            if(this.times[key])
              time = this.times[key].time;
              
            if(reset) this.reset(key);
            
            return time;
          },
  reset:  function(key){
            if(this.times[key])
              this.times[key].time = 0;
          }
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.ButtonControl = Class.create();
Object.extend(LJH.ButtonControl.prototype, {  
  initialize: function(node_id, classes){
                this.node     = $(node_id);
                this.classes  = classes;
                
                this.inactive = false;
                if(this.classes.down)
                  Event.observe(this.node, 'mousedown', this.mousedown.bind(this));
                if(this.classes.up)
                  Event.observe(this.node, 'mouseup', this.mouseup.bind(this));
                if(this.classes.up && this.classes.down)
                  Event.observe(this.node, 'mouseout', this.mouseup.bind(this));
                if(this.classes.over)
                  Event.observe(this.node, 'mouseover', this.mouseover.bind(this));
                if(this.classes.out)
                  Event.observe(this.node, 'mouseout', this.mouseout.bind(this));
                if(this.classes.inactive)
                  this.inactive = this.classes.inactive;
                this.node.control = this;
                
                if(arguments.length > 2){
                  this.href = arguments[2];
                  Event.observe(this.node, 'click', this.navigate.bind(this));
                } else if(this.node.getAttribute('href')){
                  this.href = this.node.getAttribute('href');
                  Event.observe(this.node, 'click', this.navigate.bind(this));
                }
              },
  mousedown:  function(){
                if(!this.node.hasClassName(this.inactive)){
                  this.node.removeClassName(this.classes.up);
                  this.node.addClassName(this.classes.down);
                }
              },
  mouseup:    function(){
                if(!this.node.hasClassName(this.inactive)){
                  this.node.removeClassName(this.classes.down);
                  this.node.addClassName(this.classes.up);
                }
              },
  mouseover:  function(){
                if(!this.node.hasClassName(this.inactive)){
                  this.node.removeClassName(this.classes.out);
                  this.node.addClassName(this.classes.over);
                }
              },
  mouseout:   function(){
                if(!this.node.hasClassName(this.inactive)){
                  this.node.removeClassName(this.classes.over);
                  this.node.addClassName(this.classes.out);
                }
              },
  deactivate: function(){
                if(this.inactive){
                  if(this.classes.over)
                    this.node.removeClassName(this.classes.over);
                  if(this.classes.out)
                    this.node.removeClassName(this.classes.out);
                  if(this.classes.up)
                    this.node.removeClassName(this.classes.up);
                  if(this.classes.down)
                    this.node.removeClassName(this.classes.down);
                  this.node.addClassName(this.inactive);
                }
              },
  activate:   function(){
                this.node.removeClassName(this.inactive);
                if(this.classes.up)
                  this.node.addClassName(this.classes.up);
                else if(this.classes.out)
                  this.node.addClassName(this.classes.over);
              },
  navigate:   function(){
                window.location.href = this.href;
              }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.CompareWidget = Class.create(LJH.Observable, {
  initialize:   function(container, properties, callback, compareType){
                  this.container    = $(container);
                  this.limit        = 5;
                  this.properties   = properties;
                  this.callback     = callback;
                  this.compareType  = compareType;
                  
                  var lists = this.container.select('ul');
                  var items;
                  
                  var areas = new Array(), obj = this;
                  
                  lists.each(function(list, i){
                    var items = list.select('li');
                    areas.push({ 'text': items[0], 'remove': items[1] });
                    if(items[1].select('a').length > 0)
                      items[1].select('a')[0].observe('click', obj.remove.bind(obj, properties[i].propertyCode, properties[i].propertyType));
                  });
                  
                  this.areas = areas;
                },
  setupAdd:     function(elem, text, propertyCode, propertyType){
                  elem.observe('click', this.add.bind(this, text, propertyCode, propertyType, false));
                },
  setupClear:   function(elem){
                  elem.observe('click', this.clearAll.bind(this));
                },
  setupCompare: function(elem){
                  elem.observe('click', this.compare.bind(this));
                },
  erase:        function(){
                  var obj = this;
                  this.areas.each(function(area, i){
                    area.text.innerHTML = "";
                    if(area.remove.select('a').length)
                      area.remove.select('a')[0].stopObserving('click', obj.remove.bind(obj, obj.properties[i].propertyCode, obj.properties[i].propertyType));
                    area.remove.innerHTML  = "";
                  });
                },
  clearAll:     function(e){
                  Event.stop(e);
                  if(!this.properties.length)
                    return false;
                  //Update server-side data, no need to evaluate a response
                  var request = new Ajax.Request(this.callback, { method: 'get', 
                                                                  parameters: { clear:       1,
                                                                                comparetype: this.compareType,
                                                                                x:           (new Date()).valueOf() } });
                                                                  
                  
                  this.erase();
                  this.properties = new Array();
                  
                  this.fire('clear');
                },
  add:          function(text, propertyCode, propertyType, skipCallback){
                  if(this.properties.length >= this.limit){
                    alert("You may only select up to "+this.limit+" properties for comparison. Please remove at least one from the list to add new properties.");
                    return false;
                  }
                  
                  if(this.properties.find(function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); }))
                    return false;
                    
                  this.properties.push({ 'text': text, 'propertyCode': propertyCode, 'propertyType': propertyType });
                  if(!skipCallback){
                    var parameters = { add:          propertyCode,
                                       propertytype: propertyType,
                                       comparetype:  this.compareType,
                                       x:      (new Date()).valueOf() };
                      
                    //Update server-side data, no need to evaluate a response
                    var request = new Ajax.Request(this.callback, { method:     'get', 
                                                                    parameters: parameters });
                  }
                  
                  var delLink = new Element('a', { href: 'javascript:void(0);' });
                  var delImg  = new Element('img', { src: '/images/focuscolumn/x.gif', alt: 'x', border: '0' });
                  
                  delLink.appendChild(delImg);
                  
                  var i = this.properties.length - 1;
                  
                  this.areas[i].text.innerHTML = "<a href=\"/property_profile.php?propertycode="+propertyCode+"&propertytype="+propertyType+"\">"+text+"</a>";
                  this.areas[i].remove.appendChild(delLink);
                  
                  delLink.observe('click', this.remove.bind(this, propertyCode, propertyType));
                  
                  this.fire('add', propertyCode, propertyType);
                },
  remove:       function(propertyCode, propertyType){
                  var test    = function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); };
                  var include = this.properties.reject(test);
                
                  if(include.length == this.properties.length)
                    return false;
                    
                  //Update server-side data, no need to evaluate a response
                  var parameters = { remove:       propertyCode,
                                     comparetype:  this.compareType,
                                     propertytype: propertyType,
                                     x:            (new Date()).valueOf() };
                      
                  var request = new Ajax.Request(this.callback, { method:    'get', 
                                                                  parameters: parameters });
                
                  this.erase();
                  this.properties = new Array();
                  
                  var obj = this;
                  include.each(function(property){ obj.add(property.text, property.propertyCode, property.propertyType, true); });
                  
                  this.fire('remove', propertyCode, propertyType);
                },
  toggle:       function(text, propertyCode, propertyType){
                  if(this.isset(propertyCode, propertyType))
                    this.remove(propertyCode, propertyType);
                  else
                    this.add(propertyCode, propertyType);
                }, 
  compare:      function(e){
                  Event.stop(e);
                  if(this.properties.length > 0)
                    window.location.href = '/compare.php?comparetype='+this.compareType;
                },
  isset:        function(propertyCode, propertyType){
                  return this.properties.find(function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); });
                }
});

LJH.CompareControl = Class.create(LJH.Observable, {
  initialize:   function(properties, callback, compareType){
                  this.limit        = 5;
                  this.properties   = properties;
                  this.callback     = callback;
                  this.compareType  = compareType;
                },
  add:          function(text, propertyCode, propertyType, skipCallback){
                  if(this.properties.length >= this.limit){
                    alert("You may only select up to "+this.limit+" properties for comparison. Please remove at least one from the list to add new properties.");
                    return false;
                  }
                  
                  if(this.properties.find(function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); }))
                    return false;
                    
                  this.properties.push({ 'text': text, 'propertyCode': propertyCode, 'propertyType': propertyType });
                  if(!skipCallback){
                    var parameters = { add:          propertyCode,
                                       propertytype: propertyType,
                                       comparetype:  this.compareType,
                                       x:      (new Date()).valueOf() };
                      
                    //Update server-side data, no need to evaluate a response
                    var request = new Ajax.Request(this.callback, { method:     'get', 
                                                                    parameters: parameters });
                  }
                  
                  this.fire('add', propertyCode, propertyType);
                },
  remove:       function(propertyCode, propertyType){
                  var test    = function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); };
                  var include = this.properties.reject(test);
                
                  if(include.length == this.properties.length)
                    return false;
                    
                  //Update server-side data, no need to evaluate a response
                  var parameters = { remove:       propertyCode,
                                     comparetype:  this.compareType,
                                     propertytype: propertyType,
                                     x:            (new Date()).valueOf() };
                      
                  var request = new Ajax.Request(this.callback, { method:    'get', 
                                                                  parameters: parameters });
                
                  this.properties = new Array();
                  
                  var obj = this;
                  include.each(function(property){ obj.add(property.text, property.propertyCode, property.propertyType, true); });
                  
                  this.fire('remove', propertyCode, propertyType);
                },
  toggle:       function(text, propertyCode, propertyType){
                  if(this.isset(propertyCode, propertyType))
                    this.remove(propertyCode, propertyType);
                  else
                    this.add(text, propertyCode, propertyType);
                }, 
  isset:        function(propertyCode, propertyType){
                  return !!this.properties.find(function(property){ return (property.propertyCode == propertyCode && property.propertyType == propertyType); });
                }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.ContactForm = Class.create();
Object.extend(LJH.ContactForm.prototype, {  
  initialize:       function(formNode, areas, sendButton, clearButton, initLocation){
                      this.formNode     = $(formNode);
                      this.country      = this.formNode.country;
                      this.areas        = areas;
                      
                      this.initLocation = initLocation;
                      
                      
                      Event.observe(this.country, 'change', this.changeCountry.bind(this));
                      Event.observe(sendButton.node, 'click', this.sendContact.bind(this));
                      Event.observe(clearButton.node, 'click', this.clearForm.bind(this));
                    },
  changeCountry:    function(){
                      var code = this.country.options[this.country.selectedIndex].value;
                      if(this.areas[code]){
                        this.hideAreas(code);
                        this.showAreas(code);
                      }
                    },
  hideAreas:        function(except){
                      for(var i in this.areas){
                        if(i == except)
                          continue;

                        for(var j = 0; j < this.areas[i].length; j++)
                          $(this.areas[i][j]).style.display = 'none';
                      }
                    },
  showAreas:        function(code){
                      if(this.areas[code]){
                        var showAreas = this.areas[code];
                        for(var i = 0; i < showAreas.length; i++)
                          $(showAreas[i]).style.display = 'block';
                      }
                    },
  sendContact:      function(){
                      if(!this.formNode.email_address.value.trim()){
                        alert("Please enter your email address!");
                        return false;
                      }
                      if(!this.formNode.email_address.value.match(LJH.re.mail)){
                        alert("That does not appear to be a valid email address!");
                        return false;
                      }
                      if(this.formNode.comments.value.match(/^\s*$/)){
                        alert("Please enter the details of your message!");
                        return false;
                      }
                      this.formNode.submit();
                      return true;
                    },
  clearForm:        function(){ 
                      this.formNode.reset(); 
                      this.hideAreas(this.initLocation);
                      this.showAreas(this.initLocation);
                    }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.GoogleMaps = Class.create();
Object.extend(LJH.GoogleMaps.prototype, {
  initialize:       function(map, positions, limit){
                      this.map       = map;
                      this.positions = positions;
                      this.limit     = limit;
                      this.padding   = 2;
                      this.init      = false;
                      
                      this.times     = new Object();
                      
                      if(arguments.length > 3)
                        this.icon    = arguments[3];
                      else
                        this.icon    = G_DEFAULT_ICON;
                      
                      if(arguments.length > 4)
                        this.pluralIcon = arguments[4];
                      else
                        this.pluralIcon = this.icon;

                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      GEvent.addListener(map, "load", this.refresh.bind(this, true));
                      GEvent.addListener(map, "zoomend", this.refresh.bind(this));
                      GEvent.addListener(map, "moveend", this.move.bind(this));
                    },
  refresh:          function(){
                      if(arguments.length > 0 && arguments[0])
                        this.init = true;
                      
                      if(!this.init)
                        return;
                      
                      // clear only the markers managed by this object
                      var gmap = this.map;
                      this.inBounds.each(function(marker){ gmap.removeOverlay(marker); });
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      var grid       = new Object();
                      var bounds     = this.map.getBounds();
                      var inclBounds = this.getMarkerBounds(); 
                      
                      var point, hash, marker;
                      
                      for(var i = 0; i < this.positions.length; i++){
                        point    = this.map.fromLatLngToDivPixel(this.positions[i].getLatLng());
                        hash     = this.gridHash(point);
                        
                        if(!grid[hash]) grid[hash] = new Array();
                        
                        grid[hash].push(this.positions[i]);
                      }
                      
                      for(var hash in grid){
                        var center = this.hashCenter(hash);
                        if(inclBounds.contains(center)){
                          marker = this.createMarker(grid[hash]);
                          this.map.addOverlay(marker);
                          this.inBounds.push(marker);
                        } else {
                          this.extras.push({ hash: hash, positions: grid[hash] });
                        }
                      }
                    },
  move:             function(){
                      var bounds    = this.getMarkerBounds();
                      var map       = this.map;
                      
                      var newOut = this.inBounds.select(function(marker){ return !bounds.contains(marker.getPoint()); });
                      newOut.each(function(marker){ map.removeOverlay(marker); });
                      this.inBounds = this.inBounds.without.apply(this.inBounds, newOut);
                      
                      var remExtras = new Array();
                      for(var i = 0; i < this.extras.length; i++){
                        if(bounds.contains(this.hashCenter(this.extras[i].hash))){
                          marker = this.createMarker(this.extras[i].positions);
                          this.map.addOverlay(marker);
                          this.inBounds.push(marker);
                          remExtras.push(i);
                        }
                      }
                      
                      this.extras = this.extras.select(function(extra, i){ return (remExtras.indexOf(i) == -1); });
                      
                      var newIn  = this.outBounds.select(function(marker){ return bounds.contains(marker.getPoint()); });
                      newIn.each(function(marker){ map.addOverlay(marker); });
                      this.outBounds = this.outBounds.without.apply(this.outBounds, newIn);
                      
                      this.inBounds  = this.inBounds.concat(newIn);
                      this.outBounds = this.outBounds.concat(newOut);
                    },
  gridHash:         function(point){
                      var x = Math.floor(point.x / this.limit);
                      var y = Math.floor(point.y / this.limit);
                      
                      return x.toString()+"_"+y.toString();
                    },
  hashCenter:       function(hash){
                      var xy     = hash.match(/([0-9\-]+)_([0-9\-]+)/);
                      
                      var point  = new GPoint((xy[1] * this.limit) + Math.round(this.limit / 2), (xy[2] * this.limit) + Math.round(this.limit / 2));
                      return this.map.fromDivPixelToLatLng(point);
                    },
  getMarkerBounds:  function(){
                      var mapBounds = this.map.getBounds();
                      return LJH.GoogleMaps.Util.resizeBounds(mapBounds, 2);
                    },
  createMarker:     function(positions){
                      var content, center, icon;
                      if(positions.length > 1){
                        content = this.compoundContent(positions);
                        
                        var bounds = new GLatLngBounds();
                        positions.each(function(position){ bounds.extend(position.getLatLng()); });
                        
                        center  = bounds.getCenter();
                        icon    = this.pluralIcon;
                      } else {
                        content = positions[0].content;
                        center  = positions[0].getLatLng();
                        icon    = this.icon;
                      }
                      var marker = new GMarker(center, icon);
                      marker.bindInfoWindowHtml(content);
                      
                      return marker;
                    },
  compoundContent:  function(positions){
                      var content;
                      if(positions.length >= 5 || this.map.getZoom() >= 16){
                        var bounds = new GLatLngBounds();
                        positions.each(function(position){ bounds.extend(position.getLatLng()); });
                        var toZoom = this.map.getBoundsZoomLevel(bounds);
                        var center = bounds.getCenter();
                        
                        content  = '<div style="width: 320px; height: 100px; overflow-y: auto; margin-top: 30px; font-size: 13px;">' +
                                     'There are <span class="bignum">'+positions.length+'</span> properties at this location. ' +
                                     '<img src="/images/icons/magnifier.png" /> ' +
                                     '<a href="javascript:void(LJH.Map.setCenter(new GLatLng('+center.lat()+', '+center.lng()+'), '+toZoom+'))\">Zoom in</a> to see more details.' +
                                   '</div>';
                      } else {
                        content  = '<div style="width: 320px; height: 200px; overflow-y: auto; margin-top: 10px;">';
                        positions.each(function(position){ content += position.content; });
                        content += '</div>';                        
                      }
                      
                      return content;
                    }
});

LJH.GoogleMaps2 = Class.create();
Object.extend(LJH.GoogleMaps2.prototype, {
  initialize:       function(map, markers, callback){
                      this.map       = map;
                      this.padding   = 2;
                      this.init      = false;
                      this.callback  = callback;
                      this.working   = new LJH.GoogleMaps.WorkingControl('/images/loading.gif', this.map);
                      
                      this.markers   = { };
                      this.markers[callback] = markers;
                      
                      this.refreshCallback = (arguments.length > 3) ? arguments[3] : null;
                      this.icon            = (arguments.length > 4) ? arguments[4] : null;
                      this.pluralIcon      = (arguments.length > 5) ? arguments[5] : null;
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      GEvent.addListener(map, "load", this.refresh.bind(this, true));
                      GEvent.addListener(map, "zoomend", this.refresh.bind(this));
                      GEvent.addListener(map, "moveend", this.move.bind(this));
                    },
  refresh:          function(){
                      if(arguments.length > 0 && arguments[0])
                        this.init = true;
                      
                      if(!this.init)
                        return;
                      
                      if(this.refreshCallback)
                        this.refreshCallback();
                      
                      // clear only the markers managed by this object
                      var gmap = this.map;
                      this.inBounds.each(function(marker){ gmap.removeOverlay(marker); });
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      var bounds     = this.map.getBounds();
                      var inclBounds = this.getMarkerBounds(); 
                      var zoom       = this.map.getZoom();
                      if(!this.markers[this.callback] || !this.markers[this.callback][zoom]){
                        this.loadMarkers(zoom);
                        return;
                      }
                      
                      var marker;
                      for(var i = 0; i < this.markers[this.callback][zoom].length; i++){
                        marker = this.markers[this.callback][zoom][i];
                        if(inclBounds.contains(marker.getPoint())){
                          this.inBounds.push(marker);
                        } else {
                          this.outBounds.push(marker);
                        }
                      }
                      
                      this.working.show();
                      setTimeout(this.addMarkers.bind(this), 250);
                    },
  loadMarkers:      function(zoom){
                      if(!this.callback)
                        return;
                      
                      this.working.show();
                      
                      new Ajax.Request(this.callback,
                                       { method:     'get',
                                         parameters: { 'zoom':       zoom,
                                                       'icon':       this.icon,
                                                       'pluralIcon': this.pluralIcon },
                                         onComplete: this.finishLoad.bind(this, zoom) });
                    },
  finishLoad:       function(zoom, request){
                      try {
                        eval("var callback = \""+this.callback.replace(/\"/g, "\\\"")+"\";\n"+request.responseText);
                        
                        this.working.hide();
                        if(this.map.getZoom() == zoom)
                          this.refresh();
                      } catch(e){ 
                        if(console){
                          console.log(e);
                          for(var i in e)
                            console.log('%s: %s', i, e[i]);
                        }
                        this.working.hide();
                      }
                    },
  addMarkers:       function(){
                      map = this.map;
                      this.inBounds.each(function(marker){ map.addOverlay(marker); });
                      this.working.hide();
                    },
  move:             function(){
                      var bounds    = this.getMarkerBounds();
                      var map       = this.map;
                      
                      var newOut = this.inBounds.select(function(marker){ return !bounds.contains(marker.getPoint()); });
                      newOut.each(function(marker){ map.removeOverlay(marker); });
                      this.inBounds = this.inBounds.without.apply(this.inBounds, newOut);
                      
                      var newIn  = this.outBounds.select(function(marker){ return bounds.contains(marker.getPoint()); });
                      newIn.each(function(marker){ map.addOverlay(marker); });
                      this.outBounds = this.outBounds.without.apply(this.outBounds, newIn);
                      
                      this.inBounds  = this.inBounds.concat(newIn);
                      this.outBounds = this.outBounds.concat(newOut);
                    },
  getMarkerBounds:  function(){
                      var mapBounds = this.map.getBounds();
                      return LJH.GoogleMaps.Util.resizeBounds(mapBounds, 2);
                    },
  createMarker:     function(positions){
                      var content, center, icon;
                      if(positions.length > 1){
                        content = this.compoundContent(positions);
                        
                        var bounds = new GLatLngBounds();
                        positions.each(function(position){ bounds.extend(position.getLatLng()); });
                        
                        center  = bounds.getCenter();
                        icon    = this.pluralIcon;
                      } else {
                        content = positions[0].content;
                        center  = positions[0].getLatLng();
                        icon    = this.icon;
                      }
                      var marker = new GMarker(center, icon);
                      marker.bindInfoWindowHtml(content);
                      
                      return marker;
                    }
});

LJH.GoogleMaps.Position = Class.create();
Object.extend(LJH.GoogleMaps.Position.prototype, {
  initialize:   function(lat, lng, content){
                  this.lat     = lat;
                  this.lng     = lng;
                  this.content = content;
                },
  getLatLng:    function(){
                  return new GLatLng(this.lat, this.lng);
                }
});

LJH.GoogleMaps.Util = {
  resizeBounds:   function(bounds, delta){
                    var span      = bounds.toSpan();
                    var ne        = bounds.getNorthEast();
                    var sw        = bounds.getSouthWest();
                    
                    var latAdj    = ((span.lat() * delta) - span.lat()) / 2;
                    var lngAdj    = ((span.lng() * delta) - span.lng()) / 2;
                    
                    var ne2       = new GLatLng(ne.lat() + latAdj, 
                                                ne.lng() + lngAdj);
                    var sw2       = new GLatLng(sw.lat() - latAdj, 
                                                sw.lng() - lngAdj);
                    return new GLatLngBounds(sw2, ne2);
                  }  
}


LJH.GoogleMaps.PinIcon = function(color){
  var image = "/images/mapicons/";
  switch(color){
    case "blue":
      image += "blue-pushpin";
      break;
    case "green":
      image += "green-pushpin";
      break;
    case "lightblue":
      image += "ltblue-pushpin";
      break;
    case "pink":
      image += "pink-pushpin";
      break;
    case "purple":
      image += "purple-pushpin";
      break;
    case "yellow":
      image += "yellow-pushpin";
      break;
  }
  
  if(!color)
    return G_DEFAULT_ICON;
  
  var icon              = new GIcon();
  icon.image            = image+".png";
  icon.printImage       = image+".gif";
  icon.mozPrintImage    = image+".gif";
  icon.shadow           = "/images/mapicons/pushpin_shadow.png";
  icon.iconSize         = new GSize(32, 32);
  icon.shadowSize       = new GSize(59, 32);
  icon.iconAnchor       = new GPoint(16, 32);
  icon.infoWindowAnchor = new GPoint(16, 16);
  
  return icon;
};

LJH.GoogleMaps.HouseIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/house.png";
  icon.printImage       = "/images/mapicons/house.gif";
  icon.mozPrintImage    = "/images/mapicons/house.gif";
  icon.shadow           = "/images/mapicons/house_shadow.png";
  icon.iconSize         = new GSize(32, 32);
  icon.shadowSize       = new GSize(49, 32);
  icon.iconAnchor       = new GPoint(16, 16);
  icon.infoWindowAnchor = new GPoint(16, 16);
  return icon;
};

LJH.GoogleMaps.HousesIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/houses.png";
  icon.printImage       = "/images/mapicons/houses.gif";
  icon.mozPrintImage    = "/images/mapicons/houses.gif";
  icon.shadow           = "/images/mapicons/houses_shadow.png";
  icon.iconSize         = new GSize(34, 32);
  icon.shadowSize       = new GSize(51, 32);
  icon.iconAnchor       = new GPoint(17, 16);
  icon.infoWindowAnchor = new GPoint(17, 16);
  return icon;
};

LJH.GoogleMaps.BlackOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/blackorb.png";
  icon.printImage       = "/images/mapicons/blackorb.gif";
  icon.mozPrintImage    = "/images/mapicons/blackorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.BlackPluralOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/blackpluralorb.png";
  icon.printImage       = "/images/mapicons/blackpluralorb.gif";
  icon.mozPrintImage    = "/images/mapicons/blackpluralorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.RedOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/redorb.png";
  icon.printImage       = "/images/mapicons/redorb.gif";
  icon.mozPrintImage    = "/images/mapicons/redorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.RedPluralOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/redpluralorb.png";
  icon.printImage       = "/images/mapicons/redpluralorb.gif";
  icon.mozPrintImage    = "/images/mapicons/redpluralorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.GreenOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/greenorb.png";
  icon.printImage       = "/images/mapicons/greenorb.gif";
  icon.mozPrintImage    = "/images/mapicons/greenorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.GreenPluralOrbIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/greenpluralorb.png";
  icon.printImage       = "/images/mapicons/greenpluralorb.gif";
  icon.mozPrintImage    = "/images/mapicons/greenpluralorb.gif";
  icon.shadow           = "/images/mapicons/orb-shadow.png";
  icon.iconSize         = new GSize(22, 35);
  icon.shadowSize       = new GSize(40, 35);
  icon.iconAnchor       = new GPoint(11, 35);
  icon.infoWindowAnchor = new GPoint(11, 35);
  return icon;
};

LJH.GoogleMaps.FlagIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/flag.png";
  icon.printImage       = "/images/mapicons/flag.gif";
  icon.mozPrintImage    = "/images/mapicons/flag.gif";
  icon.shadow           = "/images/mapicons/flag-shadow.png";
  icon.iconSize         = new GSize(23, 35);
  icon.shadowSize       = new GSize(41, 35);
  icon.iconAnchor       = new GPoint(2, 35);
  icon.infoWindowAnchor = new GPoint(2, 35);
  return icon;
};

LJH.GoogleMaps.FlagsIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/flags.png";
  icon.printImage       = "/images/mapicons/flags.gif";
  icon.mozPrintImage    = "/images/mapicons/flags.gif";
  icon.shadow           = "/images/mapicons/flags-shadow.png";
  icon.iconSize         = new GSize(23, 35);
  icon.shadowSize       = new GSize(41, 35);
  icon.iconAnchor       = new GPoint(2, 35);
  icon.infoWindowAnchor = new GPoint(2, 35);
  return icon;
};


LJH.GoogleMaps.LittleHouseIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/house16.png";
  icon.printImage       = "/images/mapicons/house16.gif";
  icon.mozPrintImage    = "/images/mapicons/house16.gif";
  icon.iconSize         = new GSize(16, 16);
  icon.iconAnchor       = new GPoint(8, 8);
  icon.infoWindowAnchor = new GPoint(8, 8);
  return icon;  
}

LJH.GoogleMaps.CrossIcon = function(){
  var icon              = new GIcon();
  icon.image            = "/images/mapicons/cross.png";
  icon.printImage       = "/images/mapicons/cross.gif";
  icon.mozPrintImage    = "/images/mapicons/cross.gif";
  icon.iconSize         = new GSize(24, 23);
  icon.iconAnchor       = new GPoint(12, 12);
  icon.infoWindowAnchor = new GPoint(12, 12);
  return icon;
};

LJH.GoogleMaps.CompassImage = function(pos){
  return "/images/mapicons/compass/"+pos.toLowerCase()+".png";
}

LJH.GoogleMaps.CompassIcon = function(pos){
  var icon                 = new GIcon();
  icon.image               = LJH.GoogleMaps.CompassImage(pos);
  icon.printImage          = LJH.GoogleMaps.CompassImage(pos);
  icon.mozPrintImage       = LJH.GoogleMaps.CompassImage(pos);
  icon.iconSize            = new GSize(40, 40);
  icon.iconAnchor          = new GPoint(20, 20);
  icon.infoWindowAnchor    = new GPoint(20, 20);
  return icon;
};

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.GoogleMaps.Geocoder = Class.create();
Object.extend(LJH.GoogleMaps.Geocoder.prototype, {
  initialize:     function(map, country, locality, resultsArea, searchBtn, loading){
                    this.map         = map;
                    this.locality    = $(locality);
                    this.country     = country;
                    this.resultsArea = $(resultsArea);
                    this.loading     = $(loading);
                    this.geocoder    = new GClientGeocoder();
                    
                    $(searchBtn).observe('click', this.doSearch.bind(this));
                    this.locality.observe('keypress', LJH.Control.checkEnter(this.doSearch.bind(this)));
                  },
  doSearch:       function(){
                    var locality = this.locality.value.trim();
                    
                    if(this.locality.getAttribute('default') == 1)
                      locality   = "";
                    
                    if(!locality || locality.length < 2){
                      alert("Please enter at least 2 characters!");
                      return false;
                    }

                    this.loading.show();
                    
                    this.geocoder.getLocations(locality, this.finishSearch.bind(this));
                  },
  finishSearch:   function(results){
                    this.loading.hide();
                    
                    if(results.Status.code == '200'){
                      var locations = new Array();
                      
                      for(var i = 0; i < results.Placemark.length; i++){
                        if(results.Placemark[i].AddressDetails.Country.CountryNameCode.toLowerCase() == this.country.toLowerCase())
                          locations.push(results.Placemark[i]);
                      }
                      
                      if(locations.length == 1 || (locations.length > 0 && locations[0].Accuracy == 8)){
                        var coords = locations[0].Point.coordinates;
                        this.map.setCenter(new GLatLng(coords[1], coords[0]), 14);
                      } else if(locations.length > 0){
                        this.listLocations(locations);
                      } else {
                        alert("No locations could be found to match your criteria!");
                        return false;
                      }
                    } else {
                      alert("An error occured with the service!");
                      return false;
                    }
                    return true;
                  },
  listLocations:  function(locations){
                    var list = this.resultsArea.getElementsByTagName('ul')[0];
                    list.innerHTML = '';
                    var a, li;
                    
                    for(var i = 0; i < locations.length; i++){
                      a           = $(document.createElement('a'));
                      li          = document.createElement('li');
                      a.href      = 'javascript:void(0)';
                      a.innerHTML = locations[i].address;
                      a.observe('click', this.changeLocation.bind(this, locations[i]));
                      li.appendChild(a);
                      list.appendChild(li);
                    }
                    this.resultsArea.show();
                  },
  changeLocation: function(location){
                    this.resultsArea.hide();
                    
                    //LJHMapPane.eraseSearchZone();
                    var coords = location.Point.coordinates;
                    this.map.setCenter(new GLatLng(coords[1], coords[0]), 14);
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.GoogleMaps.SearchArea = Class.create();
Object.extend(LJH.GoogleMaps.SearchArea.prototype, LJH.EventsBase);
Object.extend(LJH.GoogleMaps.SearchArea.prototype, {
  initialize:     function(map){
                    this.map         = map;
                    this.polygon     = null;
                    this.points      = new Array();
                    
                    this.events      = { 'redraw': [] };
                    
                    GEvent.addListener(this.map, 'click', this.addPoint.bind(this));
                    GEvent.addListener(this.map, 'singlerightclick', this.removePoint.bind(this));
                  },
  addPoint:       function(marker, point){
                    if(!this.points.any(function(marker){ var p = marker.getLatLng(); return (p.lat() == point.lat() && p.lon == point.lon()); })){
                      if(this.points.length <= 50){
                        var marker = new GMarker(point, { draggable: true });
                        
                        GEvent.addListener(marker, 'drag', this.redrawPolygon.bind(this));
                        
                        this.points.push(marker);
                        this.map.addOverlay(marker);
                        this.redrawPolygon();
                      } else {
                        alert("You have placed the maximum number of corner markers allowed!");
                      }
                    }
                  },
  removePoint:    function(point, src, marker){
                    if(marker){
                      var result      = this.points.partition(function(m){ return m != marker; });
                      this.points     = result[0];
                      var m           = result[1];
                      this.map.removeOverlay(m[0]);
                      this.redrawPolygon();
                    }
                  },
  clear:          function(){
                    var gmap = this.map;
                    this.points.each(function(p){ gmap.removeOverlay(p); });
                    if(this.polygon)
                      this.map.removeOverlay(this.polygon);
                    
                    this.points = new Array();
                  },
  serialize:      function(){
                    var result = "";

                    this.points.each(function(p){ 
                      var ll  = p.getLatLng();
                      result += ll.lat().toString()+","+ll.lng().toString()+";";
                    });

                    return result;
                  },
  redrawPolygon:  function(){
                    if(this.polygon)
                      this.map.removeOverlay(this.polygon);
                    
                    var vertices = this.points.collect(function(marker){ return marker.getLatLng(); });
                    if(vertices.length > 2){
                      vertices.push(vertices[0]);
                      this.polygon = new GPolygon(vertices, '#FF0000', 1, 1, '#FFCC00', 0.5, { clickable: false });
                      this.map.addOverlay(this.polygon);
                      
                      this.fireEvent('redraw');
                    }
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.GoogleMaps.Streetview = Class.create({
  initialize:     function(target, map, container){
                    this.target    = target;
                    this.container = $(container);
                    this.map       = map;
                    this.client    = new GStreetviewClient();
                    this.client.getNearestPanorama(target, this.setup.bind(this));
                  },
  setup:          function(data){
                    if(data.code != 200)
                      return;
                    
                    var latlng = data.location.latlng;
                    
                    this.latlng   = latlng;
                    this.yaw      = this.bearing(this.target, latlng);
                    
                    this.marker   = new GMarker(latlng, { icon: LJH.GoogleMaps.CompassIcon(this.direction(this.yaw)), draggable: true });
                    this.map.addOverlay(this.marker);
                    
                    GEvent.addListener(this.marker, 'dragend', this.resetView.bind(this));
                    
                    this.panorama = new GStreetviewPanorama(this.container);
                    
                    GEvent.addListener(this.panorama, "error", this.handleError.bind(this));
                    GEvent.addListener(this.panorama, "initialized", this.panoMove.bind(this));
                    GEvent.addListener(this.panorama, "yawchanged", this.panoYaw.bind(this));
                    GEvent.addListener(this.map, "click", this.positionMarker.bind(this));
                    
                    this.panorama.setLocationAndPOV(latlng, { yaw: this.yaw });
                  },
  bearing:        function(target, origin){
                    var x = Math.abs(origin.lng() - target.lng());
                    var y = Math.abs(origin.lat() - target.lat());
                    
                    var z = Math.atan(y / x);
                    
                    pos  = (target.lat() > origin.lat()) ? 'n' : 's';
                    pos += (target.lng() > origin.lng()) ? 'e' : 'w';
                    
                    switch(pos){
                      case "ne":
                        z  = (0.5 * Math.PI) - z;
                        break;
                      case "se":
                        z += 0.5 * Math.PI;
                        break;
                      case "sw":
                        z  = ((0.5 * Math.PI) - z) + Math.PI;
                        break;
                      case "nw":
                        z += 1.5 * Math.PI;
                        break
                    }
                    
                    var a = z * (180 / Math.PI);
                    
                    return a;
                  },
  direction:      function(yaw){
                    while(yaw > 360)
                      yaw -= 360;
                    quadrent = Math.ceil((Math.floor(yaw / 11.25) + 1) / 2);
                    return ['n', 'nne', 'ne', 'ene', 'e', 'ese', 'se', 'sse', 's', 'ssw', 'sw', 'wsw', 'w', 'wnw', 'nw', 'nnw', 'n'][quadrent];
                  },
  positionMarker: function(overlay, latlng){
                    if((!latlng || !latlng.lat()) && arguments.length > 2) // marker was clicked instead of map
                      latlng = arguments[2];
                    
                    if(latlng && latlng.lat()){
                      this.marker.setLatLng(latlng);
                      this.resetView();
                    }
                  },
  resetView:      function(){
                    this.client.getNearestPanorama(this.marker.getLatLng(), this.finishReset.bind(this));
                  },
  finishReset:    function(data){
                    if(data.code != 200){
                      alert('No data could be found for that location!');
                      this.marker.setLatLng(this.latlng);
                      return;
                    }
                    var latlng = data.location.latlng;
                    this.panorama.setLocationAndPOV(latlng, { yaw: this.yaw });
                    this.marker.setLatLng(latlng);
                    this.latlng = latlng;
                    this.marker.setImage(LJH.GoogleMaps.CompassImage(this.direction(this.yaw)));
                  },
  panoMove:       function(location){
                    this.marker.setLatLng(location.latlng);
                    this.map.setCenter(location.latlng);
                    this.latlng = location.latlng;
                    this.yaw = location.pov.yaw;
                    this.marker.setImage(LJH.GoogleMaps.CompassImage(this.direction(this.yaw)));
                  },
  panoYaw:        function(){
                    var pov = this.panorama.getPOV();
                    this.yaw = pov.yaw;
                    this.marker.setImage(LJH.GoogleMaps.CompassImage(this.direction(this.yaw)));
                  },
  handleError:    function(errorCode){
                    if(errorCode == 603){
                      alert("Your browser does not appear to support flash. The flash plugin is required to use Google Street View.");
                      return;
                    }
                  }
});
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

// Control written as function instead of class DELIBERATLY - google definitions may not exist at initial run time so mush be encapsulated within function

LJH.GoogleMaps.WorkingControl = Class.create();
Object.extend(LJH.GoogleMaps.WorkingControl.prototype, {
  initialize:   function(loadingImage, map){
                  this.loading     = document.createElement("img");
                  this.loading.src = loadingImage;
                  
                  this.container = $(document.createElement('div'));
                  this.container.appendChild(this.loading);
                  this.container.setOpacity(0.8);
                  
                  with(this.container){
                    style.border   = '1px #666666 solid';
                    style.position = 'absolute';
                    style.display  = 'none';
                    style.zIndex   = '99';
                  }
                  
                  this.map         = map;
                  
                  this.map.getContainer().appendChild(this.container);
                },
  reposition:   function(){
                  var dim          = $(this.map.getContainer()).getDimensions();
                  var left         = Math.round((dim.width - (this.loading.width + 2)) / 2);
                  var top          = Math.round((dim.height - (this.loading.height + 2)) / 2);
                  
                  this.container.style.left = left.toString()+'px';
                  this.container.style.top  = top.toString()+'px';
                },
  show:         function(){
                  this.reposition();
                  this.container.style.display = 'block';
                },
  hide:         function(){
                  this.container.style.display = 'none';    
                }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.NumbersOnly = Class.create();
Object.extend(LJH.NumbersOnly.prototype, {
  initialize:     function(field){
                    this.field = $(field);
                    this.field.observe('keypress', this.check.bindAsEventListener(this), true);
                  },
  check:          function(e){
                    var code;
                    var ret    = true;
                    var result = this.field.value;
                    
                    if(typeof e.charCode != 'undefined')
                      code = e.charCode;
                    else
                      code = e.keyCode;
                    
                    if(!code || e.ctrlKey || e.altKey || e.metaKey) // control character
                      return true;
                    
                    var newChar = String.fromCharCode(code);

                    if(!newChar.match(/[0-9.]/) || (newChar == '.' && result.indexOf(".") >= 0)){
                      Event.stop(e);
                      return false;
                    }
                    
                    return true;
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

// results: { i: result html }
LJH.ResultsStrip = Class.create();
Object.extend(LJH.ResultsStrip.prototype, {
  initialize:     function(container, results, startResult, totalResults, prevButton, nextButton, resultWidth, loading, classes){
                    this.container      = $(container);
                    this.classes        = classes;
                    this.loading        = loading;
                    this.resultWidth    = resultWidth;
                    this.startResult    = startResult;
                    this.totalResults   = totalResults;
                    this.leftResult     = startResult;
                    this.prevButton     = $(prevButton);
                    this.nextButton     = $(nextButton);
                    this.apetureSize    = 3;
                    this.cacheDelta     = 10;
                    this.clickCache     = new Array();
                    this.loadingSide    = null;
                    this.results        = results;
                    this.showQueue      = new Array();
                    
                    if(arguments.length > 9)
                      this.callback     = arguments[9];
                    else
                      this.callback     = '/callbacks/searchResults.php';
                    
                    if(arguments.length > 10)
                      this.params       = arguments[10];
                    
                    this.prevDisabled  = false;
                    this.nextDisabled  = false;
                    this.moving        = false;
                    
                    this.testbed      = $(document.createElement('div'));
                    with(this.testbed.style){
                      position        = 'absolute';
                      top             = '0';
                      left            = '0';
                      width           = '0';
                      height          = '0';
                      visibility      = 'hidden';
                    }
                    
                    document.body.appendChild(this.testbed);
                    
                    if(this.leftResult + this.apetureSize >= totalResults - 1)
                      this.disableNext();
                    if(this.leftResult == 0)
                      this.disablePrev();
                    
                    var tmpStart = (startResult + this.apetureSize > totalResults && totalResults >= this.apetureSize) ? totalResults - this.apetureSize : startResult;
                    
                    for(var i = 0; i < this.apetureSize; i++){
                      if((tmpStart + 1) + i <= totalResults)
                        this.writeResult(tmpStart + i, 'right');
                    }
                    
                    this.prevButton.observe('click', this.prevResult.bind(this));
                    this.nextButton.observe('click', this.nextResult.bind(this));
                    this.prevButton.observe('mousedown', this.buttonDown.bind(this, 'prev'));
                    this.nextButton.observe('mousedown', this.buttonDown.bind(this, 'next'));
                    this.prevButton.observe('mouseup', this.buttonUp.bind(this, 'prev'));
                    this.nextButton.observe('mouseup', this.buttonUp.bind(this, 'next'));
                  },
  writeResult:    function(pos, side){
                    var notVisible = (arguments.length > 2 && typeof arguments[2] == 'boolean') ? arguments[2] : false;
                    
                    var result = this.results[pos];
                    if(((side == 'right' && this.showQueue.length >= this.apetureSize) || side == 'left') && !notVisible)
                      return;
                      
                    var baseDiv = $(document.createElement('div'));
                    baseDiv.addClassName(this.classes.result);
                    with(baseDiv.style){
                      position  = 'absolute';
                      zIndex    = '1';
                      if(notVisible)
                        clip    = 'rect(0 0 0 0)';
                    }
                    
                    var div = $(document.createElement('div'));
                    div.addClassName(this.classes.result);
                    with(div.style){
                      position = 'relative';
                      top      = '-86px';
                      left     = '0px';
                      zIndex   = '1';
                    }
                    
                    baseDiv.innerHTML = "<div class=\""+this.classes.number+"\">"+(pos + 1)+"</div>";
                    div.innerHTML     = result;
                    
                    baseDiv.appendChild(div);
                    
                    var offset = Position.cumulativeOffset(this.container);
                    if(side == 'right'){
                      var x      = (offset[0] + 7) + (this.resultWidth * (this.showQueue.length));
                      this.showQueue.push({ 'pos': pos, 'result': baseDiv });
                    } else {
                      var x      = offset[0] - this.resultWidth;
                      this.showQueue.unshift({ 'pos': pos, 'result': baseDiv });
                    }
                    with(baseDiv.style){
                      left     = x+'px';
                      top      = offset[1]+'px';
                    }
                    this.container.appendChild(baseDiv);
                  },
  writeLoading:   function(side){
                    this.loadingSide = side;

                    var div    = $(document.createElement('div'));
                    div.addClassName(this.classes.result);
                    with(div.style){
                      position = 'absolute';
                      zIndex   = '1';
                      clip     = 'rect(0 0 0 0)';
                    }
                    var offset = Position.cumulativeOffset(this.container);
                    if(side == 'right'){
                      var x      = offset[0] + (this.resultWidth * (this.showQueue.length));
                      this.showQueue.push({ 'pos': 'loading', 'result': div });
                    } else {
                      var x      = offset[0] - this.resultWidth;
                      this.showQueue.unshift({ 'pos': 'loading', 'result': div });
                    }
                    with(div.style){
                      left     = x+'px';
                      top      = offset[1]+'px';
                    }
                    div.innerHTML = this.loading;
                    this.container.appendChild(div);
                  },
  replaceLoading: function(result, pos, side){
                    if(side == 'right'){
                      this.flushLoading();
                      this.writeResult(pos, side);
                      return;
                    }
                    
                    var loading, index;
                    for(var i = 0; i < this.showQueue.length; i++){
                      if(this.showQueue[i].pos == 'loading'){
                        loading = this.showQueue[i].result;
                        index   = i;
                        break;
                      }
                    }
                    this.loadingSide = null;
                    if(!loading) return false;
                    
                    loading.innerHTML = "<div class=\""+this.classes.number+"\">"+(pos + 1)+"</div>";
                    
                    var div = $(document.createElement('div'));
                    div.addClassName(this.classes.result);
                    with(div.style){
                      position = 'relative';
                      top      = '-86px';
                      left     = '0px';
                      zIndex   = '1';
                    }
                    
                    div.innerHTML     = result;
                    loading.appendChild(div);
                    this.showQueue[index].pos = parseInt(pos);
                  },
  flushLoading:   function(direction){
                    for(var i = 0; i < this.showQueue.length; i++){
                      if(this.showQueue[i].pos == 'loading'){
                        this.container.removeChild(this.showQueue[i].result);
                        this.showQueue.splice(i, 1);
                      }
                    }
                    this.loadingSide = null;
                  },
  cacheClick:     function(type){
                    this.clickCache.unshift(type);
                  },
  prevResult:     function(){
                    if(this.prevDisabled || this.loadingSide == 'left')
                      return false;
                      
                    if(this.moving){
                      this.cacheClick('prev');
                      return;
                    }
                      
                    var first = this.showQueue[0];
                    var prev  = first.pos - 1;
                    
                    if(isNaN(prev) || prev < 0)
                      return false;
                      
                    if(!this.results[prev]){ // Not cached
                      var first = (prev - this.cacheDelta < 0) ? 0 : prev - this.cacheDelta
                      this.getResults(first, prev, 'left');
                      this.writeLoading('left');
                    } else {
                      this.writeResult(prev, 'left', true);
                    }
                    this.slideRight();
                  },
  nextResult:     function(){
                    if(this.nextDisabled || this.loadingSide == 'right')
                      return false;
                      
                    if(this.moving){
                      this.cacheClick('next');
                      return;
                    }
                      
                    var last = this.showQueue.last();
                    var next = last.pos + 1;
                    
                    if(!this.results[next]){ // Not cached
                      var last = next + this.cacheDelta;
                      this.getResults(next, last, 'right');
                      this.writeLoading('right');
                    } else {
                      this.writeResult(next, 'right', true);
                    }
                    this.slideLeft();
                  },
  getResults:     function(first, last, side){
                    new Ajax.Request(this.callback,
                                     { method:     'get',
                                       parameters: Object.extend({ 'first': first, 'last':  last }, (this.params) ? this.params : { }),
                                       onComplete: this.cacheResults.bind(this, side) });
                  },
  cacheResults:   function(side, request){
                    var newResults;
                    try {
                      newResults = eval("(" + request.responseText + ")");
                    } catch(e){
                      if(console) console.log(e);
                      newResults = null;
                    }
                    
                    if(newResults){
                      var last  = 0;
                      var first = null;
                      for(var i in newResults){
                        if(i > last) last = i;
                        if(first == null || i < first) first = i;
                        this.results[i] = newResults[i];
                      }
                      
                      var pos = parseInt((side == 'left') ? last : first);

                      this.replaceLoading(newResults[pos], pos, side);
                    } else {
                      alert("An error has occured while loading the search results!");
                      this.flushLoading(direction);
                    }
                  },
  slideLeft:      function(){
                    this.moving = true;
                    new Effect.ResultsMove(this, this.moveLeft.bind(this), { afterFinish: this.finishSlide.bind(this, 'left') });
                  },
  slideRight:     function(){
                    this.moving = true;
                    new Effect.ResultsMove(this, this.moveRight.bind(this), { afterFinish: this.finishSlide.bind(this, 'right') });
                  },
  moveLeft:       function(delta){
                    var left         = this.showQueue[0].result;
                    var right        = this.showQueue.last().result;
                    var leftStart    = Position.cumulativeOffset(this.container)[0];
                    var width        = this.resultWidth;
                    
                    left.style.clip  = "rect(auto auto auto "+delta+"px)";
                    this.showQueue.each(function(x, i){
                      var leftpos = leftStart + (width * i);
                      x.result.style.left = parseInt(leftpos - delta)+'px';
                    });
                    right.style.clip = "rect(auto "+delta+"px auto auto)";
                  },
  moveRight:      function(delta){
                    var left         = this.showQueue[0].result;
                    var right        = this.showQueue.last().result;
                    var leftStart    = Position.cumulativeOffset(this.container)[0];
                    var width        = this.resultWidth;
                    var invDelta     = this.resultWidth - delta;

                    right.style.clip = "rect(auto "+invDelta+"px auto auto)";
                    this.showQueue.each(function(x, i){
                      var leftpos = leftStart + (width * (i - 1));
                      x.result.style.left = parseInt(leftpos + delta)+'px';
                    });
                    left.style.clip  = "rect(auto auto auto "+invDelta+"px)";
                  },
  finishSlide:    function(direction){
                    var item;
                    if(direction == 'right')
                      item = this.showQueue.pop();
                    else
                      item = this.showQueue.shift();
                    
                    this.updateButtons();
                    
                    this.container.removeChild(item.result);
                    this.moving = false;
                    
                    if(this.clickCache.length > 0){
                      var click = this.clickCache.shift();
                      if(click == 'prev') 
                        this.prevResult();
                      else
                        this.nextResult();
                    }
                  },
  updateButtons:  function(){
                    if(this.showQueue.last().pos != 'loading' && this.showQueue.last().pos >= this.totalResults - 1)
                      this.disableNext();
                    else if(this.nextDisabled)
                      this.enableNext();
                    
                    if(this.showQueue[0].pos != 'loading' && this.showQueue[0].pos <= 0)
                      this.disablePrev();
                    else if(this.prevDisabled)
                      this.enablePrev();
                  },
  disablePrev:    function(){
                    this.prevDisabled = true;
                    if(this.prevButton.hasClassName(this.classes.up))
                      this.prevButton.removeClassName(this.classes.up);
                    this.prevButton.addClassName(this.classes.off);
                  },
  disableNext:    function(){
                    this.nextDisabled = true;
                    if(this.nextButton.hasClassName(this.classes.up))
                      this.nextButton.removeClassName(this.classes.up);
                    this.nextButton.addClassName(this.classes.off);
                  },
  enablePrev:     function(){
                    this.prevDisabled = false;
                    if(this.prevButton.hasClassName(this.classes.off))
                      this.prevButton.removeClassName(this.classes.off);
                    this.prevButton.addClassName(this.classes.up);
                  },
  enableNext:     function(){
                    this.nextDisabled = false;
                    if(this.nextButton.hasClassName(this.classes.off))
                      this.nextButton.removeClassName(this.classes.off);
                    this.nextButton.addClassName(this.classes.up);
                  },
  buttonDown:     function(mode){
                    if(mode == 'prev'){
                      if(this.prevDisabled) return false;
                      this.prevButton.removeClassName(this.classes.up);
                      this.prevButton.addClassName(this.classes.down);
                    } else {
                      if(this.nextDisabled) return false;
                      this.nextButton.removeClassName(this.classes.up);
                      this.nextButton.addClassName(this.classes.down);
                    }
                  },
  buttonUp:       function(mode){
                    if(mode == 'prev'){
                      if(this.prevDisabled) return false;
                      this.prevButton.removeClassName(this.classes.down);
                      this.prevButton.addClassName(this.classes.up);
                    } else {
                      if(this.nextDisabled) return false;
                      this.nextButton.removeClassName(this.classes.down);
                      this.nextButton.addClassName(this.classes.up);
                    }
                  }
});

Effect.ResultsMove = Class.create();
Object.extend(Object.extend(Effect.ResultsMove.prototype, Effect.Base.prototype), {
  initialize: function(resultsObj, callback){
    this.resultsObj   = resultsObj;
    this.callback     = callback;
    var options       = Object.extend({duration: 0.35}, arguments[2] || {});
    this.start(options);
  },
  update: function(position){
    var delta = Math.ceil(position * (this.resultsObj.resultWidth));
    this.callback(delta);
  }
});


//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.HolidayBooking = Class.create();
Object.extend(LJH.HolidayBooking.prototype, {
  initialize:     function(propertyCode, officeCode){
                    this.visible      = false;
                    this.propertyCode = propertyCode;
                    this.officeCode   = officeCode;
                    this.width        = 800;
                    this.height       = 500;
                    
                    this.overlay                       = document.createElement('div');
                    with(this.overlay.style){
                      display         = 'none';
                      backgroundColor = '#000000';
                      position        = 'absolute';
                      left            = '0px';
                      top             = '0px';
                      zIndex          = '5';
                    }
                    document.body.appendChild(this.overlay);
                    
                    this.popup                          = document.createElement('div');
                    with(this.popup.style){
                      display            = 'none';
                      backgroundColor    = '#ffffff';
                      position           = 'absolute';
                      left               = '0px';
                      top                = '0px';
                      zIndex             = '6';
                      width              = this.width.toString()+'px';
                      height             = this.height.toString()+'px';
                      padding            = '10px';
                      backgroundImage    = 'url(/images/bigloading.gif)';
                      backgroundPosition = 'center center';
                      backgroundRepeat   = 'no-repeat';
                    }
                    document.body.appendChild(this.popup);
                    
                    this.close                       = $(document.createElement('div'));
                    with(this.close.style){
                      width                          = "60px";
                      height                         = "19px";
                      backgroundImage                = "url(/images/buttons/close_text.gif)";
                    }
                    this.close.className             = "button_up";
                    this.close.observe('click', this.hide.bind(this));
                    
                    new LJH.ButtonControl(this.close, { up: 'button_up', down: 'button_down' });
                    
                    Event.observe(window, 'scroll', this.reposition.bind(this));
                    Event.observe(window, 'resize', this.reposition.bind(this));
                  },
  show:           function(){
                    if(this.visible)
                      return;
                    
                    this.popup.innerHTML = "";
                    this.getForm();
                    
                    this.visible        = true;
                    this.reposition();
                    
                    Element.setOpacity(this.overlay, 0);
                    this.overlay.style.display      = 'block';
                  
                    new Effect.Opacity(this.overlay, { from: 0, to: 0.9, afterFinish: this.finishPopup.bind(this), duration: 0.7 });
                  },
  hide:           function(){
                    this.popup.style.display       = 'none';
                    this.overlay.style.display     = 'none';
                    this.visible                   = false;
                  },
  getForm:        function(){
                    new Ajax.Updater(this.popup, 
                                     '/callbacks/holidayBooking.php',
                                     { method:      'get',
                                       parameters:  { propertyCode: this.propertyCode,
                                                      officeCode:   this.officeCode },
                                       evalScripts: true,
                                       onComplete:  this.appendClose.bind(this) });
                  },
  appendClose:    function(){
                    this.popup.style.backgroundPosition = "-100px -100px";
                    
                    this.popup.innerHTML += "<br /><br />";
                    var div = document.createElement('div');
                    div.align = 'center';
                    div.appendChild(this.close);
                    this.popup.appendChild(div);
                  },
  finishPopup:    function(){
                    this.popup.style.display = 'block';
                  },
  reposition:     function(){
                    if(!this.visible) return;
                    
                    x = Env.windowWidth();
                    y = Env.windowHeight();
                    
                    var dim = Element.getDimensions(document.body);
                    
                    this.overlay.style.width  = dim.width + 'px';
                    this.overlay.style.height = dim.height + 'px';
                  
                    x = (x >= this.width) ? Math.floor((x - this.width) / 2) : 0;
                    y = (y >= this.height) ? Math.floor((y - this.height) / (2 / 0.6)) : 0;
                  
                    x += Env.scrollLeft();
                    y += Env.scrollTop();
                          
                    this.overlay.style.top  = '0';
                    this.overlay.style.left = '0';
                    
                    this.popup.style.top  = y + 'px';
                    this.popup.style.left = x + 'px';
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.HolidayCalendar = Class.create();
Object.extend(LJH.HolidayCalendar.prototype, {
  initialize:     function(propertyCode, officeCode, container, month, year, backYear, backMonth, nextYear, nextMonth){
                    this.container    = $(container);
                    this.propertyCode = propertyCode;
                    this.officeCode   = officeCode;
                    this.month        = month;
                    this.year         = year;
                    
                    this.buttons      = { year:  { next: nextYear,
                                                   back: backYear },
                                          month: { next: nextMonth,
                                                   back: backMonth } };
                    
                    with(this.container.style){
                      backgroundPosition = "-100px -100px";
                      backgroundRepeat   = "no-repeat";
                      backgroundImage    = "url(/images/bigloading.gif)";
                    }
                    this.setup();
                  },
  setup:          function(){
                    $(this.buttons.year.next).observe('click', this.nextYear.bind(this));
                    $(this.buttons.year.back).observe('click', this.prevYear.bind(this));
                    $(this.buttons.month.next).observe('click', this.nextMonth.bind(this));
                    $(this.buttons.month.back).observe('click', this.prevMonth.bind(this));
                    
                    var links = this.container.getElementsByClassName('booking_link');
                    var date;
                    for(var i = 0; i < links.length; i++){
                      date = links[i].getAttribute('date');
                      $(links[i]).observe('click', this.showForm.bind(this, date));
                    }
                  },
  nextMonth:      function(){
                    if(this.month < 12){
                      this.month++;
                    } else {
                      this.year++;
                      this.month = 1;
                    }
                    this.refresh();
                  },
  nextYear:       function(){
                    this.year++;
                    this.refresh();
                  },
  prevMonth:      function(){
                    if(this.month > 1){
                      this.month--;
                    } else {
                      this.year--;
                      this.month = 12;
                    }
                    this.refresh();
                  },
  prevYear:       function(){
                    if(this.year > 1970)
                      this.year--;
                    this.refresh();
                  },
  showForm:       function(date){
                    this.container.innerHTML = "<img src=\"/images/spacer.gif\" width=\"420px\" height=\"224px\" />";
                    this.container.style.backgroundPosition = "center center";
                    new Ajax.Updater(this.container,
                                     "/callbacks/holidayBookingForm.php",
                                     { method:      'get',
                                       parameters:  { propertyCode: this.propertyCode,
                                                      officeCode:   this.officeCode,
                                                      date:         date },
                                       evalScripts: true,
                                       onComplete:  this.formDisplay.bind(this) });
                  },
  formDisplay:    function(){
                    this.container.style.backgroundPosition = "-100px -100px";
                    $('holidaybooking_backbtn').observe('click', this.refresh.bind(this));
                  },
  refresh:        function(){
                    this.container.innerHTML = "<img src=\"/images/spacer.gif\" width=\"420px\" height=\"224px\" />";
                    this.container.style.backgroundPosition = "center center";
                    new Ajax.Updater(this.container,
                                     "/callbacks/holidayCalendar.php",
                                     { method:      'get',
                                       parameters:  { propertyCode: this.propertyCode,
                                                      officeCode:   this.officeCode,
                                                      month:        this.month,
                                                      year:         this.year },
                                       evalScripts: true,
                                       onComplete:  this.finished.bind(this) });
                  },
  finished:       function(){
                    this.container.style.backgroundPosition = "-100px -100px";
                    this.setup();
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.HolidayBookingForm = Class.create();
Object.extend(LJH.HolidayBookingForm.prototype, {
  initialize:     function(propertyCode, officeCode, date, fields, submit, container){
                    this.propertyCode = propertyCode;
                    this.officeCode   = officeCode;
                    this.date         = date;
                    this.fields       = fields;
                    this.container    = $(container);
                    
                    $(submit).observe('click', this.checkForm.bind(this));
                  },
  checkForm:      function(){
                    var error  = null;
                    var fields = this.fields;
                    if($(fields.bookingFrom).value.replace(/-/g, "") > $(fields.bookingTo).value.replace(/-/g, ""))
                      error = "The end date you have selected is prior to the starting date!";
                    else if($(fields.name).value.match(LJH.re.blank))
                      error = "Please enter your name!";
                    else if($(fields.email).value.match(LJH.re.blank))
                      error = "Please enter your email address!";
                    else if(!$(fields.email).value.match(LJH.re.mail))
                      error = "That does not appear to be a valid email address! Please enter a valid address.";
                    else if($(fields.address).value.match(LJH.re.blank))
                      error = "Please enter your address!";
                    else if($(fields.suburb).value.match(LJH.re.blank))
                      error = "Please enter your suburb!";
                    else if($(fields.state).value.match(LJH.re.blank))
                      error = "Please enter your state!";
                    else if($(fields.postcode).value.match(LJH.re.blank))
                      error = "Please enter your postcode!";
                    else if($(fields.country).value.match(LJH.re.blank))
                      error = "Please enter your country!";
                    else if($(fields.phone).value.match(LJH.re.blank))
                      error = "Please enter your contact phone number!";
                    else if($(fields.adults).options[$(fields.adults).selectedIndex].value + 
                            $(fields.children).options[$(fields.children).selectedIndex].value == 0)
                      error = "Please select the number of people staying!";
                      
                    if(error){
                      alert(error);
                      return;
                    }
                    
                    this.postForm();
                  },
  postForm:       function(){
                    new Ajax.Updater(this.container,
                                     "/callbacks/holidayBookingFormSend.php",
                                     { method:     'post',
                                       parameters: { propertyCode: this.propertyCode,
                                                     officeCode:   this.officeCode,
                                                     date:         this.date,
                                                     bookingFrom:  $(this.fields.bookingFrom).value,
                                                     bookingTo:    $(this.fields.bookingTo).value,
                                                     name:         $(this.fields.name).value,
                                                     email:        $(this.fields.email).value,
                                                     address:      $(this.fields.address).value,
                                                     suburb:       $(this.fields.suburb).value,
                                                     state:        $(this.fields.state).value,
                                                     postcode:     $(this.fields.postcode).value,
                                                     country:      $(this.fields.country).value,
                                                     phone:        $(this.fields.phone).value,
                                                     adults:       $(this.fields.adults).options[$(this.fields.adults).selectedIndex].value,
                                                     children:     $(this.fields.children).options[$(this.fields.children).selectedIndex].value,
                                                     beds:         $(this.fields.beds).options[$(this.fields.beds).selectedIndex].value },
                                       evalScripts:  true });
                  }
});

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------

LJH.Lightbox = Class.create();
Object.extend(LJH.Lightbox.prototype, LJH.EventsBase);
Object.extend(LJH.Lightbox.prototype, {
  initialize:     function(){
                    this.visible      = false;
                    this.testBed      = $(document.createElement('div'));
                    this.options      = { overlayColor: '#000000' };
                    
                    this.events       = { close: [] };
                    
                    if(arguments.length > 0 && typeof arguments[0] == 'object')
                      Object.extend(this.options, arguments[0]);
                      
                    with(this.testBed.style){
                      position        = 'absolute';
                      left            = '-1000px';
                      top             = '-1000px';
                      visibility      = 'hidden';
                    }
                    document.body.appendChild(this.testBed);
                    
                    this.overlay                       = document.createElement('div');
                    with(this.overlay.style){
                      display         = 'none';
                      backgroundColor = this.options.overlayColor;
                      position        = 'absolute';
                      left            = '0px';
                      top             = '0px';
                      zIndex          = '5';
                    }
                    document.body.appendChild(this.overlay);
                    
                    this.popup                          = document.createElement('div');
                    with(this.popup.style){
                      display            = 'none';
                      backgroundColor    = '#ffffff';
                      position           = 'absolute';
                      left               = '0px';
                      top                = '0px';
                      zIndex             = '6';
                      padding            = '10px';
                    }
                    document.body.appendChild(this.popup);
                    
                    this.close                       = $(document.createElement('div'));
                    with(this.close.style){
                      width                          = "60px";
                      height                         = "19px";
                      backgroundImage                = "url(/images/buttons/close_text.gif)";
                    }
                    this.close.className             = "button_up";
                    this.close.observe('click', this.hide.bind(this));
                    
                    new LJH.ButtonControl(this.close, { up: 'button_up', down: 'button_down' });
                    
                    Event.observe(window, 'scroll', this.reposition.bind(this));
                    Event.observe(window, 'resize', this.reposition.bind(this));
                  },
  getDimensions:  function(html){
                    var div                = $(document.createElement('div'));
                    div.innerHTML          = html;
                    this.testBed.appendChild(div);
                    
                    var dimensions         = div.getDimensions();
                    
                    this.testBed.innerHTML = "";
                    return dimensions;
                  },
  show:           function(html){
                    if(this.visible)
                      return;
                    
                    var dimensions       = this.getDimensions(html);
                    this.width           = dimensions.width;
                    this.height          = dimensions.height;
                    with(this.popup.style){
                      width              = dimensions.width.toString()+'px';
                    }

                    this.popup.innerHTML = html;
                    
                    this.appendClose();
                    
                    this.visible         = true;
                    this.reposition();
                    
                    Element.setOpacity(this.overlay, 0);
                    this.overlay.style.display      = 'block';
                  
                    new Effect.Opacity(this.overlay, { from: 0, to: 0.9, afterFinish: this.finishPopup.bind(this), duration: 0.7 });
                  },
  hide:           function(){
                    this.popup.style.display       = 'none';
                    this.overlay.style.display     = 'none';
                    this.visible                   = false;
                    
                    this.fireEvent('close');
                  },
  appendClose:    function(){
                    this.popup.innerHTML += "<br /><br />";
                    var div = document.createElement('div');
                    div.align = 'center';
                    div.appendChild(this.close);
                    this.popup.appendChild(div);
                  },
  finishPopup:    function(){
                    this.popup.style.display = 'block';
                  },
  reposition:     function(){
                    if(!this.visible) return;
                    
                    x = Env.windowWidth();
                    y = Env.windowHeight();
                    
                    var dim = Element.getDimensions(document.body);
                    
                    this.overlay.style.width  = dim.width + 'px';
                    this.overlay.style.height = dim.height + 'px';
                  
                    x = (x >= this.width) ? Math.floor((x - this.width) / 2) : 0;
                    y = (y >= this.height) ? Math.floor((y - this.height) / (2 / 0.6)) : 0;
                  
                    x += Env.scrollLeft();
                    y += Env.scrollTop();
                          
                    this.overlay.style.top  = '0';
                    this.overlay.style.left = '0';
                    
                    this.popup.style.top  = y + 'px';
                    this.popup.style.left = x + 'px';
                  }
});


/**** VERSION 2 ****/

/**************************************************************************************************
*
* Search Type Select
* ------------------
* Used by home page for selection of search type for search form
*
**************************************************************************************************/

LJH.SearchTypeSelect = Class.create(LJH.Observable, {
  initialize:     function(container){
                    container      = $(container);
                    this.container = container;
                    this.selected  = container.select('div.selected')[0];
                    this.types     = container.select('a');
                    
                    var t = this;

                    this.types.each(function(type){ type.observe('click', t.select.bind(t, type)); });
                  },
  select:         function(elem, event){
                    Event.stop(event);
                    this.selected.innerHTML = elem.innerHTML;
                    this.types.each(function(type){ if(!type.visible()) type.show(); });
                    
                    elem.hide();
                    this.fire('change', elem.className);
                  },
  current:        function(){ 
                    return this.container.select('a').find(function(a){ return !a.visible(); }).className;
                  }
});


LJH.Loading = Class.create({
  initialize:     function(reference, className){
                    this.div  = new Element('div', { 'class': className });
                    
                    reference = $(reference);
                    var dim   = reference.getDimensions();
                    var pos   = reference.cumulativeOffset();
                    
                    this.div.setStyle({ position: 'absolute', 
                                        width:    dim.width.toString()+'px', 
                                        height:   dim.height.toString()+'px', 
                                        top:      pos.top.toString()+'px', 
                                        left:     pos.left.toString()+'px' });
                    this.div.setOpacity(0.8);

                    document.body.appendChild(this.div);
                  },
  close:          function(){
                    document.body.removeChild(this.div);
                  }
});

/**************************************************************************************************
*
* Mortgage Calculator
* -------------------
* Used for Mortgage calculator fragment
*
**************************************************************************************************/

LJH.MortgageCalculator = Class.create({
  initialize:     function(container){
                    container      = $(container);
                    
                    if(arguments.length > 1)
                      this.currency = arguments[1];
                    else
                      this.currency = '$';
                    
                    this.principal = container.select('input.principal')[0];
                    this.interest  = container.select('input.interest')[0];
                    this.term      = container.select('input.term')[0];
                    this.schedule  = container.select('select.schedule')[0];
                    
                    new LJH.NumbersOnly(this.principal);
                    new LJH.NumbersOnly(this.interest);
                    new LJH.NumbersOnly(this.term);
                    
                    this.paymentAmt     = container.select('div.payment_amount')[0];
                    

                    container.select('input.calculate')[0].observe('click', this.calculate.bind(this));
                  },
  calculate:      function(){
                    var p = parseFloat($F(this.principal));
                    var i = parseFloat($F(this.interest));
                    var t = parseFloat($F(this.term));
                    var s = $F(this.schedule);
                    
                    if(isNaN(p) || isNaN(i) || isNaN(t))
                      return false;

                    var r, f;
                    
                    if(s == 'yearly'){
                      r   = t;
                      f   = 100;
                    } else if(s == 'weekly'){
                      r   = t * 52;
                      f   = 5200;
                    } else if(s == 'fortnightly'){
                      r   = t * 26;
                      f   = 2600;
                    } else if(s == 'monthly'){
                      r   = t * 12;
                      f   = 1200;
                    } else { 
                      return false;
                    }
                    
                    var payment = (p * i * Math.pow(1 + i / f, r)) / (f * (Math.pow(1 + i / f, r) - 1));
                    
                    if(this.currency == 'Rs.'){
                      var amt       = payment.toFixed();
                      var formatted = amt.substr(-3, 3);
                      amt           = amt.slice(0, -3);
                      if(amt)
                        formatted   = ','+formatted;
                      
                      while(amt){
                        formatted   = amt.substr(-2, 2)+formatted;
                        amt         = amt.slice(0, -2);
                        if(amt)
                          formatted = ','+formatted;
                      }
                      
                      this.paymentAmt.innerHTML     = formatted;
                    } else {
                      this.paymentAmt.innerHTML     = this.currency+LJH.NumberFormat(payment, 2, ",");
                    }
                  }
});

/**************************************************************************************************
*
* Region Map
* ----------
* Used by major search forms to display maps for selection of regions/suburbs
*
**************************************************************************************************/

LJH.RegionMap = new LJH.Observable();

Object.extend(LJH.RegionMap, {
  init:       function(container){
                this.container = $(container);
                this.multiple  = (arguments.length > 1) ? arguments[1] : true;
                this.maps      = [ ];
              },
  select:     function(params){
                var elem = this.maps.find(function(elem){ return Object.equals(elem.params, params); });
                if(elem){
                  this.container.innerHTML = elem.html;
                  elem.script.map(function(script) { return eval(script) });
                } else {
                  this.showMap(params);
                }
              },
  loadMap:    function(params, request){
                this.loading.close(); 
                this.maps.push({ params: params, 
                                 html:   request.responseText.stripScripts(),
                                 script: request.responseText.extractScripts() });
              },
  showMap:    function(params){
                this.fire('show');
                
                this.loading = new LJH.Loading(this.container, 'searchform_loading');
                
                var parms2      = Object.clone(params);
                parms2.multiple = (this.multiple) ? 1 : 0;
                
                if(this.container){
                  new Ajax.Updater(this.container,
                                   '/callbacks/regionMap.php',
                                   { parameters:  parms2,
                                     evalScripts: true,
                                     onComplete:  this.loadMap.bind(this, params) });
                }
              }
});

/**************************************************************************************************
*
* Shortlist
* ---------
* Used to store property list in a cookie
*
**************************************************************************************************/

LJH.Shortlist = new LJH.Observable();
Object.extend(LJH.Shortlist, {
  get:        function(){
                var shortlist = unescape(LJH.Cookie.get('LJHShortlist'));
                return (shortlist) ? shortlist.split('|') : new Array();
              },
  set:        function(list){
                LJH.Cookie.set('LJHShortlist', list.join('|'));
              },
  add:        function(propertyID){
                var shortlist = this.get();
                if(shortlist.indexOf(propertyID) < 0)
                  shortlist.push(propertyID);
                this.set(shortlist);
                
                this.fire('add', propertyID);
              },
  remove:     function(propertyID){
                var shortlist = this.get(), index;
                if((index = shortlist.indexOf(propertyID)) >= 0)
                  shortlist.splice(index, 1);
                this.set(shortlist);
                
                this.fire('remove', propertyID);
              },
  toggle:     function(propertyID){
                if(!!this.retrieve(propertyID))
                  this.remove(propertyID);
                else
                  this.add(propertyID);
              },
  retrieve:   function(propertyID){
                var shortlist = this.get();
                var index;
                if((index = shortlist.indexOf(propertyID)) >= 0)
                  return shortlist[index];
                return false;
              }
});

/**************************************************************************************************
*
* Slideshow
* ---------
* Used to control image rotation on the home pages
*
**************************************************************************************************/

LJH.Slideshow = Class.create(LJH.Observable, new LJH.Destructable, {
  defaults:       { duration:   2, // duration of interchange (in seconds)
                    interval:   5, // interval between changes (in seconds)
                    minOpacity: 0, // the opacity level at which the images are changed
                    playIcon:   '/images/icons/play.png',
                    pauseIcon:  '/images/icons/pause.png',
                    className:  'featureimage',
                    iconWidth:  60,
                    iconHeight: 60 }, 
  initialize:     function(container, elements){
                    this.options   = (arguments.length > 2) ? Object.extend(Object.clone(this.defaults), arguments[2]) : this.defaults;
                    this.container = $(container);
                    this.elements  = [];

                    this.initElements(elements);
                    this.current   = 0;
                    this.container.removeChild(this.container.down('img'));
                    
                    this.elements[0].tag.style.display = '';

                    // deferred startup for flash files (takes them a while after becoming visible for api to become available
                    if(this.elements[0].elem.type == 'flash'){
                      var flashelem = this.getFlashElem(this.elements[0].tag);
                      var delay     = this.elements[0].elem.delay;
                      var config    = function(){ 
                                        if(typeof flashelem.Play != 'undefined'){
                                          flashelem.Play();
                                          setTimeout(function(){ flashelem.StopPlay(); }, delay);
                                        } else {
                                          setTimeout(config, 100);
                                        }
                                      };
                      setTimeout(config, 100);
                    }

                    
                    this.timer   = null;
                    this.running = true;
                    
                    this.pause   = this.createControl(this.options.pauseIcon);
                    this.play    = this.createControl(this.options.playIcon);
                    
                    this.createObserver(this.container, 'mouseover', this.displayControl.bind(this));
                    this.createObserver(this.container, 'mouseout', this.hideControl.bind(this));
                    this.createObserver(this.container, 'click', this.selectSlide.bind(this));
                    
                    this.createObserver(this.pause, 'click', this.runControl.bind(this, 'pause'));
                    this.createObserver(this.play, 'click', this.runControl.bind(this, 'play'));
                    
                    if(this.elements.length > 1)
                      this.startTimer();
                  },
  initElements:   function(elements){
                    var obj = this;
                    elements.each(function(elem, i){
                      var init = new Element('div');
                      var tag;
                      if(elem.type == 'image'){
                        tag    = "<img src=\""+elem.src+"\" class=\""+obj.options.className+"\" style=\"display:none\" />";
                      } else if(elem.type == 'flash'){
                        var params = "<param name=\"quality\" value=\"high\" /> "+
                                     "<param name=\"bgcolor\" value=\"#FFFFFF\" /> "+
                                     "<param name=\"allowScriptAccess\" value=\"sameDomain\" /> "+
                                     "<param name=\"wmode\" value=\"transparent\" /> " +
                                     "<param name=\"play\" value=\"false\" /> " +
                                     "<param name=\"loop\" value=\"false\" /> ";
                        
                        tag    = "<object style=\"display:none\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" "+
                                   "width=\"100%\" height=\"100%\" "+
                                   "codebase=\"http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab\"> " +
                                   "<param name=\"movie\" value=\""+elem.src+"\" /> " +
                                   params;
                        if(!Prototype.Browser.IE){
                          tag += "<object type=\"application/x-shockwave-flash\" data=\""+elem.src+"\" width=\"100%\" height=\"100%\"> " +
                                 params +
                                 "</object>"
                        }
                        tag   += "</object>";
                      }
                      init.innerHTML = tag;
                      tag            = init.firstChild;
                      init.innerHTML = "";
                      
                      obj.container.appendChild(tag);
                      obj.elements.push({ 'elem' : elem,
                                          'tag'  : tag });
                    });
                  },
  createControl:  function(icon){
                    var img = $(new Image());
                    img.src = icon;
                    
                    var dim = this.elements[this.current].tag.getDimensions();
                    
                    var t   = 15;
                    var l   = Math.round((dim.width - (dim.width * 0.03)) - this.options.iconWidth);
                    
                    
                    with(img.style) {
                      position = 'absolute';
                      top      = t.toString()+'px';
                      left     = l.toString()+'px';
                      width    = this.options.iconWidth.toString() + 'px';
                      height   = this.options.iconHeight.toString() + 'px';
                      display  = 'none';
                      cursor   = 'pointer';
                      zIndex   = '5';
                    }
                    
                    this.container.appendChild(img);
                    return img;
                  },
  displayControl: function(){
                    if(this.running)
                      this.pause.show();
                    else
                      this.play.show();
                  },
  hideControl:    function(e){
                    var dim = this.elements[this.current].tag.getDimensions();
                    var pos = this.elements[this.current].tag.cumulativeOffset();
                    
                    if(e.clientY >= pos.top && e.clientY <= (pos.top + dim.height) && e.clientX >= pos.left && e.clientX <= (pos.left + dim.width))
                      return false;

                    this.pause.hide();
                    this.play.hide();    
                  },
  runControl:     function(ctrl){
                    if(ctrl == 'pause'){
                      this.halt();
                      this.pause.hide();
                    } else {
                      this.start();
                      this.play.hide();
                    }
                  },
  selectSlide:    function(){
                    if(this.elements[this.current].elem.link){
                      if(this.elements[this.current].elem.external){
                        window.open(this.elements[this.current].elem.link);
                      } else {
                        location.href = this.elements[this.current].elem.link;
                      }
                    }
                  },
  change:         function(){
                    var next = (this.current + 1 >= this.elements.length) ? 0 : this.current + 1;

                    if(next == this.current)
                      return;
                    
                    var a = this.elements[this.current].tag;
                    var b = $(this.elements[next].tag);
                    
                    b.setOpacity(this.options.minOpacity);
                    b.style.display = '';
                    
                    if(this.elements[this.current].elem.type == 'flash'){
                      var flashelem = this.getFlashElem(this.elements[this.current].tag);
                      flashelem.StopPlay();
                    }
                    
                    new Effect.Parallel([
                      new Effect.Opacity(a, {
                        duration:    this.options.duration,
                        from:        1.0,
                        to:          this.options.minOpacity }),
                      new Effect.Opacity(b, {
                        duration:    this.options.duration,
                        from:        this.options.minOpacity,
                        to:          1.0 })],
                      { afterFinish: this.reset.bind(this, this.elements[this.current], next) });
                  },
  getFlashElem:   function(tag){
                    tag = $(tag);
                    if(typeof tag.Play == 'undefined' && tag.down('object'))
                      return tag.down('object');
                    else
                      return tag;
                  },
  reset:          function(old, nextIndex){
                    if(old.elem.type == 'flash'){
                      var flashelem = this.getFlashElem(old.tag);
                      flashelem.Rewind();
                    }
                    
                    old.tag.style.display = 'none';
                    this.current          = nextIndex;
                    
                    if(this.elements[this.current].elem.type == 'flash'){
                      var flashelem = this.getFlashElem(this.elements[this.current].tag);
                      
                      if(!flashelem.IsPlaying())
                        flashelem.Play();
                    }
                    
                    this.startTimer();
                  },
  startTimer:     function(){
                    var delay = parseInt((this.elements[this.current].elem.delay) ? this.elements[this.current].elem.delay : 0);
                    if(this.running)
                      this.timer = setTimeout(this.change.bind(this), delay + (this.options.interval * 1000));
                  },
  halt:           function(){
                    clearTimeout(this.timer);
                    this.pause.hide();
                    this.play.hide();
                    this.running = false;    
                  },
  stop:           function(){
                    clearTimeout(this.timer);
                    this.flushObservers();
                    this.pause.hide();
                    this.play.hide();
                    this.running = false;
                  },
  start:          function(){
                    this.running = true;
                    this.change();
                  }
});

/**************************************************************************************************
*
* Local Info
* ----------
* Generic container to store information within a cookie
*
**************************************************************************************************/

LJH.LocalInfo = {
  open:     function(){
              var info = unescape(LJH.Cookie.get('LJHInfo')).trim();
              info     = info.toQueryParams();
              
              return info;
            },
  save:     function(info){
              var infoStr = Object.toQueryString(info);
              
              LJH.Cookie.set('LJHInfo', infoStr);
            },
  get:      function(key){
              info = this.open();
              return info[key];
            },
  set:      function(key, value){
              info      = this.open();
              info[key] = value.trim();
              
              this.save(info);
            }
};

/**************************************************************************************************
*
* Thumb Strip
* -----------
* Used to control thumbnail image strip on property profile page
*
**************************************************************************************************/

LJH.ThumbStrip = Class.create({
  initialize:       function(strip, viewport, size, direction, next, prev){
                      this.strip     = $(strip);
                      this.viewport  = $(viewport);
                      this.size      = size;
                      this.direction = direction;
                      this.clicks    = new Array();
                      this.running   = false;
                      
                      if(direction == 'x'){
                        $(next).observe('click', this.queueClick.bind(this, 'right'));
                        $(prev).observe('click', this.queueClick.bind(this, 'left'));
                        this.viewportSize = this.viewport.getDimensions().width;
                      } else {
                        $(next).observe('click', this.queueClick.bind(this, 'down'));
                        $(prev).observe('click', this.queueClick.bind(this, 'up'));
                        this.viewportSize = this.viewport.getDimensions().height;
                      }
                    },
  run:              function(){
                      this.running = true;
                      
                      if(this.clicks.length){
                        var click = this.clicks.shift();
                        click.func();
                      }
                    },
  completeClick:    function(){
                      if(this.clicks.length)
                        this.run();
                      else
                        this.running = false;
                    },
  queueClick:       function(type){
                      var click = { type: type };
                      switch(type){
                        case 'up':
                          click.func = this.moveUp.bind(this);
                          break;
                        case 'down':
                          click.func = this.moveDown.bind(this);
                          break;
                        case 'left':
                          click.func = this.moveLeft.bind(this);
                          break;
                        case 'right':
                          click.func = this.moveRight.bind(this);
                          break;
                      }
                      
                      if(this.clicks.last() && this.clicks.last().type != type)
                        this.clicks = [click];
                      else
                        this.clicks.push(click);
                      
                      if(!this.running)
                        this.run();
                    },
  moveUp:           function(){
                      this.moveNeg(this.strip.positionedOffset().top, this.strip.getDimensions().height);
                    },
  moveDown:         function(){
                      this.movePos(this.strip.positionedOffset().top);
                    },
  moveLeft:         function(){
                      this.moveNeg(this.strip.positionedOffset().left, this.strip.getDimensions().width);
                    },
  moveRight:        function(){
                      this.movePos(this.strip.positionedOffset().left);
                    },
  moveNeg:          function(offset, dimension){
                      if(dimension - Math.abs(offset) <= (this.viewportSize)){
                        this.completeClick();
                      } else {
                        new Effect.Move(this.strip, Object.extend({ mode: 'relative', duration: 0.5, afterFinish: this.completeClick.bind(this) },  
                                                                  ((this.direction == 'x') ? { x: -1 * this.size, y: 0 } : 
                                                                                             { y: -1 * this.size, x: 0 })));
                      }
                    },
  movePos:          function(offset){
                      if(offset >= 0){
                        this.completeClick();
                      } else {
                        new Effect.Move(this.strip, Object.extend({ mode: 'relative', duration: 0.5, afterFinish: this.completeClick.bind(this) },  
                                                                  ((this.direction == 'x') ? { x: this.size, y: 0 } : 
                                                                                             { y: this.size, x: 0 })));
                      }
                    }
});

/**************************************************************************************************
*
* Lightbox
* ------------------
* Generic lightboxing class based on principles of lightbox and lightview libraries.
* Images can be added as either singletons or gallery groups.
* To configure a link as a singleton add 'lightbox' to the class name.
* To configure a link as a gallery add 'lightbox gallery[{gallery name}]' to the class name.
*
**************************************************************************************************/

LJH.Lightbox = Class.create({ 
  defaultConfig:      { overlayColor:   '#000000',
                        overlayOpacity: 0.6,
                        startWidth:     300,
                        startHeight:    150,
                        borderSize:     10,
                        infoHeight:     37,
                        buttonWidth:    50,
                        buttonHeight:   100 },
  initialize:         function(config){
                        this.config    = Object.extend(this.defaultConfig, config);
                        this.galleries = { };
                        this.dim       = null;
                        this.build();
                        this.current   = null;
                        this.loadImg   = new Image();
                      },
  parseImages:        function(){
                        var links     = $$("a.lightbox");
                        var galleries = { };
                        var obj       = this;
                        var j         = 0;
                        
                        links.each(function(link){
                          var name = link.className.match(/gallery\[(\w+)\]/);
                          name     = (name) ? name[1] : 'single'+(j++);

                          if(!galleries[name])
                            galleries[name] = [ ];
                          
                          galleries[name].push(link.href);
                          
                          link.observe('click', obj.open.bind(obj, name, galleries[name].length - 1));
                        });
                        
                        this.galleries = galleries;
                      },
  open:               function(gallery, i){
                        if(arguments.length > 2)
                          Event.stop(arguments[2]);
                        this.dim = { width: this.config.startWidth, height: this.config.startHeight };
                        
                        this.change(gallery, i);
                      },
  change:             function(gallery, i){
                        if(!this.galleries[gallery] || this.galleries[gallery].length <= i)
                          return false;
                        
                        this.current = { gallery: gallery, i: i };
                        
                        this.positionElements();
                        
                        this.setInfoMessage("Image "+(i + 1)+" of "+this.galleries[gallery].length);
                        
                        this.setLoading(true);
                        var imageBox = this.box.imageBox;
                        var img      = imageBox.select('img')[0];
                        
                        imageBox.style.width  = this.dim.width.toString()+'px';
                        imageBox.style.height = this.dim.height.toString()+'px';
                        
                        img.style.width  = "";
                        img.style.height = "";
                        
                        img.setOpacity(0);
                        img.src          = this.galleries[gallery][i];
                        this.loadImg     = new Image();
                        this.loadImg.src = this.galleries[gallery][i];
                        
                        var info     = imageBox.select('.info')[0];
                        info.hide();
                        
                        this.box.nextButton.hide();
                        this.box.prevButton.hide();
                        this.box.overlay.show();
                        this.box.imageBox.show();
                        
                        this.load(img);    
                      },
  next:               function(e){
                        Event.stop(e);
                        this.change(this.current.gallery, this.current.i + 1);
                      },
  prev:               function(e){
                        Event.stop(e);
                        this.change(this.current.gallery, this.current.i - 1);                        
                      },
  close:              function(){
                        if(arguments.length)
                          Event.stop(arguments[0]);
                        
                        this.box.overlay.hide();
                        this.box.imageBox.hide();
                        this.box.nextButton.hide();
                        this.box.prevButton.hide();
                      },
  load:               function(img){
                        if(img.complete && this.loadImg.complete){
                          var dim = this.resizeImage(img);
                          this.setLoading(false);
                          
                          var percentx = (dim.width / this.dim.width) * 100;
                          var percenty = (dim.height / this.dim.height) * 100;
                          var extray   = ((dim.height + this.config.infoHeight) / dim.height) * 100;
                          
                          if(Math.abs(percentx - 100) > 1){
                            new Effect.Scale(this.box.imageBox, percentx, { scaleX:          true,
                                                                            scaleY:          false,
                                                                            scaleContent:    false,
                                                                            scaleFromCenter: true,
                                                                            duration:        0.3,
                                                                            queue:           'end',
                                                                            scaleMode:       { originalHeight: this.dim.height, 
                                                                                               originalWidth:  this.dim.width } });
                          } else {
                            this.box.imageBox.style.width = dim.width.toString()+'px';
                          }
                          
                          
                          if(Math.abs(percenty - 100) > 1){
                            new Effect.Scale(this.box.imageBox, percenty, { scaleX:          false,
                                                                            scaleY:          true,
                                                                            scaleContent:    false,
                                                                            scaleFromCenter: true,
                                                                            duration:        0.3,
                                                                            queue:           'end',
                                                                            afterFinish:     this.displayInfo.bind(this),
                                                                            scaleMode:       { originalHeight: this.dim.height, 
                                                                                               originalWidth:  this.dim.width } });
                          } else {
                            this.displayInfo();
                            this.box.imageBox.style.height = dim.height.toString()+'px';
                          }
                          
                          new Effect.Opacity(this.box.imageBox.select('img')[0], { from: 0, to: 1, duration: 0.3, queue: 'end' });
                          
                          new Effect.Scale(this.box.imageBox, extray,   { scaleX:          false,
                                                                          scaleY:          true,
                                                                          scaleContent:    false,
                                                                          scaleFromCenter: false,
                                                                          duration:        0.3,
                                                                          queue:           'end',
                                                                          afterFinish:     this.showButtons.bind(this),
                                                                          scaleMode:       { originalHeight: dim.height, 
                                                                                             originalWidth:  dim.width } });
                          this.dim = dim;
                        } else {
                          setTimeout(this.load.bind(this, img), 300);
                        }
                      },
  displayInfo:        function(){
                        this.box.imageBox.select('.info')[0].show();
                      },
  setLoading:         function(loading){
                        if(loading)
                          this.box.imageBox.addClassName('loading');
                        else
                          this.box.imageBox.removeClassName('loading');
                      },
  resizeImage:        function(img){
                        var odim  = { width: this.loadImg.width, height: this.loadImg.height };
                        var vdim  = document.viewport.getDimensions();
                        var dim   = { width: 0, height: 0 };
                        
                        // adjust max dimensions to 90% of viewport size
                        vdim.width  = (vdim.width * 0.9) - (this.config.borderSize * 2);
                        vdim.height = (vdim.height * 0.9) - (this.config.borderSize * 2) - this.config.infoHeight;
                        
                        var ar1  = odim.width / odim.height;
                        var ar2  = vdim.width / vdim.height;
                        
                        if(ar1 >= ar2){ //adjust width
                          dim.width  = (odim.width > vdim.width) ? vdim.width : odim.width;
                          dim.height = (odim.width > vdim.width) ? Math.round(ar1 / vdim.width) : odim.height;
                        } else { //adjust height
                          dim.width  = (odim.height > vdim.height) ? Math.round(ar1 * vdim.height) : odim.width;
                          dim.height = (odim.height > vdim.height) ? vdim.height : odim.height;
                        }
                        
                        img.style.width  = dim.width.toString()+'px';
                        img.style.height = dim.height.toString()+'px';
                        
                        return dim;
                      },
  positionElements:   function(){
                        var vdim   = document.viewport.getDimensions();
                        var offset = document.viewport.getScrollOffsets();
                        
                        var left = Math.round((vdim.width - this.dim.width) / 2);
                        var top  = Math.round((vdim.height - this.dim.height) / 2);
                        
                        top     += offset.top;
                        
                        this.box.imageBox.style.left = left.toString()+'px';
                        this.box.imageBox.style.top  = top.toString()+'px';
                      },
  showButtons:        function(){
                        var pos  = this.box.imageBox.positionedOffset();
                        
                        var top  = (pos.top - this.config.borderSize) + (this.dim.height * 0.2);
                        var left = pos.left;
                        
                        if(this.galleries[this.current.gallery].length > this.current.i + 1){
                          this.box.nextButton.style.top  = top.toString()+'px';
                          this.box.nextButton.style.left = (left + this.dim.width + this.config.borderSize).toString()+'px';
                          this.box.nextButton.style.clip = 'rect(auto 0px auto auto)';  
                          this.box.nextButton.show();
                          new Effect.ShowButton(this.box.nextButton, 'right', this.config.buttonWidth);
                        } 
                        
                        if(this.current.i > 0){
                          this.box.prevButton.style.top  = top.toString()+'px';
                          this.box.prevButton.style.left = (left - this.config.buttonWidth).toString()+'px';
                          this.box.prevButton.style.clip = 'rect(auto auto auto '+this.config.buttonWidth+'px)';
                          this.box.prevButton.show();
                          new Effect.ShowButton(this.box.prevButton, 'left', this.config.buttonWidth);
                        }
                      },
  setInfoMessage:     function(message){
                        var adFrame = ($('oas-popupsrc')) ? $('oas-popupsrc').value : '';
                        
                        if(adFrame)
                          message  = "<div class=\"lightbox-message\">"+message+"</div>"+adFrame;
                        
                        this.box.imageBox.select('div.info')[0].select('div.infoleft')[0].innerHTML = message;
                      },
  build:              function(){
                        var box         = { };
                        box.overlay     = this.buildOverlay();
                        box.imageBox    = this.buildImageBox();
                        box.nextButton  = this.buildButton('next');
                        box.prevButton  = this.buildButton('prev');
                        
                        box.nextButton.observe('click', this.next.bind(this));
                        box.prevButton.observe('click', this.prev.bind(this));
                        box.overlay.observe('click', this.close.bind(this));
                        
                        for(var n in box){
                          box[n].hide(); 
                          document.body.appendChild(box[n]) ;
                        }
                        
                        this.box = box;
                        this.dim = { width: this.config.startWidth, height: this.config.startHeight };
                        
                        this.positionElements();
                      },
  buildOverlay:       function(){
                        var overlay = new Element('div');
                        var dim     = $(document.body).getDimensions();
                        
                        overlay.style.width           = dim.width.toString()+'px';
                        overlay.style.height          = dim.height.toString()+'px';
                        overlay.style.backgroundColor = this.config.overlayColor;
                        overlay.setOpacity(this.config.overlayOpacity);
                        overlay.style.position        = 'absolute';
                        overlay.style.zIndex          = 1000;
                        overlay.style.top             = '0px';
                        overlay.style.left            = '0px';
                        overlay.style.overflow        = 'hidden';
                        
                        overlay.addClassName('_lightbox');
                        
                        return overlay;
                      },
  buildImageBox:      function(){
                        var imageBox = new Element('div');
                        
                        imageBox.style.width           = this.config.startWidth.toString()+'px';
                        imageBox.style.height          = this.config.startHeight.toString()+'px';
                        imageBox.style.position        = 'absolute';
                        imageBox.style.zIndex          = 1001;
                        imageBox.style.backgroundColor = '#FFFFFF';
                        
                        imageBox.addClassName('_lightbox');
                        imageBox.addClassName('container');
                        imageBox.addClassName('loading');
                        
                        var img      = new Element('img');
                        imageBox.appendChild(img);
                        
                        imageBox.appendChild(this.buildInformation());
                        
                        // for safari
                        if(Prototype.Browser.WebKit)
                          img.onload = function(){ img.complete = true; };
                        
                        return imageBox;
                      },
  buildButton:        function(type){
                        var button = new Element('a');
                        
                        button.href           = 'javascript:void(0);';
                        button.style.position = 'absolute';
                        button.style.zIndex   = 1001;
                        button.addClassName('_lightbox');
                        button.addClassName((type == 'next') ? 'nextbutton' : 'prevbutton');
                        
                        return button;
                      },
  buildInformation:   function(){
                       var info = new Element('div');
                       
                       info.style.backgroundColor = '#ffffff';
                       info.addClassName('_lightbox');
                       info.addClassName('info');
                       
                       var left = new Element('div');
                       left.addClassName('infoleft');
                       
                       var right = new Element('div');
                       right.addClassName('inforight');
                       
                       var close  = new Element('a');
                       close.href = 'javascript:void(0);';
                       close.addClassName('_lightbox');
                       close.addClassName('close');
                       
                       close.observe('click', this.close.bind(this));
                       
                       right.appendChild(close);
                       
                       info.appendChild(left);
                       info.appendChild(right);
                       
                       return info;
                     }
});

// Used by lightbox to display prev/next buttons
Effect.ShowButton = Class.create(Effect.Base.prototype, {
  initialize: function(button, side, width){
    this.button  = button;
    this.side    = side;
    this.width   = width;
    var options  = Object.extend({duration: 0.3}, arguments[2] || {});
    this.start(options);
  },
  update: function(position){
    var delta;
    var pos;

    if(this.side == 'left')
      delta = Math.ceil((1 - position) * this.width);
    else
      delta = Math.ceil(position * this.width);
    
    this.button.style.clip = (this.side == 'left') ? 'rect(auto auto auto '+delta+'px)' : 'rect(auto '+delta+'px auto auto)';
  }
});

/**************************************************************************************************
*
* Advice Centre
* -------------
* Controlling class for advice centre - handles the display / modification of question topics.
*
* N.B. Code is located here even though it is only used by one page because it is large enough to
* justify removing it from a loadscript.
*
**************************************************************************************************/

LJH.AdviceCentre = Class.create({
  initialize:     function(subjectSelect, questionsArea, answerArea){
                    this.faqs          = { };
                    this.subjectSelect = $(subjectSelect);
                    this.questionsArea = $(questionsArea);
                    this.answerArea    = $(answerArea);
                    
                    this.observers     = [ ];
                    
                    this.subjectSelect.observe('change', this.changeSubject.bind(this));
                  },
  changeSubject:  function(){
                    var subject = $F(this.subjectSelect);
                    
                    if(this.faqs[subject])
                      this.refreshSubject(subject);
                    else
                      this.loadSubject(subject);
                  },
  loadSubject:    function(subject){
                    var obj = this;
                    this.clearQuestions();
                    
                    this.questionsArea.innerHTML = "<span class=\"red\">loading ...</span>";
                    
                    new Ajax.Request('/callbacks/advicecentre.php',
                                     { method:      'get',
                                       parameters:  { 'func':    'selectSubject',
                                                      'subject': subject },
                                       onComplete:  function(response){ obj.parseSubject(subject, response); obj.refreshSubject(subject); } });
                  },
  parseSubject:   function(subject, response){
                    var json      = response.responseText.replace(/\]\{\]/, "");
                    var questions = eval("("+json+")");
                    var obj       = this;
                    
                    this.faqs[subject] = { };
                    questions.each(function(question){
                      obj.faqs[subject][question.id] = question;
                    });
                  },
  refreshSubject: function(subject){
                    var questions = (this.faqs[subject]) ? this.buildQuestions(subject, this.faqs[subject]) : '';
                    var obj       = this;
                    
                    this.clearQuestions();
                    this.questionsArea.innerHTML = questions;
                    
                    this.questionsArea.select('a').each(function(a){
                      var observer = { node: a, event: 'click', callback: obj.selectQuestion.bind(obj, a) };
                      a.observe('click', observer.callback);
                      obj.observers.push(observer);
                    });
                  },
  clearQuestions: function(){
                    this.answerArea.innerHTML = "";
                    this.answerArea.hide();
                    
                    this.observers.each(function(observer){
                      observer.node.stopObserving(observer.event, observer.callback);
                    });
                    
                    this.questionsArea.innerHTML = "";
                  },
  buildQuestions: function(subject, questions){
                    var list = '<ul>';
                    for(var id in questions){
                      list  += '<li>';
                      list  += '<a href="?subject='+subject+'&question='+id+'">'+questions[id].question+'</a></li>';
                      list  += '</li>';
                    }
                    list    += '</ul>';

                    return list;
                  },
  selectQuestion: function(a, e){
                    Event.stop(e);
                    
                    var params   = a.href.toQueryParams();
                    
                    if(this.faqs[params.subject] && this.faqs[params.subject][params.question]){
                      this.answerArea.innerHTML = "<p class=\"question\">"+this.faqs[params.subject][params.question].question+"</p>" +
                                                  this.faqs[params.subject][params.question].answer;
                      this.answerArea.show();
                      this.scrollIntoView(this.answerArea);
                    }
                    
                    this.questionsArea.select('a').each(function(link){ link.removeClassName('active'); });
                    
                    a.addClassName('active');
                  },
  scrollIntoView: function(node){
                    var offset    = node.cumulativeOffset().top;
                    var viewport  = document.viewport.getHeight();
                    var scrollPos = document.viewport.getScrollOffsets().top;
                    var position  = scrollPos + viewport;
                    
                    if(position <= offset + 100 || scrollPos >= offset){ //answer not in screen
                      var height  = node.getHeight() + 50;
                      if(height > viewport)
                        height    = viewport - 10;
                      var adjust  = offset + height - viewport;
                      window.scrollBy(0, (adjust - scrollPos));
                    }
                  }
});

/**************************************************************************************************
*
* Analytics Loader
* ----------------
* Class to load Google Analytics in an optimised fashion.
*
* See: http://higginsforpresident.net/2008/06/google-analytics-after-onload/ &
*      http://alex.dojotoolkit.org/2009/04/ending-the-gajs-wait/
*
**************************************************************************************************/

LJH.AnalyticsLoader = Class.create(LJH.Observable, {
  interval:   420,
  initialize: function(trackingNo){
                this.trackingNo = trackingNo;
                
                var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
                var script   = new Element('script', { 'src': gaJsHost + 'google-analytics.com/ga.js', 'type': 'text/javascript' });
                $$('head')[0].appendChild(script);
                setTimeout(this.checkLoad.bind(this), this.interval);
              },
  checkLoad:  function(){
                setTimeout((window['_gat']) ? this.startup.bind(this) : this.checkLoad.bind(this), this.interval);
              },
  startup:    function(){
                this.tracker = _gat._getTracker(this.trackingNo);
                this.tracker._initData();
                this.fire('load');
              }
});