function Currency(isoCode, symbol, name, shortName, relativeRate) {
    this.isoCode      = isoCode;
    this.symbol       = symbol;
    this.name         = name;
    this.shortName    = shortName;
    this.relativeRate = relativeRate;
};

/**
 * A 'class' representing generic currency conversion functionality for deal matrices.
 *
 * Follows a convention based approach for identifying cells that require conversion.
 * Rather than a single synchronous conversion, the approach taken is to split the work required up into
 * logical sections (known henceforth as 'deal blocks') and returning control to the browser momentarily between
 * deal block conversions.
 *
 * The convention is as follows:
 *
 *           _________________________________________________________
 *       ___|______________________________________________________   |
 *   ___|_______________________________________________________   |  |
 *  |                                                           |  |  |
 *  | div: id=currencyGroup                                     |  |  |
 *  |      name=currencyGroup                                   |  |  |
 *  |      isoCode=CODE                                         |  |  |
 *  |      index=indexNumber                                    |  |  |
 *  |                                                           |  |  |
 *  |    ____________________________                           |  |  |
 *  |   | div: name=dealBlock        |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   |____________________________|                          |  |  |
 *  |    ____________________________                           |  |  |
 *  |   | div: name=dealBlock        |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   | span, name=dealRateCell    |                          |  |  |
 *  |   |                            |                          |  |__|
 *  |   |____________________________|                          |__|
 *  |___________________________________________________________|
 *
 *
 * Blocks that relate to the same currency are encapsulated in DIV elements with an ID of 'currencyGroup'
 * and a name of 'currencyGroup'. Cells within a 'currencyGroup' are further subdivided into logical units known
 * as 'deal blocks'. These are denoted by DIV elements with a name of 'dealBlock'. Each 'deal block' is processed
 * synchronously. After processing a deal block, the next block is scheduled for processing at a later stage
 * (denoted by the TIME_BETWEEN_DEAL_BLOCKS parameter defined below). This approach is taken so that control can be
 * returned to the browser between conversions, allowing continual response to the user for large pages.
 *
 * Each rate cell within a deal block is expected to contain semi-structured data:
 *
 *   ---> <span name="dealRateCell">[currencySymbol]RATE</span>
 *  (the currencySymbol is optional, but cannot contain digits).
 *
 * To provide further flexibility, users of this class can schedule callbacks for various phases in the lifecycle:
 *
 * before each currency group: addPreCurrencyGroupCallback(function(...) { ... } );
 *
 * after each currency group: addPostCurrencyGroupCallback(function(...) { ... } );
 *
 * before performing cell conversions on each deal block: addPreDealBlockCallback(function(...) { ... } );
 *
 * after performing cell conversions on each deal block: addPostDealBlockCallback(function(...) { ... } );
 *
 * after entire conversion: addPostConversionCallback(function() { ... } );
 *
 * These callbacks can be useful in performing various random tasks (i.e. updating messages pertaining to currency
 * codes).
 *
 */
function CurrencyConverter() {
    this.postConversionCallbacks    = new Array();
    this.preCurrencyGroupCallbacks  = new Array();
    this.postCurrencyGroupCallbacks = new Array();
    this.preDealBlockCallbacks      = new Array();
    this.postDealBlockCallbacks     = new Array();

    this.preDealBlockCallbacks.push(fullRateBackgroundChanger);
    this.preDealBlockCallbacks.push(emptyFullRateChanger);
    this.postDealBlockCallbacks.push(unhighlightAllSupplierRows);
    this.postConversionCallbacks.push(fixFontSize);
    this.postConversionCallbacks.push(function() {if (window.hideTooltip) { window.hideTooltip(); }});

    this.currentTimeout = null;
    this.lastCalendar = null;
};

// DEFINE 'CLASS' level constants.
CurrencyConverter.prototype.DEAL_RATE_REGEX           = /name="?dealRateCell?"?[^>]*>([^\d]*?)(\d+)(:?.|\n)*?<\/[Ss]/g;
CurrencyConverter.prototype.TIME_BETWEEN_DEAL_BLOCKS  = 20;
CurrencyConverter.prototype.CONVERSION_TYPE_SINGLE    = 1;
CurrencyConverter.prototype.CONVERSION_TYPE_MULTI     = 2;
CurrencyConverter.prototype.CURRENCY_CODE_ATTR        = 'isoCode';
CurrencyConverter.prototype.CURRENCY_GROUP_NAME       = 'currencyGroup';
CurrencyConverter.prototype.DEAL_BLOCK_NAME           = 'dealBlock';

/**
 * Specify that the given function should be called when all conversions are complete.
 */
CurrencyConverter.prototype.addPostConversionCallback = function(cb) {
    this.postConversionCallbacks.push(cb);
};

/**
 * Specify that the given function should be called before applying conversions to a currencyGroup DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'currencyGroup' element
 * fromCurrency   --> a Currency object representing the default currency for the group
 * toCurrency     --> a Currency object representing the indicative currency for the group
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 * converter      --> the converter performing the callbacks
 */
CurrencyConverter.prototype.addPreCurrencyGroupCallback = function(cb) {
    this.preCurrencyGroupCallbacks.push(cb);
};

/**
 * Specify that the given function should be called after applying conversions to a currencyGroup DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'currencyGroup' element
 * fromCurrency   --> a Currency object representing the default currency for the group
 * toCurrency     --> a Currency object representing the indicative currency for the group
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 * converter      --> the converter performing the callbacks
 */
CurrencyConverter.prototype.addPostCurrencyGroupCallback = function(cb) {
    this.postCurrencyGroupCallbacks.push(cb);
};


/**
 * Specify that the given function should be called before applying conversions to a deal block DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'dealBlock' element
 * fromCurrency   --> a Currency object representing the default currency for the current currency group
 * toCurrency     --> a Currency object representing the indicative currency
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 * converter      --> the converter performing the callbacks
 */
CurrencyConverter.prototype.addPreDealBlockCallback = function(cb) {
    this.preDealBlockCallbacks.push(cb);
};

/**
 * Specify that the given function should be called after applying conversions to a deal block DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'dealBlock' element
 * fromCurrency   --> a Currency object representing the default currency for the current currency group
 * toCurrency     --> a Currency object representing the indicative currency
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 * converter      --> the converter performing the callbacks
 */
CurrencyConverter.prototype.addPostDealBlockCallback = function(cb) {
    this.postDealBlockCallbacks.push(cb);
};

/**
 * Perform a currency conversion:
 *
 * currencies is a mapping of isoCode --> Currency objects.
 *
 * indicativeCurrency is the ISO code of the currency to convert to.
 */
CurrencyConverter.prototype.convert = function(currencies, indicativeCurrency) {
    if (this.currentTimeout != null) {
        window.clearTimeout(this.currentTimeout);
    }
    var scopedThis          = this;
    this.turningOff         = false;
    this.longRatePresent    = false;
    this.currencies         = new CloneCurrencyList(currencies);
    this.indicativeCurrency = this.currencies[new String(indicativeCurrency)];
    this.currencyGroups     = this.findCurrencyGroups();
    this.currencyGroupNum   = 0;
    createCookie("indicativeCurrencyCode", indicativeCurrency);
    this.currentTimeout = window.setTimeout(function () { scopedThis.convertCurrencyGroup(); }, 1);
};

/**
    This function creates session cookie.
 */
function createCookie(cookieName, cookieValue) {
        document.cookie = cookieName + "=" + cookieValue + ";" + "path=" + "/" ;
};

function getRates(indicativeCurrencyCode) {
    new Ajax.Request('/currency/CreateCurrency', {
     method:'get',
     onSuccess: function(response) {
         var result = eval("(" + response.responseText + ")");
         currencyConverter.convert(result.currencies, indicativeCurrencyCode);
     }
   })
};

/**
 * Turn off any conversions that are currently applied.
 */
CurrencyConverter.prototype.turnOff = function() {
    if (this.currentTimeout != null) {
        window.clearTimeout(this.currentTimeout);
    }
    var scopedThis          = this;
    this.longRatePresent    = false;
    this.turningOff         = true;
    this.indicativeCurrency = null;
    this.currencyGroups     = this.findCurrencyGroups();
    this.currencyGroupNum   = 0;
    unsetSessionCookie("indicativeCurrencyCode");
    if(this.currencies) {
        /* Dont need to turn off in this case as nothing was turned on */
        this.currentTimeout = window.setTimeout(function () { scopedThis.convertCurrencyGroup(); }, 1);
    }
};

function unsetSessionCookie(cookieName) {
    var curCookie = cookieName + "=" + escape(false) +
      ("; expires=" + "Thu, 01-Jan-1970 00:00:01 GMT") +
      ("; path=" + "/");
  document.cookie = curCookie;
}

/**
 * Internal function for converting a currency group.
 */
CurrencyConverter.prototype.convertCurrencyGroup = function() {
    if (this.currencyGroupNum < this.currencyGroups.length) {
        var scopedThis       = this;
        var currencyGroup    = this.currencyGroups.item(this.currencyGroupNum++);
        this.currencyGroup   = currencyGroup;
        this.defaultCurrency = this.currencies[currencyGroup.getAttribute(this.CURRENCY_CODE_ATTR)];
        this.conversionType  = this.determineConversionType();
        this.conversionFunc  = this.determineConversionFunc();
        this.dealBlocks      = this.findDealBlocks(currencyGroup);
        this.dealBlockNum    = 0;
        this.fireCallbacks(currencyGroup, this.preCurrencyGroupCallbacks);
        this.currentTimeout = window.setTimeout(function() { scopedThis.convertDealBlock(); }, 1);
    } else {
        for (var callbackIndex = 0; callbackIndex < this.postConversionCallbacks.length; callbackIndex++) {
            this.postConversionCallbacks[callbackIndex]();
        }
    }
};

/**
 * Internal function for converting a deal block.
 */
CurrencyConverter.prototype.convertDealBlock = function() {
    var scopedThis = this;
    if (this.dealBlockNum < this.dealBlocks.length) {
        // change this deal block and schedule the next one
        var dealBlock = this.dealBlocks[this.dealBlockNum++];
        this.fireCallbacks(dealBlock, this.preDealBlockCallbacks);
        dealBlock.innerHTML = dealBlock.innerHTML.replace(this.DEAL_RATE_REGEX, function (match, symbol, rate) {
            return scopedThis.conversionFunc(match, symbol, rate);
        });
        this.fireCallbacks(dealBlock, this.postDealBlockCallbacks);

        this.currentTimeout = window.setTimeout(function() { scopedThis.convertDealBlock(); }, this.TIME_BETWEEN_DEAL_BLOCKS);
    } else {
        // next currency block
        this.fireCallbacks(this.currencyGroup, this.postCurrencyGroupCallbacks);
        this.currentTimeout = window.setTimeout(function() { scopedThis.convertCurrencyGroup(); }, 1);
    }
};

/**
 * Rate cell conversion function for displaying a single rate.
 */
CurrencyConverter.prototype.showOneCurrency = function(match, symbol, rate) {
    if (rate.length + symbol.length >= 5) {
        this.longRatePresent = true;
    }
    return 'name="dealRateCell">' + symbol + rate + '</s';
};

/**
 * Rate cell conversion function for displaying the indicative rate as well as the default rate
 */
CurrencyConverter.prototype.showBothCurrencies = function(match, symbol, rate) {
    var indicativeSymbol = symbol == '' ? '' : this.indicativeCurrency.shortName;
    var amountInt = convertCurrency(rate, this.defaultCurrency.relativeRate, this.indicativeCurrency.relativeRate);
    if (rate.length + symbol.length >= 5 || amountInt.length + indicativeSymbol.length >=5) {
        this.longRatePresent = true;
    }
    return 'name="dealRateCell">' + symbol + rate + '<p class="convertedRate">' + indicativeSymbol + amountInt + '</p></s';
};

/**
 * Internal function for determining what type of conversion is being performed currently:
 * - single, or
 * - multi
 */
CurrencyConverter.prototype.determineConversionType = function() {
    if (this.turningOff || (this.indicativeCurrency.isoCode == this.defaultCurrency.isoCode)) {
        return this.CONVERSION_TYPE_SINGLE;
    }
    return this.CONVERSION_TYPE_MULTI;
};

/**
 * Internal function for determining which function to use for converting deal cells.
 */
CurrencyConverter.prototype.determineConversionFunc = function() {
    if (this.conversionType == this.CONVERSION_TYPE_SINGLE) {
        return this.showOneCurrency;
    } else {
        return this.showBothCurrencies;
    }
};

/**
 * Internal function for finding all currency groups.
 */
CurrencyConverter.prototype.findCurrencyGroups = function() {
    return document.getElementsByName(this.CURRENCY_GROUP_NAME);
};

/**
 * Internal function for finding all deal blocks inside a currency group.
 */
CurrencyConverter.prototype.findDealBlocks = function(currencyGroup) {
    var divs = currencyGroup.getElementsByTagName("div");
    var dealBlocks = new Array();
    for (var divNum = 0; divNum < divs.length; divNum++) {
        var div = divs.item(divNum);
        if (div.getAttribute('name') == this.DEAL_BLOCK_NAME) {
            dealBlocks.push(div);
        }
    }
    return dealBlocks;
};

/**
 * Internal function for calling all callbacks for a div based on the given callback array.
 */
CurrencyConverter.prototype.fireCallbacks = function(divElement, callbackArray) {
    for (var callbackIndex = 0; callbackIndex < callbackArray.length; callbackIndex++) {
        callbackArray[callbackIndex](divElement, this.defaultCurrency, this.indicativeCurrency, this.conversionType, this);
    }
};

/**
 * Internal post currency group callback function for unhighlighting all supplier rows.
 */
function unhighlightAllSupplierRows(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
    var rows = groupDiv.getElementsByTagName("tr");
    for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
        unhighlightSupplierRow(rows.item(rowIndex));
    }
};

/**
 * Internal pre currency group callback function to alter the background colour of full-rate cells.
 */
function fullRateBackgroundChanger(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
    if (conversionType == CurrencyConverter.prototype.CONVERSION_TYPE_MULTI) {
        switchClasses(groupDiv, "td", "fullrate", "fullrateChanged");
    } else {
        switchClasses(groupDiv, "td", "fullrateChanged", "fullrate");
    }
};

/**
 * Internal pre currency group callback function to save the calendar widget.
 */
function preserveCalendar(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
	// We look for wotifDateTextBox here and append it as the last node in the next method because IE has issues with
	// finding the last node as it considers white space to be text elements.
	// If the calendar widget ever becomes anything other than the last element this will need changing.
	var calendars = Element.getElementsByClassName(groupDiv, 'wotifDateTextBox');
    if (calendars.length > 0) {
    	var parent = calendars[0].parentNode;
    	converter.lastCalendar = parent.removeChild(calendars[0]);
    }
};

/**
 * Internal pre currency group callback function to restore the calendar widget.
 */
function restoreCalendar(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
    var parent = Element.getElementsByClassName(groupDiv, 'nowshowing');
    if (parent.length > 0) {
    	parent = parent[0];
    	parent.appendChild(converter.lastCalendar);
    }
};

function emptyFullRateChanger(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
    if (conversionType == CurrencyConverter.prototype.CONVERSION_TYPE_MULTI) {
        var spanNodes = groupDiv.getElementsByTagName("span");
        for (var spanIndex = 0; spanIndex < spanNodes.length; spanIndex++) {
            var spanNode = spanNodes.item(spanIndex);
            if (spanNode.getAttribute('name') == "emptyFullRate") {
                spanNode.innerHTML = groupCurrency.shortName + '--<p class="convertedRate">' + newCurrency.shortName + '--</p>';
            }
        }
    } else {
        var spanNodes = groupDiv.getElementsByTagName("span");
        for (var spanIndex = 0; spanIndex < spanNodes.length; spanIndex++) {
            var spanNode = spanNodes.item(spanIndex);
            if (spanNode.getAttribute('name') == "emptyFullRate") {
                spanNode.innerHTML = groupCurrency.shortName + '--';
            }
        }
    }
}

function fixFontSize() {
    if (currencyConverter.longRatePresent) {
        shrinkTextSizeBasedOnWidth();
    } else {
        unshrinkTextSize();
    }
}

/**
 * Switch the css classes for all 'elementName' elements under 'rootElement' that have a
 * className of 'fromClass' to 'toClass'.
 */
function switchClasses(rootElement, elementName, fromClass, toClass) {
    var childNodes = rootElement.getElementsByTagName(elementName);
    for (var childIndex = 0; childIndex < childNodes.length; childIndex++) {
        var childNode = childNodes.item(childIndex);
        if (childNode.className == fromClass) {
            childNode.className = toClass;
        }
    }
}

function CloneCurrencyList(source) {
    for (var field in source) {
        var c = source[field];
        this[new String(field)] = new Currency(
                new String(c.isoCode), new String(c.symbol), new String(c.name), new String(c.shortName), parseFloat(c.relativeRate));
    }
}

function convertCurrency(amount, fromRate, toRate) {
    return new String(parseInt((parseInt(amount) / toRate) * fromRate));
}

function convertFullRateTooltip(groupDiv, groupCurrency, newCurrency, conversionType, converter) {
    var tdElements = groupDiv.getElementsByTagName("td");
    for (var tdIndex = 0; tdIndex < tdElements.length; tdIndex++) {
        var tdElement = tdElements.item(tdIndex);
        if (tdElement.id.indexOf("fullrate") != -1) {
            var propId = tdElement.id.split("-")[1];
            if (conversionType == CurrencyConverter.prototype.CONVERSION_TYPE_MULTI) {
                FullRateTips[propId].convert(groupCurrency, newCurrency);
            } else {
                FullRateTips[propId].turnOffConversion(groupCurrency, newCurrency);
            }
        }
    }
}



function FullRateTooltip(roomTypes, currencyShortName, displayRackRate) {
    this.roomTypes         = roomTypes;
    this.currencyShortName = currencyShortName;
    this.displayRackRate   = displayRackRate;
    this.showTwoCurrencies = false;
};

FullRateTooltip.prototype.convert = function(defaultCurrency, indicativeCurrency) {
    this.defaultCurrency    = defaultCurrency;
    this.indicativeCurrency = indicativeCurrency;
    this.showTwoCurrencies  = true;
};

FullRateTooltip.prototype.turnOffConversion = function(defaultCurrency, indicativeCurrency) {
    this.defaultCurrency    = null;
    this.indicativeCurrency = null
    this.showTwoCurrencies  = false;
};

FullRateTooltip.prototype.render = function() {
    var htmlStr = "";

    if (this.showTwoCurrencies) {
        htmlStr += "<table class='tooltip multicurrency' border='0' cellspacing='0' cellpadding='0'>";
    } else {
        htmlStr += "<table class='tooltip' border='0' cellspacing='0' cellpadding='0'>";
    }

    if (!this.displayRackRate) {
        htmlStr += "<tr><th>This property does not display Full Rates</th></tr>";
        htmlStr += "<tr><th>Room types available</th></tr>";
    } else {
        htmlStr += "<tr>";
        htmlStr += "<th>Room Type</th>";
        htmlStr += "<th>Full Rate " + this.currencyShortName + "</th>";
        if (this.showTwoCurrencies) {
            htmlStr += "<th class='convertedRate'>Full Rate " + this.indicativeCurrency.shortName + "</th>";
        }
        htmlStr += "</tr>";
    }

    for (var roomTypeIndex = 0; roomTypeIndex < this.roomTypes.length; roomTypeIndex++) {
        var roomType = this.roomTypes[roomTypeIndex];
        htmlStr += "<tr><td>" + roomType.name + "</td><td>" + roomType.currencySymbol + roomType.fullRate + "</td>";
        if (this.showTwoCurrencies && this.displayRackRate) {
            htmlStr += "<td class='convertedRate'>" + this.indicativeCurrency.symbol + convertCurrency(roomType.fullRate, this.defaultCurrency.relativeRate, this.indicativeCurrency.relativeRate) + "</td>";
        }
        htmlStr += "</tr>";
    }

    htmlStr += "</table>";
    return htmlStr;
};

function RoomType(name, currencySymbol, fullRate) {
    this.name = name;
    this.currencySymbol = currencySymbol;
    this.fullRate = fullRate;
};

/**
 * Checks the browser window width.
 * If it's too narrow for 6-digit prices (incl. currency symobl) the page text-size is reduced.
 * Used on Search page, Hotel page and WotsOn page.
 */
function shrinkTextSizeBasedOnWidth() {
    var width = determineWindowWidth();
    if (width && width < 950) {
        document.body.id = "shrink";
    } else {
        // potentially the window has been resized, so we may need to 'unshrink' the text.
        unshrinkTextSize();
    }
}

function unshrinkTextSize() {
    if (document.body.id == "shrink") {
        document.body.id = "";
    }
}

function determineWindowWidth() {
    var width = window.innerWidth;
    if (!width) {
        width = document.body.offsetWidth;
    }
    return width;
}

var currencyPopupWindow;
// This method assumes that you have created a variable on the page as a result of calling window.open()
// eg currencyPopupWindow=window.open(.......);
function closeCurrencyPopup() {
    if (currencyPopupWindow) {
        currencyPopupWindow.close();
    }
}
