/*==========================================================================
    Copyright 2009 Thin Client Solutions ABN 13159144040
    
    Developed for WHITSUNDAYS SAILING ADVENTURES.COM
    
    wsa_availability.js
    VERSION 1.5.2

==========================================================================
	Change History
	1.4 - Incorporate hack for bug in IE6 & IE7, make scrolling elements relative positioning
	1.5 (5 Apr 2011) add support for default coupon
	1.5.1 (5 Apr 2011) changed for(var i in obj) to for(var i=0; i<array.length; i++) where obj was really an array
	1.5.2 (1 Jul 2011) Changed link to ccc to handle https without error
==========================================================================*/


/*==========================================================================
    Bring in the stuff that is needed by this code   
*/

var theProtocol = (window.location.protocol == "https:")? "https:" : "http:";
var default_css = '<link href="'+theProtocol+'//whitsailing.com/css/wsa_availability.css" rel="stylesheet" type="text/css" />'
document.write(default_css);

//alert ('version 1.5.2');

//function showObject(obj,n){
//    var s = n+"::";
//    for (p in obj){
//        s += p+":"+obj[p]+",";
//    }
//    window.status = s;
//}
//function dump_props(obj, obj_name) {
//     msgWindow=window.open("","displayWindow")
//     var result = "";
//     for (var i in obj) {
//	//if (i.indexOf("on",0)!=0)
//          msgWindow.document.write (obj_name + "." + i + " = " + obj[i] + "<BR>"); }
//}

/*=============== START OF CLASS DEFINITION ================================*/
function wsaAvailability(siteName, div_id, vesselName, smallCell, calMonth){
    // Constants used by this object
    
    // class names
    this._AC_DEFAULT = 'ac-default';
    this._AC_EMPTYDIV = 'ac-emptydiv';
    this._AC_LOADINGDIV = 'ac-loadingdiv';
    this._AC_TEXT = 'ac-text';
    this._AC_AVAIL = 'ac-avail';
    this._AC_FULL = 'ac-full';
    this._AC_CELLDEFAULT = 'ac-celldefault';
    this._AC_TOPLEFT = 'ac-topleft';
    this._AC_TOPRIGHT = 'ac-topright';
    this._AC_BOTTOMLEFT = 'ac-bottomleft';
    this._AC_BOTTOMRIGHT = 'ac-bottomright';
    this._AC_CURMONTH = 'ac-curmonth';
    this._AC_OTHERMONTH = 'ac-othermonth';
    this._AC_HEADERNAME = 'ac-headername';
    this._AC_HEADERMONTH = 'ac-headermonth';
    this._AC_HEADERCELL = 'ac-headercell';
    this._AC_DAYHEADER = 'ac-dayheader';
    this._AC_SELECT = 'ac-select';
    this._AC_POPUP = 'ac-popup';
    this._AC_POPUPHEADER = 'ac-popupheader';
    this._AC_POPUPCELLHEADER = 'ac-popupcellheader';
    this._AC_POPUPCELL = 'ac-popupcell';
    this._AC_INNERTABLE = 'ac-innertable';
    this._AC_CALTABLE = 'ac-caltable';
    this._AC_HELPBUTTON = 'ac-helpbutton';
    this._AC_HELPDIV = 'ac-helpdiv';
    
    // default messages
    this._MSG_EMPTY = 'Vessel Availability';
    this._MSG_LOADING = 'Loading...';   
    this._EMSG_PROXY_INSTALL_ERROR = 'The WSA proxy server is not installed correctly';
    this._EMSG_SERVICE_UNAVAILABLE = 'Vessel Departure Information is Temporarily Unavailable'; 
    this._EMSG_UNEXPECTED_STATUS = this._EMSG_SERVICE_UNAVAILABLE+'\n\nUnexpected status:';
    this._EMSG_DATA_ERROR = 'The data received is corrupted.\nPlease advise the Webmaster if this condition persists.';
    
    // other constants, can be settable
    this._CELL_SIZE = 30;
    this._ROWS = 5;
    this._COLS = 7;
    this._DAYNAMES_HEIGHT = 20;
    this._PROXY_PATH = '';
    this._PROXY_NAME = 'wsa_availability_proxy.php';
    this._BOOKING_URL = 'http://whitsailing.com/booking.php';
    this._HELPDIV_OFFSETLEFT = 50;
    this._HELPDIV_OFFSETLEFT_SMALL = 0;
    this._HELPDIV_OFFSETTOP = 50;
    
    this._HELP_HTML = 'This calendar shows availability on #vessel# for #month#.<br>';
    this._HELP_HTML += '<br>The <span class=ac-avail>&nbsp;&nbsp;&nbsp;&nbsp;</span> cells represent departures that have berths available.<br>';
    this._HELP_HTML += '<br><b>To View Availability:</b><br>';
    this._HELP_HTML += 'Place the cursor on the <span class=ac-avail>&nbsp;&nbsp;&nbsp;&nbsp;</span> area corresponding to your desired departure date.<br>';
    this._HELP_HTML += '<br><b>To Show a Different Month:</b><br>';
    this._HELP_HTML += 'Select the desired month and year from the drop down list.<br>';
    this._HELP_HTML += '<br><b>To Make a Booking:</b><br>';
    this._HELP_HTML += 'Click on the <span class=ac-avail>&nbsp;&nbsp;&nbsp;&nbsp;</span> area corresponding to your desired departure date.';
    
//======= End of constants =====================

/*============================================================================
        PUBLIC METHODS
*/        
    /*---------------------------------------------------------------
        Public method to set the ajax proxy path
        parameters:
            server path must end with /
        returns
            nothing
    */
    this.setProxyPath = function(path){
        this._PROXY_PATH = path;
    }
    /*---------- end public method setProxyPath----------------*/

    /*---------------------------------------------------------------
        Public method to set the ajax proxy name
        parameters:
            file name cannot contain '/'
        returns
            nothing
    */
    this.setProxyName = function(name){
        this._PROXY_NAME = name;
    }
    /*---------- end public method setProxyName ----------------*/

    /*---------------------------------------------------------------
        Public method to set the vessel name
        parameters:
            vessel name
        returns
            nothing
    */
    this.setVesselName = function(n){
        this.vesselName = n;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to set the calendar month
        parameters:
            calMonth: the date as YYYY.MM.DD
        returns
            false if the date is invalid
    */
    this.setMonth = function(calMonth){
        if (!calMonth) return false;
        var dte= new Date();
        var year = dte.getFullYear();
        var date_ok = false;
        while (true){
            
            var parts = calMonth.split("-");
            if (parts.length!=3) break;
            parts[1] = '00'+parts[1];
            parts[1] = parts[1].substr(parts[1].length-2);
            if ((parts[0]<year)||(parts[0]>(year+3))) break;
            if ((parts[1]<1)||(parts[1]>12)) break;
            if ((parts[2]<1)||(parts[1]>31)) break;
            date_ok = true;
            break;
        }
        if (!date_ok) return false;
        this.calMonth = parts[0]+'-'+parts[1]+'-01'; // must be yyyy-mm-01
        
        // set the date selector
        if (this.selectDate){
            this.selectDate.selectedIndex = 0;
            for (var i=0; i<this.selectDate.length; i++){
                if (this.selectDate.options[i].value==this.calMonth){
                    this.selectDate.selectedIndex = i;
                    break;
                } 
            }
        }
        return true;
    }
    /*---------- end public method setDate ----------------*/
    
    /*---------------------------------------------------------------
        Public method to set the site name
        parameters:
            site name
        returns
            nothing
    */
    this.setSiteName = function(site){
        this.siteName = site;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to set a coupon code
        parameters:
            coupon code
        returns
            nothing
    */
    this.setCouponCode = function(code){
        this.couponCode = code;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to disable/enable any default coupon code
        parameters:
            boolean value
        returns
            nothing
    */
    this.setDisableDefaultCouponCode = function(bool){
        this.disableDefaultCouponCode = bool;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to disable/enable display of reduced proces ion the popup
        parameters:
            boolean value
        returns
            nothing
    */
    this.setDisableShowPriceReduction = function(bool){
        this.disableShowPriceReduction = bool;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to get the current default coupon code
        parameters:
            none
        returns
            coupon code if set, otherwise null
    */
    this.getDefaultCouponCode = function(){
        if (this.defaultCouponCode){
            return this.defaultCouponCode;
        }
        return null;
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to set the booking callback
        parameters:
            Pointer to a function to be called when the availability cell is clicked
            This function must accept 4 parameters
                siteName, vesselName, run id, departure date
        returns
            false if callback is not a function
    */
    this.setBookingCallBack = function(callBack){
    	if (typeof(callBack)=='function'){
            this.bookingCallBack = callBack;
    		return true;
    	}
    	else{
    		return false;
    	}
    }
    /*---------- end public method setVesselName ----------------*/
    
    /*---------------------------------------------------------------
        Public method to refresh the calendar
        parameters:
            none
        returns
            nothing
    */
    this.refresh = function(){
        // Query the ajax server    
        //alert(this._getAjaxUrl(this.vesselName, this.calMonth));
        this.ajax.getData(this._getAjaxUrl(this.vesselName, this.calMonth));

        // in case the vessel name has changed
        this._setText(this.namecell,this.vesselName);        
        
        // show the 'loading' message
        this._setText(this.loadingtd,this._MSG_LOADING);
        this._putElement(this.bdiv, this.loadingdiv);
        this._putElement(this.div, this.cdiv);
        //this.div.insertBefore(this.helpdiv,this.cdiv) 
        //this.div.insertBefore(this.popupdiv,this.cdiv) 
        //this.div.appendChild(this.helpdiv);
        //this.div.appendChild(this.popupdiv);

        // adjust the position of the help button based on current rendering
        this.helpbutton.style.left = (this.cdiv.clientWidth-this.helpbutton.offsetWidth)+'px';        
    }
    /*---------- end public method refresh ----------------*/
    
    /*---------------------------------------------------------------
        Public method to start the booking wizard
        parameters:
            siteName:  Text represent the name of the calling website
            run: the run number for the booking.
        returns
            nothing
    */
    this.startBooking = function(siteName, run, coupon){
        var cpn = '';
        if (coupon){
            cpn = '&coupon='+coupon;
        }        
        var url = this._BOOKING_URL+'?run='+run+'&source='+siteName.replace(/ /g, '+')+cpn; 
        var win = window.open(url,"booking_new", "width=500,height=500,scrollbars=yes,status=no,resizable=yes");
        return win;
    }
    /*---------- end public method startBooking ----------------*/

    /*---------------------------------------------------------------
        Public method to set the help contents
        parameters: (all parameters are optional
            html:  used for innerHTML to the help popup
            offsetLeft, offsetTop: used to position the popup
        returns
            nothing
    */
    this.setHelpHtml = function(html, offsetLeft, offsetTop){
        if (html) this._HELP_HTML = html;
        if (offsetLeft) this.helpdivOffsetLeft = offsetLeft;
        if (offsetTop) this.helpdivOffsetTop = offsetTop;                    
    }
    /*---------- end public method setHelpHtml ----------------*/

//================================== End of public methods =====================
    
    /*---------------------------------------------------------
    Private Class to construct a simple Ajax object
    parameters:
        gotdata is a function to receive the returned data,
            format is an object {htmlStatus:status, htmlData:string}
    */
    this._myAjax = function (gotData){
        this.xmlhttp=null;
        this.count=0;
        this.gotData = gotData;
        if (window.XMLHttpRequest){
            // code for Firefox, Opera, IE7, etc.
            this.xmlhttp=new XMLHttpRequest();
        }
        else if (window.ActiveXObject){
            // code for IE6, IE5
            this.xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
        }
        if (this.xmlhttp==null)
    		alert("Your browser does not support XMLHTTP.");
        this.getData = function(url){
            if (this.xmlhttp!=null){
                this.xmlhttp.open("GET",url+"?count="+this.count++,true);
                this.xmlhttp.onreadystatechange=this.regStateChange(this);
                //alert("Ready State = "+this.xmlhttp.readyState);
    			this.xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2005 00:00:00 GMT");
                this.xmlhttp.send(null);
            }
        }
        this.regStateChange = function(context){
            return function(){context.stateChange(context)}
        }
        // PRIVATE METHOD
        this.stateChange = function(context){
            //alert("Ready State = "+this.xmlhttp.readyState);
            if (context.xmlhttp.readyState==4){
                // 4 = "loaded"
                context.gotData({htmlStatus:context.xmlhttp.status, htmlData:context.xmlhttp.responseText});
            }
        }
    }

    /*---------------------------------------------------------
    Private Class to construct a position object
    parameters:
        obj is a DOM element
        parentObj is a DOM element (if null relative to body)
    Position Object:
            {left:(absolute left location), top:(), width:(clientWidth), height:(clientHeight), x:(center), y:(center)}
    */
    this._findPos = function(obj, parentObj) {
        if (!parentObj) parentObj = null;
        var orig = obj;  // save the original object
        this.width = obj.clientWidth;
        this.height = obj.clientHeight;
    	var x = parseInt(obj.clientWidth/2);
	    var y = parseInt(obj.clientHeight/2);
    	var curleft = curtop = 0;
    	if (obj.offsetParent) {
    		curleft = obj.offsetLeft
    		curtop = obj.offsetTop
    		while ((obj = obj.offsetParent)&&(obj!=parentObj)) {
    			curleft += obj.offsetLeft
    			curtop += obj.offsetTop
    		}
    	    //if (obj==parentObj) alert('found parent');
    	}
    	this.left = curleft;
    	this.top = curtop;
	    // allow for scrolling
	    // we need to use parentNode for this, offsetParent doesnt work in Firefox or Chrome
    	var curleft = curtop = 0;
    	if (orig.parentNode) {
        	do {
        	    if (orig == document.body) break;
        		if (orig.scrollLeft>0) curleft += orig.scrollLeft;
        		if (orig.scrollTop>0) curtop += orig.scrollTop;
            } while ((orig = orig.parentNode)&&(orig!=parentObj));
    	    //if (orig==parentObj) alert('found parentNode');
        	this.left -= curleft;
        	this.top -= curtop;
            //status='left='+this.left+', top='+this.top;
	    }    
    	this.x = this.left + x;
	    this.y = this.top + y;
    }
    /* -----------------------------------------------------------------
        Private method to return the ajax url
        parameters
            vesselname: string 
            month: string yyyy-mm-dd (optional)
    */
    this._getAjaxUrl = function(vesselname,month){
        var url = this._PROXY_PATH+this._PROXY_NAME;
        // replace any spaces in the vessel name with +        
        url += '?vessel='+vesselname.replace(/ /g,"+");
        url += '&source='+this.siteName.replace(/ /g,"+");
        if (month){
            url += '&date='+month;
        }
        url += '&end';
        //alert(url);
        return url;
    }

    /* -----------------------------------------------------------------
    /* -----------------------------------------------------------------
        Private method to register the ajax callback
    */
    this._regCallBack = function(context){
        return function(d){context._gotAjaxData(d)}
    }

    /* -----------------------------------------------------------------
        Private method to process ajax data
    */
    this._gotAjaxData = function(ajaxObj){
        //showObject(ajaxObj,'ajax');
        if (ajaxObj.htmlStatus!='200'){
            this._showErrorMessage(ajaxObj);
            return;
        }
        try{
            eval('this.calObj = '+ajaxObj.htmlData);
        }
        catch(er){}
        if (typeof(this.calObj)!='object'){
            ajaxObj.dataError = true;
            this._showErrorMessage(ajaxObj);
            return;
        }
        this.useDefaultCoupon = false;
        this.defaultCouponCode = "";
        if ((this.calObj)&&(this.calObj[2])){
            if (this.calObj[2].OCFG_DEFAULT_COUPON){
                this.defaultCouponCode = this.calObj[2].OCFG_DEFAULT_COUPON;
            }
            if (this.calObj[2].OCFG_USE_DEFAULT_COUPON){
                if (this.calObj[2].OCFG_USE_DEFAULT_COUPON == '1'){
                    this.useDefaultCoupon = true;
                }
            }
        }

        this._updateCalendar();
    }

    /* -----------------------------------------------------------------
        Private method to get the ajax run object
        parameters:
            run: the run number
        returns:
            the run object or null if not found
    */
    this._getRunObject = function(run){
        var ajaxrunobjs = this.calObj[1];
        for (var i=0; i< ajaxrunobjs.length; i++){
            var arobj = ajaxrunobjs[i];
            if (arobj.run == run) return arobj;
        }
        return null;
    }
    /* ------------ end private method _getRunObject ---------------*/
    /* -----------------------------------------------------------------
        Private method to process mouseover event
        parameters:
            calcell: the calcell object
        see the _createCelCell method for a description of the calcell object
        returns:
            nothing
    */
    this._mouseOver = function(calcell){
        this._createPopup(calcell);
        
        var cellPos = new this._findPos(calcell.avail); //,this.cdiv);
        //showObject(cellPos,'pos');

    	// calculate the popup position based in the position of the cell in the calendar
    	var divCtr = new this._findPos(this.cdiv);
    	// horizontal position
    	if (divCtr.x > cellPos.left){
    		// position the popup to the right of the cell
    		var posX = cellPos.left + cellPos.width;
    	}
    	else{
    		// position the popup to the left of the cell
    		var posX = cellPos.left - this.popupdiv.scrollWidth;
    	}
    	// vertical position
    	if (divCtr.y > cellPos.top){
    		// position the popup below the cell
    		var posY = cellPos.top + cellPos.height;
    	}
    	else{
    		// position the popup above the cell
    		var posY = cellPos.top - this.popupdiv.scrollHeight;
    	}

    	// move the popup to the correct location
    	this.popupdiv.style.left = (posX) + 'px';
    	this.popupdiv.style.top = (posY) + 'px';

        this.popupdiv.style.visibility = 'visible';
        return null;
    }
    /* ------------ end private method _mouseOver ---------------*/
    /* -----------------------------------------------------------------
        Private method to process mouseout event
        parameters:
            calcell: the calcell object
        see the _createCelCell method for a description of the calcell object
        returns:
            nothing
    */
    this._mouseOut = function(calcell){
        this.popupdiv.style.visibility = 'hidden';
        return null;
    }
    /* ------------ end private method _mouseOver ---------------*/

    /* -----------------------------------------------------------------
        Private method to process click event
        parameters:
            calcell: the calcell object
        see the _createCelCell method for a description of the calcell object
        returns:
            nothing
    */
    this._click = function(calcell){
        if (this.bookingCallBack){
            this.bookingCallBack(this.siteName, this.vesselName, calcell.run.run, calcell.run.depart);
        }
        else{
            var coupon = null;
            if ((!this.disableDefaultCouponCode)&&(this.useDefaultCoupon)){
                coupon = this.defaultCouponCode;
            }
            if (this.couponCode){
                coupon = this.couponCode;
            }
            this.startBooking(this.siteName, calcell.run.run, coupon);
        }
    }
    /* ------------ end private method _click ---------------*/

    /* -----------------------------------------------------------------
        Private method to update an existing calendar with ajax data
        no parameters, returns nothing
        see the _createCelCell method for a description of the calcell object
        See the server (wsa_availability_ajax.php) for a description of calObj
    */
    this._updateCalendar = function(){
        // this.calObj is the ajax data
        var celldata = this.calObj[0];
        // celldata is now an array of objects, one per calendar cell
        for (var i=0; i < celldata.length; i++){
            var ajaxcelldata = celldata[i]; 
            var calcell = this.calcells[i];
            calcell.run = null; // clear any old run data
            this._resetCalCell(calcell);
            this._setText(calcell.txt, ajaxcelldata.txt);
            calcell.txt.className = this._AC_TEXT+' '+(ajaxcelldata.curmth?this._AC_CURMONTH:this._AC_OTHERMONTH);
            calcell.txt.style.textAlign = 'center';
            if (ajaxcelldata.run){
                if (ajaxcelldata.avail>0){
                    // run has availability
                    calcell.avail.className = this._AC_AVAIL;                        
                    calcell.run = this._getRunObject(ajaxcelldata.run);
                    // add the mouse over/out Event handlers
                    function regMouseOverHandler(context,cell){
                	    return function(){context._mouseOver(cell)}
                    }
                    function regMouseOutHandler(context,cell){
                	    return function(){context._mouseOut(cell)}
                    }
                    function regClickHandler(context,cell){
                	    return function(){context._click(cell)}
                    }
                    calcell.mouseOverHandler = regMouseOverHandler(this,calcell);
                    calcell.mouseOutHandler = regMouseOutHandler(this,calcell);
                    calcell.clickHandler = regClickHandler(this,calcell);
                	if (calcell.avail.addEventListener){
                  		calcell.avail.addEventListener('mouseover', calcell.mouseOverHandler, false);
                  		calcell.avail.addEventListener('mouseout', calcell.mouseOutHandler, false);
                  		calcell.avail.addEventListener('click', calcell.clickHandler, false);
                	} else if (calcell.avail.attachEvent){
                  		calcell.avail.attachEvent('onmouseover', calcell.mouseOverHandler);
                  		calcell.avail.attachEvent('onmouseout', calcell.mouseOutHandler);
                  		calcell.avail.attachEvent('onclick', calcell.clickHandler);
                	}
                }
                else{
                    // run is full
                    this._setText(calcell.avail, 'Full');
                    calcell.avail.className = this._AC_FULL;
                } // if (ajaxcelldata.avail>0)                
            } // end if (ajaxcelldata.run)   
            if (this.smallCell){
                calcell.cell.className = this._AC_CELLDEFAULT+' '+calcell.cell.className;
            }
            
            
            
        } // end for
        this._putElement(this.bdiv, this.caltable);
        //this.bdiv.appendChild(this.popupdiv);
        // adjust the container heights
        this.bdiv.style.height = this.caltable.scrollHeight+'px';
        this.cdiv.style.height = (this.headerheight+this.caltable.scrollHeight)+'px';
        // adjust the position of the help button based on current rendering
        this.helpbutton.style.left = (this.cdiv.clientWidth-this.helpbutton.offsetWidth)+'px';        
        this.helpbutton.style.visibility = 'visible';        
    }
    /* ------------ end private method _updateCalendar ---------------*/
    /* -----------------------------------------------------------------
        Private method to create a calendar cell
        parameters:
            none
        Returns an object with the following properties (* properties are optional)
            cell: the outside td element
            smallCell: true if the small version
            txt: the td element holding the calendar text            
            avail: the td for the run availability
            run:* the ajax object with the run availability, set by _updateCalendar  
            mouseOverHandler:* event handler, set by _updateCalendar  
            mouseOouHandler:* event handler, set by _updateCalendar  
    */
    this._createCalCell = function(){

        var mytd =  document.createElement('td');
        mytd.className = this._AC_CELLDEFAULT;
    	var myobj = {smallCell:this.smallCell};
        if (this.smallCell){
            mytd.style.width = this._CELL_SIZE+'px';
            mytd.style.height = this._CELL_SIZE+'px';
            mytd.innerHTML = '&nbsp;';
            
            // assemble the output object
            myobj.cell = mytd;
            myobj.txt = mytd;
            myobj.avail = mytd;
        }
        else{
            mytd.style.width = (this._CELL_SIZE * 2)+'px';
            mytd.style.height = (this._CELL_SIZE * 2)+'px';
            var mytable = document.createElement('table');
            mytable.className = this._AC_INNERTABLE;
            var mytbody = document.createElement('tbody');
            mytable.appendChild(mytbody);
            mytable.style.width = '100%';
            mytable.style.height = '100%';
            mytable.style.borderCollapse = 'collapse';
            var myrow1 = document.createElement('tr');
            var myrow2 = document.createElement('tr');
            var mycell1 = document.createElement('td');
            mycell1.style.width  = this._CELL_SIZE+'px';
            mycell1.style.height = this._CELL_SIZE+'px';
            mycell1.innerHTML = '&nbsp;';
            var mycell2 = mycell1.cloneNode(true);
            var mycell3 = mycell1.cloneNode(true);
            var mycell4 = mycell1.cloneNode(true);
            mycell1.className = this._AC_TOPLEFT;
            mycell2.className = this._AC_TOPRIGHT;
            mycell3.className = this._AC_BOTTOMLEFT;
            mycell4.className = this._AC_BOTTOMRIGHT;
            // assemble the table
            myrow1.appendChild(mycell1);
            myrow1.appendChild(mycell2);
            myrow2.appendChild(mycell3);
            myrow2.appendChild(mycell4);
            mytbody.appendChild(myrow1);
            mytbody.appendChild(myrow2);
            mytd.appendChild(mytable);

            // assemble the output object
            myobj.cell = mytd;
            myobj.txt = mycell4;
            myobj.avail = mycell1;
        }
    	return myobj;
    }
    /*---------- end private method createCalCell ----------------*/
    /* -----------------------------------------------------------------
        Private method to reset the defaults of a calendar cell
        parameters:
            calCell object to be reset (see _createCalCell for definition)
        Returns nothing
    */
    this._resetCalCell = function(myobj){

        myobj.cell.className = this._AC_CELLDEFAULT;
        if (myobj.smallCell){
            myobj.cell.style.width = this._CELL_SIZE+'px';
            myobj.cell.style.height = this._CELL_SIZE+'px';
            myobj.cell.innerHTML = '&nbsp;';
        }
        else{
            myobj.cell.style.width = (this._CELL_SIZE * 2)+'px';
            myobj.cell.style.height = (this._CELL_SIZE * 2)+'px';
            myobj.txt.innerHTML = '&nbsp;';
            myobj.avail.innerHTML = '&nbsp;';
            myobj.avail.className = this._AC_TOPLEFT;
            myobj.txt.className = this._AC_BOTTOMRIGHT;
        }
        // remove any mouse handlers
        if (myobj.mouseOverHandler){
        	if (myobj.avail.removeEventListener){
          		myobj.avail.removeEventListener('mouseover', myobj.mouseOverHandler, false);
          		myobj.avail.removeEventListener('mouseout', myobj.mouseOutHandler, false);
          		myobj.avail.removeEventListener('click', myobj.clickHandler, false);
        	} else if (myobj.avail.detachEvent){
          		myobj.avail.detachEvent('onmouseover', myobj.mouseOverHandler);
          		myobj.avail.detachEvent('onmouseout', myobj.mouseOutHandler);
          		myobj.avail.detachEvent('onclick', myobj.clickHandler);
        	}
            myobj.mouseOverHandler = null; 
            myobj.mouseOutHandler = null;
        }
    	return ;
    }
    /*---------- end private method createCalCell ----------------*/
    
    /*---------------------------------------------------------------
        Private method for browser independent set text
        parameters:
            elem: Element to a apply text to
            txt: text
    */
    this._setText = function(elem, txt){
        if (elem.innerText != undefined){
            elem.innerText = txt;
        }
        else
        if (elem.textContent != undefined){
            elem.textContent = txt;
        }
    }
    /*---------- end private method _setText ----------------*/
    
    /*---------------------------------------------------------------
        Private method to put an element as the only child in a target element
        parameters:
            target: target element
            elem: Element to put
    */
    this._putElement = function(target, elem){
        // Remove any existing stuff from the target div
        while (target.firstChild) {
            target.removeChild(target.firstChild);
        }
        target.appendChild(elem);
    }
    /*---------- end private method _putElement ----------------*/

    /*---------------------------------------------------------------
        Private method to build a select element for 24 months from today
        parameters:
            mth: selected month yyyy-mm-dd
    */
    this._getMonthSelector = function(mth){
        if (!mth) mth = this.calMonth;
        var mths = ['January','February','March','April','May','June','July','August','September','October','November','December'];
        var parts = mth.split("-");
        var dte = new Date();
        var m = dte.getMonth();
        var y = dte.getFullYear();
        var objs = Array();
        for (var i=0;i<24;i++){
            var obj = {};
            obj.mth = '00'+(m+1);
            obj.mth = obj.mth.substr(obj.mth.length-2);
            obj.mthname = mths[m];
            obj.year = y;
            if ((parts[0]==y)&&((parts[1]-1)==m)) obj.selected = true;
            m++;
            if (m>11){
                m = 0;
                y++;
            }
            objs.push(obj);
        }
        this.selectDate = document.createElement('select');
        this.selectDate.className = this._AC_SELECT;
        for (var i=0; i < objs.length; i++){
            var myoption = document.createElement('option');
            myoption.innerHTML = objs[i].mthname+' '+objs[i].year;
            myoption.setAttribute('value',objs[i].year+'-'+objs[i].mth+'-01');
            if (objs[i].selected) myoption.setAttribute('selected','selected');
            this.selectDate.appendChild(myoption);
        }
        
        // set up an event handler for date change
        function regSelectDateHandler(context,selector){
    	    return function(){context._selectDate(selector)}
        }
    	// register the event handler for the change event
    	if (this.selectDate.addEventListener){
      		this.selectDate.addEventListener('change', regSelectDateHandler(this,this.selectDate), false);
    	} else if (this.selectDate.attachEvent){
      		this.selectDate.attachEvent('onchange', regSelectDateHandler(this,this.selectDate));
    	}
        return this.selectDate;
    }
    /*---------- end private method _getMonthSelector ----------------*/

    /*---------------------------------------------------------------
        Private method to handle a date change request
        parameters:
            selector: DOM select element causing the event
    */
    this._selectDate = function(selector){
        this.calMonth = selector.value;
        this.refresh();        
    }
    /*---------- end private method _selectDate ----------------*/

    /*---------------------------------------------------------------
        Private method to display a message when ajax fails
        parameters:
            ajaxObj: response object from myAjax
    */
    this._showErrorMessage = function(ajaxObj){
        var emsg;
        if (ajaxObj.dataError){
            emsg = this._EMSG_DATA_ERROR;
        }
        else if ((ajaxObj.htmlStatus+'').substr(0,1)=='4'){
            emsg = this._EMSG_PROXY_INSTALL_ERROR;
        }
        else if ((ajaxObj.htmlStatus+'').substr(0,1)=='5'){
            emsg = this._EMSG_SERVICE_UNAVAILABLE;
        }
        else{
            emsg = this._EMSG_UNEXPECTED_STATUS+ajaxObj.htmlStatus;
            emsg += '\n'+ajaxObj.htmlData;
        }
        this._setText(this.loadingtd,emsg);
    }
    /*---------- end private method _showErrorMessage ----------------*/

    /*---------------------------------------------------------------
        Private method to contruct the popup table
        parameters:
            calcell: calcell object, see _createCalCell for description 
        returns
            nothing
    */
    this._createPopup = function(calcell){
        var runobj = calcell.run;
        
        var mytable = document.createElement('table');
        //mytable.className = this._AC_POPUP;
        mytable.style.borderCollapse = 'collapse';
        var mytbody = document.createElement('tbody');
        mytable.appendChild(mytbody);
        
        // Header row
        var myrow = document.createElement('tr');
        var mytd = document.createElement('td');
        mytd.colSpan = '3';
        mytd.className = this._AC_POPUPHEADER;
        this._setText(mytd, this.vesselName);
        myrow.appendChild(mytd);
        mytbody.appendChild(myrow);
        
        // departure date row
        var myrow = document.createElement('tr');
        var mytd = document.createElement('td');
        mytd.colSpan = '3';
        mytd.className = this._AC_POPUPHEADER;
        this._setText(mytd, runobj.depart);
        myrow.appendChild(mytd);
        mytbody.appendChild(myrow);
        
        // cell header row
        var myrow = document.createElement('tr');
        var mytd = document.createElement('td');
        mytd.style.textAlign = 'left';
        mytd.className = this._AC_POPUPCELLHEADER;
        this._setText(mytd, 'Cabin/Berth Class');
        myrow.appendChild(mytd);
        var mytd = document.createElement('td');
        mytd.style.textAlign = 'center';
        mytd.className = this._AC_POPUPCELLHEADER;
        this._setText(mytd, 'Price pp');
        myrow.appendChild(mytd);
        var mytd = document.createElement('td');
        mytd.style.textAlign = 'center';
        mytd.className = this._AC_POPUPCELLHEADER;
        this._setText(mytd, 'Avail Pax');
        myrow.appendChild(mytd);
        mytbody.appendChild(myrow);
        
        // berth class rows
        for (var i=0; i < runobj.bclass.length; i++){
            var bc = runobj.bclass[i];
            var myrow = document.createElement('tr');
            var mytd = document.createElement('td');
            mytd.style.textAlign = 'left';
            mytd.className = this._AC_POPUPCELL;
            this._setText(mytd, bc.bcname);
            myrow.appendChild(mytd);
            var mytd = document.createElement('td');
            mytd.style.textAlign = 'center';
            mytd.className = this._AC_POPUPCELL;
            if (this.couponCode){
                this._setText(mytd, 'Special');
            }
            else if ((!this.disableDefaultCouponCode)&&(this.useDefaultCoupon)){
                this._setText(mytd, 'Special');
            }
            else if ((!this.disableShowPriceReduction)&&(bc.showReducedPrice)&&(bc.paxOrgCost > bc.price)){
                mytd.innerHTML = '<span style="text-decoration:line-through">'+bc.paxOrgCost+'</span>'+bc.price;
            }
            else{
                this._setText(mytd, bc.price);
            }
            myrow.appendChild(mytd);
            var mytd = document.createElement('td');
            mytd.style.textAlign = 'center';
            mytd.className = this._AC_POPUPCELL;
            this._setText(mytd, bc.avail);
            myrow.appendChild(mytd);
            mytbody.appendChild(myrow);            
        }
        // Footer
        var myrow = document.createElement('tr');
        var mytd = document.createElement('td');
        mytd.colSpan = '3';
        mytd.className = this._AC_POPUPCELL;
        mytd.style.textAlign = 'center';
        this._setText(mytd, 'Click to make a Booking');
        myrow.appendChild(mytd);
        mytbody.appendChild(myrow);
        
        // wrap the table in a div
        // we need to do this because the class won't work on this.popupdiv (can't work out why)
        var mydiv = document.createElement('div');
        mydiv.className = this._AC_POPUP;
        mydiv.appendChild(mytable);
        
        // replace the old div
        var olddiv = this.popupdiv.getElementsByTagName('div')[0];
        this.popupdiv.replaceChild(mydiv,olddiv);
        this._destroy(olddiv);
    }
    /*---------- end private method _createPopup ----------------*/
    
    /*---------------------------------------------------------------
        Private method to destroy a DOM element and all child elements
        Hopefully (not tested) this frees the memory
        This is a recursive method
        parameters:
            target: the DOM element to be destroyed
        returns
            nothing
    */
    this._destroy = function(target){
        while (target.firstChild) {
            var child = target.removeChild(target.firstChild);
            this._destroy(child);
            child = null;
        }
    }
    /*---------- end private method _destroy ----------------*/
   
    /*---------------------------------------------------------------
        Private method to handle a help click
    */
    this._helpClick = function(){
        if (this.helpdiv.style.visibility!='hidden'){
            this.helpdiv.style.visibility = 'hidden';
            return;
        }
        var html = this._HELP_HTML.replace(/#vessel#/g, this.vesselName);
        html = html.replace(/#month#/g, this.selectDate.options[this.selectDate.selectedIndex].text);
        this.helpdiv.innerHTML = html;
        var hdivPos = new this._findPos(this.hdiv);
        var left = ((this.smallCell?this._HELPDIV_OFFSETLEFT_SMALL:this._HELPDIV_OFFSETLEFT) + hdivPos.left);
        var top = (this._HELPDIV_OFFSETTOP + hdivPos.top);
        if (this.helpdivOffsetLeft) left += this.helpdivOffsetLeft;
        if (this.helpdivOffsetTop) top += this.helpdivOffsetTop;
        this.helpdiv.style.left = (left)+'px';
        this.helpdiv.style.top = (top)+'px';
        this.helpdiv.style.visibility = 'visible';
    }
    /*---------- end private method _helpClick ----------------*/
   
    /*---------------------------------------------------------------
        Private method to handle a help mouse out
    */
    this._helpMouseOut = function(){
        this.helpdiv.style.visibility = 'hidden';
    }
    /*---------- end private method _helpMouseOut ----------------*/
   
    
//=============================================================================
    // Initialization (construction) code 
    
    // Save the parameters    
    this.siteName = siteName;
    this.smallCell = false; // default is large
    if ((smallCell)&&(typeof(smallCell)=='string')){
        if ((smallCell.substr(0,1)=="S")||(smallCell.substr(0,1)=="s")){
            this.smallCell = true;
        }
    }
    this.vesselName = vesselName;
    if (!this.setMonth(calMonth)){
        // default to this month if there is not a valid month specified
        var dte = new Date();
        calMonth = dte.getFullYear()+"-"+(dte.getMonth()+1)+"-01";
        this.setMonth(calMonth);
    }
    this.div = document.getElementById(div_id);
    if ((!this.div)||(this.div.tagName.toUpperCase()!='DIV')){
        alert('Parameter 2 is not a valid DIV id');
        return;
    }
    
    // set the coupon defaults    
    this.disableDefaultCouponCode = false;
    this.useDefaultCoupon = false;
    this.defaultCouponCode = "";
    this.couponCode = "";
    this.disableShowPriceReduction = false;
        
    // set the sizes to use (depends on smallCell)
    this.calcellsize = this._CELL_SIZE * (this.smallCell?1:2);
    this.calwidth = this.calcellsize * this._COLS;
    this.calheight = this.calcellsize * this._ROWS + this._DAYNAMES_HEIGHT; // 20 is day names header
    this.headerheight = this.smallCell?(this._DAYNAMES_HEIGHT * 2):this._CELL_SIZE;
    this.containerheight = this.calheight + this.headerheight;
    // some of these are updated in _updateCalendar

    // Start the ajax
    this.ajax = new this._myAjax(this._regCallBack(this));

    // Construct the default display message    
    this.defaultdiv = document.createElement('div'); // container 
    this.defaultdiv.style.height = this.containerheight+'px';
    this.defaultdiv.style.width = this.calwidth+'px';
    this.defaultdiv.innerHTML = '<table style="width:100%;height:100%"><tr><td style="width:100%;height:100%"><td></tr></table>';
    this.defaulttd = this.defaultdiv.getElementsByTagName('td')[0];
    this.defaulttd.className = this._AC_EMPTYDIV;
    this._setText(this.defaulttd,this._MSG_EMPTY);
    
    // display the default message
    this._putElement(this.div, this.defaultdiv);
    
    // Construct the loading display message    
    this.loadingdiv = document.createElement('div'); // container 
    this.loadingdiv.style.width = this.calwidth+'px';
    this.loadingdiv.style.height = this.calheight+'px';
    this.loadingdiv.innerHTML = '<table style="width:100%;height:100%"><tr><td style="width:100%;height:100%"><td></tr></table>';
    this.loadingtd = this.loadingdiv.getElementsByTagName('td')[0];
    this.loadingtd.className = this._AC_LOADINGDIV;
    this._setText(this.loadingtd,this._MSG_LOADING);
    
    // Construct the container divs
    this.cdiv = document.createElement('div'); // calendar container 
    this.cdiv.className = this._AC_DEFAULT;
    this.cdiv.style.position = 'relative'; // this is the base for all our positioning
    //this.cdiv.id = "cdiv";
    this.cdiv.style.width = this.calwidth+'px';
    this.cdiv.style.height = this.containerheight+'px';

    this.hdiv = document.createElement('div'); // header
    //this.hdiv.id = "hdiv";
    this.hdiv.style.width = this.calwidth+'px';
    this.hdiv.style.height = this.headerheight+'px';
    //this.hdiv.style.position = 'relative'; // so we can position the help button
    
    this.bdiv = document.createElement('div'); // body
    //this.bdiv.id = "bdiv";
    this.bdiv.style.width = this.calwidth+'px';
    this.bdiv.style.height = this.calheight+'px';
    this.cdiv.appendChild(this.hdiv);
    this.cdiv.appendChild(this.bdiv);

    // cells for the header items
    this.namecell = document.createElement('td');
    this.namecell.className = this._AC_HEADERCELL+' '+this._AC_HEADERNAME;
    if (this.vesselName) this._setText(this.namecell,this.vesselName);
    this.mthcell = document.createElement('td');
    this.mthcell.className = this._AC_HEADERCELL+' '+this._AC_HEADERMONTH;
    this.mthcell.appendChild(this._getMonthSelector(this.calMonth));
    
    // Build the header table
    var htable = document.createElement('table');
    var htbody = document.createElement('tbody');
    htable.appendChild(htbody);
    htable.style.width = '100%';
    htable.style.height = '100%';
    htable.style.borderCollapse = 'collapse';
    var hrow1 = document.createElement('tr');
    var hrow2 = document.createElement('tr');
    if (smallCell){
        hrow1.appendChild(this.namecell);
        hrow2.appendChild(this.mthcell);
        htbody.appendChild(hrow1);
        htbody.appendChild(hrow2);
    }
    else{
        hrow1.appendChild(this.namecell);
        hrow1.appendChild(this.mthcell);
        htbody.appendChild(hrow1);
    }
    // add to the header container
    this.hdiv.appendChild(htable);
    
    // Build the calendar body
    // This is accessable through an array of calcell objects
    this.calcells = Array();
    for(var i=0;i<(this._ROWS*this._COLS);i++){
        this.calcells.push(this._createCalCell());        
    }

    // assemble the calendar table
    this.caltable = document.createElement('table');
    this.caltable.className = this._AC_CALTABLE;
    var caltbody = document.createElement('tbody');
    this.caltable.appendChild(caltbody);
    this.caltable.style.width = '100%';
    this.caltable.style.height = '100%';
    this.caltable.style.borderCollapse = 'collapse';
    // add the day header row
    var dayheaderrow = document.createElement('tr');
    var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
	for(var i=0; i<days.length; i++){
        var mytd = document.createElement('td');
        mytd.className = this._AC_DAYHEADER;
        mytd.style.height = '20px';
        this._setText(mytd,days[i].substr(0,(this.smallCell?1:3)));
        dayheaderrow.appendChild(mytd);
    }
    caltbody.appendChild(dayheaderrow);

    // create and add the rows
    var myrows = Array();
    for(var i=0;i<this._ROWS;i++){
        myrows[i] = document.createElement('tr');
        caltbody.appendChild(myrows[i]);
        // cells in the row
        for(var j=0;j<this._COLS;j++){
            myrows[i].appendChild(this.calcells[(i*this._COLS)+j].cell);
        }
    }
    
    // help button
    this.helpbutton = document.createElement('div');
    this.helpbutton.className = this._AC_HELPBUTTON;
    this.helpbutton.style.position = 'absolute';
    this.helpbutton.style.zIndex = '2';
    this.helpbutton.style.visibility = 'hidden'; // hide for now
    this._setText(this.helpbutton, '?');
    this.helpbutton.title = 'Click for Help';
    this.hdiv.appendChild(this.helpbutton);
    
    // position the help button
    this.helpbutton.style.top = '0px';
    this.helpbutton.style.left = (this.calwidth-12)+'px';
    
    // add the event handlers
    function regHelpClickHandler(context){
	    return function(){context._helpClick()}
    }
    function regHelpMouseOutHandler(context){
	    return function(){context._helpMouseOut()}
    }
    this.helpClickHandler = regHelpClickHandler(this);
    this.helpMouseOutHandler = regHelpMouseOutHandler(this);
	if (this.helpbutton.addEventListener){
  		this.helpbutton.addEventListener('click', this.helpClickHandler, false);
  		this.helpbutton.addEventListener('mouseout', this.helpMouseOutHandler, false);
	} else if (this.helpbutton.attachEvent){
  		this.helpbutton.attachEvent('onclick', this.helpClickHandler);
  		this.helpbutton.attachEvent('onmouseout', this.helpMouseOutHandler);
	}
    
    // create the popup div  - _createPopup method will build the table in the div
    this.popupdiv = document.createElement('div');
    //this.popupdiv.className = this._AC_POPUP;
    this.popupdiv.style.visibility = 'hidden';
    this.popupdiv.style.position = 'absolute';
    this.popupdiv.style.left = '0px'
    this.popupdiv.style.top = '0px'
    this.popupdiv.style.zIndex = '1050';
    this.popupdiv.id = div_id; // this is so the style can be set using the id as a selector
    // the dummy table is required by the _createPopup method
    var dummytable = document.createElement('div');
    this.popupdiv.appendChild(dummytable);
    //document.body.appendChild(this.popupdiv);
    
    // create the help div
    this.helpdiv = document.createElement('div');
    this.helpdiv.className = this._AC_HELPDIV;
    this.helpdiv.style.position = 'absolute';
    this.helpdiv.style.zIndex = '1000';
    this.helpdiv.style.visibility = 'hidden';
    this.helpdiv.style.left = '0px'
    this.helpdiv.style.top = '0px'
    this.helpdiv.innerHTML = this._HELP_HTML;
    this.helpdiv.id = div_id; // this is so the style can be set using the id as a selector
    //document.body.appendChild(this.helpdiv);

    // this bit of code is to fix a bug in IE6 & IE7
    // make any scrolling elements postion relative
    if ((navigator.userAgent.toLowerCase().indexOf('msie 6') != -1)
    	|| (navigator.userAgent.toLowerCase().indexOf('msie 7') != -1)){
        var orig = this.div;
    	if (orig.parentNode) {
        	do {
        	    if (orig == document.body) break;
        		if ((orig.currentStyle.overflow=='scroll')||(orig.currentStyle.overflow=='auto')){
        		    if (orig.currentStyle.position!='absolute') orig.style.position = 'relative';
        		}
            } while (orig = orig.parentNode);
	    }            
    }

    // add an event handler to add our popups to the body when it has finished loading
    function regBodyLoadHandler(context){
	    return function(){context._bodyLoad()}
    }
    this.bodyLoadHandler = regBodyLoadHandler(this);
	if (window.addEventListener){
  		window.addEventListener('load', this.bodyLoadHandler, false);
	} else if (window.attachEvent){
  		window.attachEvent('onload', this.bodyLoadHandler);
	}
    this._bodyLoad = function(){
        document.body.appendChild(this.helpdiv);
        document.body.appendChild(this.popupdiv);
    }
    
    // if there is no vessel name supplied we're finished
    if (!this.vesselName) return;

    this.refresh();    

    return;
}

