/**------------------------------------------------------------------------
 * Set of function and classes which deal with currency calculator
 *
 * Dependencies:
 *  dojo.js, version 0.9 
 *
 * Author: 
 *  Bartlomiej.Pawlowski@swx.com
 *
 * @version $Id: currencyCalc.js,v 1.2 2010/05/07 12:36:30 obo Exp $
 *
 *-----------------------------------------------------------------------*/

/**
 * CurrencyCalculator class 
 *
 * @param ratesTable - table which keeps the current currency rates
 * @param currTableId - id of the html table which displays currency rates when
 *        on currency should be converted into all currencies
 * @param messageElemId - id of the html element used to display messages
 * @param convertDateElemId - id of the html element used to display rate date 
 * @param convertResultElemId - id of the html element used to display converted currencies
 *        when one currency should be converted in to another, but not to all 
 * @param chfDispName - CHF currency display name
 * @param rateDate - rate as of date (Date)
 * @param userMessages (map) - keeps different messages displayed to the user
 * @param lang 
 */
function CurrencyCalculator(ratesTable, currencyIcons, currTableId, messageElemId, convertDateElemId, convertResultElemId, chfDispName, rateDate, userMessages, lang) {
    this.ratesTable         = ratesTable;
    this.currencyIcons      = currencyIcons;
    this.currTableId        = currTableId;
    this.messageElemId      = messageElemId;
    this.convertDateElemId  = convertDateElemId;
    this.convertResultElemId  = convertResultElemId;
    this.chfDispName        = chfDispName;
   
    // rate as of
    this.rateDate           = _getOnlyDate(rateDate);

    this.userMessages = userMessages;

    this.lang               = lang;
    //var message             = null;

    // array with any messages (errors, warning, etc) displayed to the user
    var messages            = [];

    // variable is used to checked whether new rates are already
    // loaded after exchange rate date was changed by the user
    var ratesLoaded         = true;
    
    // ------------------------ constants 

    var resetAction = false;

    // used to format numbers
    var PRECISION           = 4;

    var ALL_CURRENCIES      = "---";

    // convert form field ids
    var AMOUNT_KEY          = "amount";
    var CURR_FROM_KEY       = "currencyFrom";
    var CURR_TO_KEY         = "currencyTo";
    var EXCH_RATE_DATE_KEY  = "exchangeDate";

    var LOADING_MSG_KEY     = "loadingMsg";
    var LOADED_MSG_KEY     = "loadedMsg";

    var ERROR               = 1;
    var INFO                = 2;

    // used to highlight row in a currency table
    var HIGHLIGHT_COLOR     = '#FFECEC';

    // colors used to display currency table
    var COLORS              = ["ebebeb", "white"];

    var NEW_RATES_URL       = "ajax/fqs-currency.json";

    // ----------------------- public methods

    /**
     * Convert and display converted currencies
     * @param formId id of the form which keeps amount, currencyFrom, 
     *               currencyTo, rateDate data
     */
    this.convertCurrencies = function(formId) {
        // raw values coming from the form
        var formValues = dijit.byId(formId).getValues();

        // values after validation, this is a map with the keys exactly
        // the same as in formValues
        var values = null;

        var amount;
        var currencyFrom;
        var currencyTo;
        
        if ( ratesLoaded ) {
            _hideElements();
            var values = _validate(formValues);    
            if ( values != null ) {
                amount = values[AMOUNT_KEY];
                currencyFrom = values[CURR_FROM_KEY];
                currencyTo = values[CURR_TO_KEY];
    
                if ( currencyTo == ALL_CURRENCIES ) {
                    _clearTable();
                    _updateTable(_calculateRates(amount, currencyFrom));
                    _updateRateDate(this.rateDate);
                    findElement(currTableId).style.display = ''; 
                    findElement(convertDateElemId).style.display = ''; 
                }
                else {      // convert one currency to just one other currency
                    var rateFrom = this.ratesTable[currencyFrom];
                    var rateTo = this.ratesTable;
                    _updateResultElem(_calculateOne2One(amount, currencyFrom, currencyTo));
                    _updateRateDate(this.rateDate);
                    findElement(convertResultElemId).style.display = ''; 
                    findElement(convertDateElemId).style.display = ''; 
                }

            }
            else {
                _showMessages();
            }
        }
    }

    /**
     * When new date is selected go to the server and ask
     * for the new exchange rate
     *
     * @param dateTBId id of the DateTextBox field
     * @param date (Date)
     */
    this.changeRateDate = function(dateTBId, date) {
        if ( date.length == 0 ) {
            // date is empty, just set to the old date
            dijit.byId(dateTBId).setValue(this.rateDate);
        }
        else if ( ! _datesTheSame(this.rateDate, date) ) {
            _hideElements();
            _clearMessages();
            messages.push([INFO, 
                userMessages[LOADING_MSG_KEY] + " (" + 
                dojo.date.locale.format(date, 
                    {datePattern:'dd.MM.yyyy', selector: 'date'}) + ")"
            ]);
            ratesLoaded = false;
            if ( !resetAction ) {
                _showMessages();
            }
            _fetchNewRates(date, this.lang)
            this.rateDate = date;
        }
        else {
            // dates are the same do nothing
        }
    }

    /**
     *
     *
     */
    this.resetForm = function() {
        resetAction = true;
        dijit.byId(AMOUNT_KEY).setValue(1);
        dijit.byId(CURR_FROM_KEY).setValue("CHF");
        dijit.byId(CURR_TO_KEY).setValue("EUR");
        dijit.byId(EXCH_RATE_DATE_KEY).setValue(new Date());
        //dojo.byId(messageElemId).style.display="none";

    }

    // --------------------------- private methods

    /**
     * @param newDate (Date)
     * @param lang (string)
     */
    function _fetchNewRates(newDate, lang) {
        ratesLoaded = true;
        var today = new Date();
        var _url = NEW_RATES_URL + "?date=" + 
                   dojo.date.locale.format(newDate, 
                        {datePattern:'yyyyMMdd', selector: 'date'}) +
                   "&lang=" + lang;

        _clearMessages();

        if ( newDate > today ) {
            messages.push([ERROR, userMessages[EXCH_RATE_DATE_KEY]]);
            _showMessages();
            return;
        }

        dojo.xhrGet( {
            url         : _url,
            handleAs    : "json-comment-filtered",
            timeout         : 10000,      // in miliseconds
            preventCache    : true,

            load: function(response, ioArgs) {
                //console.log("ajax call");
                //console.log(response);
                messages.push([INFO, 
                    userMessages[LOADED_MSG_KEY] + " (" + 
                    dojo.date.locale.format(newDate, 
                        {datePattern:'dd.MM.yyyy', selector: 'date'}) + ")"
                ]);
                ratesTable = response.rates;
                _refreshSelectWidgets(response.codes);
                _showMessages();

            },      // end load

            error: function(response, ioArgs) {
                console.log("AJAX Error, HTTP status code: " + ioArgs.xhr.status);
                return response;
            }       // end error

        } );        // end dojo.xhrGet

    }
    /**
     * Validate values coming from the input form
     *
     * @param values values coming for the form (map)
     * @return if everything ok then map with validate values
     *         if validation fails then returns null and set
     *         messages to be displayed
     */
    function _validate(values) {
        var validatedValues = {};
        var today = new Date(); 

        _clearMessages();
         
        for (var val in values) {
            if ( val == AMOUNT_KEY ) {
                if ( values[val] == null || values[val].length == 0 )
                    messages.push([ERROR, userMessages[val]]);
                else {
                    var amount = values[val].replace(",", ".") * 1;
                    if ( isNaN(amount) || amount <= 0 ) 
                        messages.push([ERROR, userMessages[val]]);
                    else
                        validatedValues[val] = amount;
                }
            } 
            else {
                if ( values[val] == null || values[val].length == 0 )
                    messages.push([ERROR, userMessages[val]]);
                else {
                    if ( val == EXCH_RATE_DATE_KEY && values[val] > today)
                        messages.push([ERROR, userMessages[EXCH_RATE_DATE_KEY]]);
                    else
                        validatedValues[val] = values[val];
                }
            }
        }
       
        if ( messages.length > 0 )
            return null;

        return validatedValues;
    }

    /**
     * Clear all messages
     */
    function _clearMessages() {
        var item = null;
        for (var i = 0; i < messages.length; i++) {
            item = messages.pop();
            item = null;
        }
        messages = [];
    }

    /**
     * Set day, month, year from date
     * @param date (Date) to be set
     * @return return date which has set only year, month and day
     */
    function _getOnlyDate(date) {
        var mDate = new Date();
        mDate.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
        mDate.setHours(0,0,0,0);
        return mDate;
    }

    /**
     * Check if two dates are the same
     * @param date1 (Date)
     * @param date2 (Date)
     * @return true is date1 and date2 are the same
     *         false otherwise
     */
    function _datesTheSame(date1, date2) {
        if ( date1 < date2 ) 
            return false;
        else if ( date2 < date1 )    
            return false;
        else
            return true;

    }

    /**
     * If messages are not empty display all
     *
     */
    function _showMessages() {
        var messElem = findElement(messageElemId);
        var innerHtml = "";
        for (var i = 0; i < messages.length; i++) {
            var message = messages[i];
            innerHtml += '<span ';
            if ( message[0] == ERROR )
                innerHtml += 'style="color: #FF0011"';
            else if ( message[0] == INFO )
                innerHtml += 'style="color: #003266"';

            innerHtml +='>' + " " + message[1] + '</span>';
            if ( i != messages.length )
                innerHtml += '<br/>';
        }
        messElem.innerHTML = innerHtml;
        if ( resetAction ) {
            messElem.style.display = 'none';
            resetAction = false;
        }
        else {
            messElem.style.display = '';
        }
    }
   
    /**
     * Update the select widgets with the new currency codes
     *
     * @param codes new codes (array) [{ n: "value", v: "value"}, { n: "value", v: "value"} ]
     */
    function _refreshSelectWidgets(codes) {
            
        var currencyCodesStore           = new dojo.data.ItemFileReadStore({data:
            {   identifier : "v",
                items: codes 
            }
        });
        
        var iconsUpdater = new FilteringSelectIconUpdater();
        
        var fromCurrencyStore = iconsUpdater.createReadStoreFromStore(currencyCodesStore, currencyIcons);
        var toCurrencyStore = fromCurrencyStore;
        
        var widget = dijit.byId(CURR_FROM_KEY);
        
        var value = widget.getValue();

        widget.store = fromCurrencyStore;
        widget.reset();

        if ( _valueExists(codes, value) ) {
            widget.setValue(value);
        }
       
        widget = dijit.byId(CURR_TO_KEY);
        value = widget.getValue();

        widget.store = toCurrencyStore;
        widget.reset();

        if ( _valueExists(codes, value) ) {
            widget.setValue(value);
        }
    }

    /**
     * @return element with elId or null
     */
    function findElement(elId) {
        var elem = document.getElementById(elId);
        return elem;
    }

    /**
     * Hide all elements
     */
    function _hideElements() {
        findElement(currTableId).style.display = 'none'; 
        findElement(messageElemId).style.display = 'none'; 
        findElement(convertDateElemId).style.display = 'none'; 
        findElement(convertResultElemId).style.display = 'none'; 
    }

    /** 
     * remove all rows from a table, except header
     */
    function _clearTable() {
        var tab = findElement(currTableId);
        if ( tab.rows.length == 2 ) { // table has just the header, do nothing
            return;
        }
        while (tab.rows.length > 2) {
            tab.rows[2].onmouseover = null;
            tab.rows[2].onmouseout = null;
            tab.deleteRow(2);
        }
    } 

    /**
     * Calculate all rates
     *
     * @return array with newly calculated currency values
     */
    function _calculateRates(amount, currencyCode) {
        var newRates = [];
        var a = amount;
   
        var i = 0; 
        var rate = null;

        if ( isNaN(a) || a <= 0 ) {
            return newRates;
        }

        if ( currencyCode == 'CHF' ) {
            for ( code in this.ratesTable ) {
                rate = this.ratesTable[code];
                newRates[i++] = [code + ": " + rate[1], 
                                (a / rate[0]).toFixed(PRECISION)];
            }
        }
        else {
            // calculate for CHF
            rate = this.ratesTable[currencyCode];
            newRates[0] = ['CHF: ' + chfDispName, 
                           (a * rate[0]).toFixed(PRECISION)];

            i = 1;   
            // calculate for the rest
            for ( code in this.ratesTable ) {
                var ratei = this.ratesTable[code];
                if ( currencyCode != code ) {
                    newRates[i++] = [code + ": " + ratei[1],
                                    (a*(rate[0]/ratei[0])).toFixed(PRECISION)];
                }
            }
        }
        return newRates;
    }

    /**
     * Convert one currency into another 
     * @param amount
     * @param currencyFrom
     * @param currencyTo
     * 
     * @return map { from: [code, name], 
     *               to: [code, name], 
     *               fromTo: [1, 2], 
     *               toFrom: [2, 1] }
     */
    function _calculateOne2One(amount, currencyFrom, currencyTo) {

        var result = {};
        var rate = 0;

        result['from']  = (currencyFrom == 'CHF') 
                          ? ["CHF", chfDispName]
                          : [currencyFrom, ratesTable[currencyFrom][1]]; 
        result['to']    = (currencyTo == 'CHF') 
                          ? ["CHF", chfDispName]
                          : [currencyTo, ratesTable[currencyTo][1]]; 

        if ( currencyFrom == currencyTo ) {
            result['fromTo'] = [amount, amount];
            result['toFrom'] = [amount, amount];
        }
        else if ( currencyFrom == 'CHF' ) {
            result['fromTo'] = [
                amount, 
                (amount * 1/ratesTable[currencyTo][0]).toFixed(PRECISION)
            ];
            result['toFrom'] = [
                amount, 
                (amount * ratesTable[currencyTo][0]).toFixed(PRECISION)
            ];
        }
        else if ( currencyTo == 'CHF' ) {
            result['fromTo'] = [
                amount, 
                (amount * ratesTable[currencyFrom][0]).toFixed(PRECISION)
            ];
            result['toFrom'] = [
                amount, 
                (amount * 1/ratesTable[currencyFrom][0]).toFixed(PRECISION)
            ];
        }
        else {
            result['fromTo'] = [
                amount, 
                (amount * ratesTable[currencyFrom][0]/ratesTable[currencyTo][0]).toFixed(PRECISION)
            ];
            result['toFrom'] = [
                amount, 
                (amount * ratesTable[currencyTo][0]/ratesTable[currencyFrom][0]).toFixed(PRECISION)
            ];
        }

        return result; 
    }

    /**
     * 
     * @param newRates
     */
    function _updateTable(newRates) {
        var tab = findElement(currTableId);
        for (i = 0; i < newRates.length; i++) {
            var row = tab.insertRow(tab.rows.length);
            row.onmouseover = _hightlightFun;
            row.onmouseout = _unhightlightFun;
            var cells = newRates[i];
            if ( i % 2 == 0 )
                row.className = "row-odd";
            else 
                row.className = "row-even";

            for (j = 0; j < cells.length; j++) {
                var cell = row.insertCell(j);
                if ( j == 0 ) {
                    cell.className = "first";
                    cell.align = "left";
                }
                else {
                    cell.align = "right";
                }
                cell.innerHTML = cells[j];
            }
        }
    }
  
    /**
     * Update the rate date to be displayed on the page
     * @param rDate (date)
     */
    function _updateRateDate(rDate) {
        var dateElem = findElement(convertDateElemId).rows[0].cells[2]; 
        var datePatten = "";
        var dateStr = "";

        if ( lang == "en" ) {
            datePattern = 'EEE, dd MMMM yyyy';
        }
        else if ( lang == "de" ) {
            datePattern = 'EEE, dd. MMMM yyyy';
        }
        else {
            datePattern = 'EEEE, dd MMMM yyyy';
        }
        dateStr =  dojo.date.locale.format(rDate, 
            {selector: 'date',
            datePattern: datePattern, fullYear: true, locale: lang});

        
        if ( lang == "fr" ) {
            var arr = dateStr.split(",");
            var day = arr[0];
            day = day.substring(0, 3);
            day = day.substr(0, 1).toUpperCase() + day.substr(1);
            dateStr = day + ", " + arr[1];
        }

        dateElem.innerHTML = dateStr;
    }

    /**
     * Update the page which displays the 1-to-1 currency conversion
     * @param retes map returned by _calculateOne2One method
     */
    function _updateResultElem(rates) {
        var resultElem = findElement(convertResultElemId); 
        var codeFrom = rates['from'];
        var codeTo = rates['to'];
        var fromTo = rates['fromTo'];
        var toFrom = rates['toFrom'];

        document.getElementById("icon1").src=currencyIcons[codeFrom[0]];
        resultElem.rows[0].cells[1].childNodes[0].innerHTML = fromTo[0];
        resultElem.rows[0].cells[1].childNodes[1].innerHTML = codeFrom[1] + ' (' + codeFrom[0] + ')';
       
        document.getElementById("icon2").src=currencyIcons[codeTo[0]];
        resultElem.rows[0].cells[4].childNodes[0].innerHTML = fromTo[1];
        resultElem.rows[0].cells[4].childNodes[1].innerHTML = codeTo[1] + ' (' + codeTo[0] + ')';

        document.getElementById("icon3").src=currencyIcons[codeTo[0]];
        resultElem.rows[1].cells[1].childNodes[0].innerHTML = toFrom[0];
        resultElem.rows[1].cells[1].childNodes[1].innerHTML = codeTo[1] + ' (' + codeTo[0] + ')';
        
        document.getElementById("icon4").src=currencyIcons[codeFrom[0]];
        resultElem.rows[1].cells[4].childNodes[0].innerHTML = toFrom[1];
        resultElem.rows[1].cells[4].childNodes[1].innerHTML = codeFrom[1] + ' (' + codeFrom[0] + ')';
    }

    /**
     * Highlight row in a currency table
     * @param evt
     */
    function _hightlightFun(evt) {
        if ( !evt ) evt = window.event;
        var src = evt.target != null ? evt.target : evt.srcElement;
        var _row = src.parentNode;
        // highlight all cells in a row
        for (var ii = 0; ii < _row.cells.length; ii++) {
            _row.cells[ii].style.backgroundColor = HIGHLIGHT_COLOR;
            _row.cells[ii].style.fontWeight = "900";
            _row.cells[ii].style.fontSize = "11px";
        }

    }

    /**
     * Unhighlight row in a currency table
     * @param evt
     */
    function _unhightlightFun(evt) {
        if ( !evt ) evt = window.event;
        var src = evt.target != null ? evt.target : evt.srcElement;
        var _row = src.parentNode;
        for (var ii = 0; ii < _row.cells.length; ii++) {
            _row.cells[ii].style.backgroundColor = 
            COLORS[_row.rowIndex %2];
            _row.cells[ii].style.fontWeight = 'normal';
            _row.cells[ii].style.fontSize = "10px";
        }
    }

    function _valueExists(array, value) {
        for ( var i in array ) {
            if ( array[i].v == value )
                return true;
        }

        return false;
    }
}
