/**------------------------------------------------------------------------
 * Set of function and classes which deal with bond yield calculator
 *
 * Dependencies:
 *  dojo.js, version 0.9 
 *
 * Author: 
 *  Bartlomiej.Pawlowski@swx.com
 *
 * @version $Id: yieldCalc.js,v 1.1 2009/08/13 08:12:36 ssk Exp $
 *
 *-----------------------------------------------------------------------*/

dojo.require("dojo.fx");
dojo.require("dojox.collections.Dictionary");


// this variable is made global in order to be easy accesible in CalculatorGUI 
// and calculator without coupling both of them
var dayCountMethodCodes = {
    FLAT            : '0',
    GERMAN          : '1',
    ENGLISH         : '2',
    FRENCH          : '3',
    ISMA_YEAR       : '5',
    ISMA_99_NORMAL  : '6',
    SPECIAL_GERMAN  : '9',
    
    /* currently not used in FQS */
    US              : '10',
    ISMA_99_ULTMO   : '11'
}

// how many coupon payments within year
var couponFrequencyCodes = {
    YEARLY                      : '1',
    SEMI_ANNUAL                 : '2',
    THREE_TIMES_PER_YEAR        : '3',
    QUATERLY                    : '4',
    FIVE_TIMES_PER_YEAR         : '5',
    SIX_TIMES_PER_YEAR          : '6',
    SEVEN_TIMES_PER_YEAR        : '7',
    EIGHT_TIMES_PER_YEAR        : '8',
    NINE_TIMES_PER_YEAR         : '9',
    TEN_TIMES_PER_YEAR          : '10',
    ELEVEN_TIMES_PER_YEAR       : '11',
    MONTHLY                     : '12',
    EVERY_SECOND_YEAR           : 'D',
    EVERY_THIRD_YEAR            : 'E',
    EVERY_FOURTH_YEAR           : 'F',
    EVERY_FIFTH_YEAR            : 'G',
    EVERY_SIXTH_YEAR            : 'H',
    EVERY_SEVENTH_YEAR          : 'I',
    EVERY_EIGTH_YEAR            : 'J',
    EVERY_NINETH_YEAR           : 'K',
    EVERY_TENTH_YEAR            : 'L',
    EVERY_ELEVENTH_YEAR         : 'M',
    EVERY_TWELVE_YEAR           : 'N',
    EVERY_THIRTEENTH_YEAR       : 'O',
    EVERY_FOURTEENTH_YEAR       : 'P',
    EVERY_FIFTHTEENTH_YEAR      : 'Q',
    ANYTIME                     : 'X',
    IRREGULAR_RECCURENCE        : 'Y',
    NO_OCCURENCE                : 'Z'
}

// what to calculate store codes (SC)
var whatCalculateSC = {
    YIELD : 'cYield',
    PRICE : 'cPrice'
}


var monthsShortNames = {
    0: 'jan',
    1: 'feb',
    2: 'mar',
    3: 'apr',
    4: 'may',
    5: 'jun',
    6: 'jul',
    7: 'aug',
    8: 'sept',
    9: 'oct',
    10: 'nov',
    11: 'dec'
}

/**
 * Bond calculator gui controler
 */
function BondYieldCalculatorGUI() {
    
    // ------------------------ constants 

    // different dojo read store codes

    // what to calculate store codes (SC)
    //this.whatCalculateSC = {
    //    YIELD : 'cYield',
    //    PRICE : 'cPrice'
    //}

    // static values that can be used as public values
    this.pubstatics = {
        INCREASE_STEP   : 0.25,
        COUPON_RATE_INCREASE_STEP : 0.25
    }

    // advanced validation messages keys
    this.advValidationMsgKeys = {
        LAST_COUPON_DATE_GREATER_NEXT_COUPON_DATE   : 
            ['1',                                       // code
            // @return true if last coupon date > next coupon date 
            function(lastCouponDate, nextCouponDate) {  // validate function
                if ( lastCouponDate > nextCouponDate )
                    return true; 
                else
                    return false;

            }],

        NEXT_COUPON_DATE_GREATER_MATURITY_DATE      : 
            ['2', 
            // @retur
            function(nextCouponDate, maturityDate) {
                if ( nextCouponDate > maturityDate )
                    return true;
                else
                    return false;
            }],
        SETTLEMENT_DATE_LESS_ISSUE_DATE             :
            ['3',
            function(settlementDate, issueDate) {
                
                /*
                 * this does not need to be checked by automatic validation
                 * so always return false
                 *
                if ( settlementDate < issueDate ) 
                    return true;
                else
                    return false;
                */
                return false;

            }]
 
        /*
        SETTLEMENT_DATE_LESS_LAST_COUPON_DATE       : 
            ['4', 
            function(settlementDate, lastCouponDate) {
                if ( settlementDate < lastCouponDate ) 
                    return true;
                else
                    return false;
            }]
        */
        /*
        SETTLEMENT_DATE_GREATER_NEXT_COUPON_DATE    : 
            ['5', 
            function(settlementDate, nextCouponDate) {
                if ( settlementDate > nextCouponDate ) 
                    return true;
                else
                    return false;
            }]
        */        
    }

    // names of the html form fields
    // when form definition os the html page changes the following
    // names should also be adjusted
    this.formFields = {
        NOMINAL_VALUE       : 'nominalValue',
        MATURITY_DATE       : 'maturityDate',
        COUPON_RATE         : 'couponRate',
        LAST_COUPON_DATE    : 'lastCouponDate',
        COUPON_FREQUENCY    : 'couponFrequency',
        NEXT_COUPON_DATE    : 'nextCouponDate',
        SETTLEMENT_DATE     : 'settlementDate',
        DAY_COUNT_METHOD    : 'dayCountMethod',
        WHAT_CALCULATE      : 'whatCalculate',
        CLEAN_PRICE         : 'cleanPrice',
        YIELD               : 'yield'
    }
    
    // The corresponding labels for the fields should just have an 'lbl' prefix and the same names
    // as above i.e. a label id for NOMINAL values is: 'lbl' + this.formFields.NOMINAL_VALUE 
    var labelPrefix = "lbl";
   
    // names of the html elements that display
    // calulated values (ie yieldPriceValue)
    this.resultElemIds = {
        YIELD_PRICE         : 'yieldPriceValue',
        NOMINAL_VALUE       : 'nomValue',
        DURATION            : 'durationValue',
        PRINCIPAL           : 'principalValue',
        MODIFIED_DURATION   : 'modDurationValue',
        DAYS                : 'daysValue',
        ACC_INTEREST        : 'accruedInterestValue',
        REMAINING_LIFE      : 'remainingLifeValue',
        ACTUAL_VALUE        : 'actualValue'
    }

    this.TheQuery = {
            query       : 2
    }

    // --------------------------- private variables 
    var thisRef = this;
   
    var calc = new BondCalculator(thisRef.formFields, thisRef.resultElemIds);

    // possible form field types, used to do automatic basic
    // form validation
    var fieldTypes = {
        NUMBER  : 'N',
        DATE    : 'D',
        STRING  : 'S'
    }

    // default values and types for the form fields
    var formDefaults = new dojox.collections.Dictionary(); 
    formDefaults.add(this.formFields.NOMINAL_VALUE,         ['10000', fieldTypes.NUMBER]);
    formDefaults.add(this.formFields.MATURITY_DATE,         [_calcDefaultMaturityDate(), fieldTypes.DATE]);
    formDefaults.add(this.formFields.COUPON_RATE,           ['5.00', fieldTypes.NUMBER]);
    formDefaults.add(this.formFields.LAST_COUPON_DATE,      [_calcDefaultLastCouponDate(), fieldTypes.DATE]);
    formDefaults.add(this.formFields.COUPON_FREQUENCY,      ['1', fieldTypes.STRING]);
    formDefaults.add(this.formFields.NEXT_COUPON_DATE,      [_calcDefaultNextCouponDate(), fieldTypes.DATE]);
    formDefaults.add(this.formFields.SETTLEMENT_DATE,       [_calcDefaultSettlementDate(), fieldTypes.DATE]);
    formDefaults.add(this.formFields.DAY_COUNT_METHOD,      [dayCountMethodCodes.GERMAN, fieldTypes.NUMBER]);
    formDefaults.add(this.formFields.WHAT_CALCULATE,        [whatCalculateSC.YIELD, fieldTypes.STRING]);
    formDefaults.add(this.formFields.CLEAN_PRICE,           ['100.00', fieldTypes.NUMBER]);
    formDefaults.add(this.formFields.YIELD,                 ['5.00', fieldTypes.NUMBER]);

    
    // static values that should not be used as public values
    var statics = {
        ONE_DAY         : 1000 * 60 * 60 * 24,

        // default settlement currency code
        DEFAULT_SETTLEMENT_CURR_CODE : 'CHF',
    
        // colors used to fade the yield and current price labels
        FADE_IN_COLOR   : "red",
        FADE_DONE_COLOR : "black",

        // color used to display error messages
        //ERROR_MSG_COLOR : '#FF0011',
        ERROR_MSG_COLOR : 'black',

        // types of errors that might occur during form validation 
        ERROR           : 1,
        INFO            : 2,
        
        //colors to highlight labels for fields that fail validation 
        // or do not have a value on the Analysis tab
        LABEL_HIGHLIGTH_COLOR_ON : 'red',
        LABEL_HIGHLIGTH_COLOR_OFF : 'black'

    }

    // id of a different html elements from html page
    var _elemIds = {
        
        // info, error dialog box id
        MSG_DIALOG_ID   : "messageDialog",

        // element displaying message that says the calculate button
        // should be pressed
        STATE_CHANGE_ID : "stateChange",
        
        //the id of the message that gets displayed when the calculator is displayed in a Analysis tab
        //for a Bond
        NOT_AVAILABLE_INFO_ID : "valueNotAvailable4Bond"
    }

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

    // Dictionary with resultElemId as keys 
    // and all calculated values returned by internal calculator
    var result              = null;
    
    // Dictionary with resultElemId as keys 
    // and all calculated values returned by internal calculator on previuos calculation
    // used for flashing cells
    var prevResult              = null;

    // variable indicates whether the result table is currently shown or not
    var resultShown         = false;

    // variable indicates if the calculate button was already pressed
    var firstCalculationDone = false;

    // variable indicates whether the page and form was already loaded
    // used to know whether the yield and current price label should 
    // use red color when displayed
    var formLoaded = false;

    // ------------------------ public methods
    /**
     * @param formId, string, id of the form which keeps all data required
     *          to calculate the yield
     *
     * @param basicValidationMsg (Dictionary) map with form field names as keys
     *        and basic validation messages
     *
     * @param advValidationMsgs (Dictionary) map with keys from .......
     *        and messages form advanced validation (like last coupon date > next coupon date, etc)
     */
    this.calculateYield = function(formId, basicValidationMsg, advValidationMsgs) {
        // raw values coming from the form
        var formValues = dijit.byId(formId).getValues();
        //hide message displayed on first load of tab with missing values within the Analysis tab
        _displayNotAvailableInfo(_elemIds.NOT_AVAILABLE_INFO_ID, false);
        
        //alert(dojo.toJson(formValues));
        
        // values after validation, this is a map with the keys exactly
        // the same as in formValues
        var values = _validate(formValues, basicValidationMsg, advValidationMsgs);
        //mark visually fields that may have failed validation\
        _markDirtyFields(); 
        if ( values != null ) {   
            result = calc.calculate(values);
            _showResult(values[this.formFields.WHAT_CALCULATE]);
            dojo.byId(_elemIds.STATE_CHANGE_ID).style.display="none";
            firstCalculationDone = true;
        }
        else {
            _showMessages();
        }
    }

    /**
     * Hide all shown components and reset all form fields
     *
     * @param formId, string, id of the dojo form widget
     */
    this.clearForm = function(formId) {
        _hideResult();
        firstCalculationDone = false;

        var formMetaData = dijit.byId(formId).getValues();
        var e, val, dijitNode;

        // reset values
        for ( e in formMetaData ) {
            dijitNode = dijit.byId(e);
            val = formDefaults.item(e);
            if ( val[0] ) {
                dijitNode.setValue(val[0]);

                // because the ValidationTextBox.setValue method makes use 
                // of displayMessage method only when the TextBox is focused, 
                // the TextBox label's color has to be reset manaully 
                if ( dijitNode.labelId )
                    dojo.byId(dijitNode.labelId).style.color = 'black';
            }
            else
                dijitNode.domNode.value = '';

        }

    }

    /**
     * @param elemId - string, id of the dijit element
     * @param value - number
     */
    this.increase = function(elemId, value) {
        _increaseValue(dijit.byId(elemId), value); 
        this.stateChanged();
    }

    /**
     * @param elemId - string, id of the dijit element
     * @param value - string,
     */
    this.decrease = function(elemId, value) {
        _increaseValue(dijit.byId(elemId), -value); 
        this.stateChanged();
    }

    this.stateChanged = function() {
        //console.log("runs stateChanged ...");
        if ( firstCalculationDone ) 
            dojo.byId(_elemIds.STATE_CHANGE_ID).style.display="";
    }

    /**
     * Initialize the calculator when it is displayed in the Bonds Analysis tab i.e.
     * some values may be prefilled and others need to get their defaults.
     */
    this.initCalcAfterDisplayFromTab = function() {
        //indicates any issues
        var noProbs = true;
        _clearDirtyFields();
        
        //console.log(">> initCalcAfterDisplay ... ");
        
        if(dijit.byId(this.formFields.SETTLEMENT_DATE).value == '') {
                // set default settlement date
                dijit.byId(this.formFields.SETTLEMENT_DATE).setValue(
                    formDefaults.item(this.formFields.SETTLEMENT_DATE)[0]);
        }

        /* all of these fields can be filled from FQS so apply proper handling for missing values */
        if(dijit.byId(this.formFields.NOMINAL_VALUE).value == '') {
                dirtyFields.push(this.formFields.NOMINAL_VALUE);
                noProbs = false;
        }
        
        if(dijit.byId(this.formFields.COUPON_RATE).value == '') {
                dirtyFields.push(this.formFields.COUPON_RATE);
                noProbs = false;
        }
        
        if(dijit.byId(this.formFields.COUPON_FREQUENCY).value == '') {
                dirtyFields.push(this.formFields.COUPON_FREQUENCY);
                noProbs = false;
        }
         
        if(dijit.byId(this.formFields.MATURITY_DATE).value == '') {
                dirtyFields.push(this.formFields.MATURITY_DATE);
                noProbs = false;
        }

        if(dijit.byId(this.formFields.LAST_COUPON_DATE).value == '') {
                dirtyFields.push(this.formFields.LAST_COUPON_DATE);
                noProbs = false;
        }

        if(dijit.byId(this.formFields.NEXT_COUPON_DATE).value == '') {
                dirtyFields.push(this.formFields.NEXT_COUPON_DATE);
                noProbs = false;
        }
        
        if(dijit.byId(this.formFields.DAY_COUNT_METHOD).value == '') {
                dirtyFields.push(this.formFields.DAY_COUNT_METHOD);
                noProbs = false;
        }
        
        if(dijit.byId(this.formFields.CLEAN_PRICE).value == '') {
                dirtyFields.push(this.formFields.CLEAN_PRICE);
                noProbs = false;
        }
       
        //run the calculation if everything is OK
        if(noProbs) {
                this.calculateYield('yieldForm', basicValidationMsg, advValidationMsgs);
        } else {
                _displayNotAvailableInfo(_elemIds.NOT_AVAILABLE_INFO_ID, true);
                _markDirtyFields();
        }
       
        dojo.byId(_elemIds.STATE_CHANGE_ID).style.display="none";
    }
    
    /**
     * Allows you to get the label name for a form field to reduce code clutter.
     */
    function lid(formFieldId) {
            //console.log(">>> returning label id: " + labelPrefix + "" + formFieldId);
            return (labelPrefix + "" + formFieldId); 
    }


    /**
     * Displays text below the calculator indicating that not all data is available for a bond
     * to perform a calculation. Displayed only in the Analysis tab.
     * @param valueNotAvailable4BondId The id of the control usually a span tag
     * @param on Whether the control should be made red and displayed true or false accepted
     */
    function _displayNotAvailableInfo(valueNotAvailable4BondId, on) {
        if(on) {
                document.getElementById(valueNotAvailable4BondId).style.display = "";
                document.getElementById(valueNotAvailable4BondId).style.color = statics.LABEL_HIGHLIGTH_COLOR_ON;
        } else {
                document.getElementById(valueNotAvailable4BondId).style.display = "none";
                document.getElementById(valueNotAvailable4BondId).style.color = statics.LABEL_HIGHLIGTH_COLOR_OFF;
        }
    }
    
    /**
     * Highlights a label for a field in a specific color when a validation fails etc.
     * Note: The id of the control and its label are the same with the exception of
     * a label prefix, that is why we can take a control id and convert it to a label id which we 
     * can use to hightlight the label.
     * @param dijitControlId The id of the control to highlight usually a span tag
     * @param on A boolean indicating whether the color should be changed.
     */
     function _highlightLabel(dijitControlId, on) {
        var labelId = lid(dijitControlId);
        if(on) {
                document.getElementById(labelId).style.color = statics.LABEL_HIGHLIGTH_COLOR_ON;
        } else {
                document.getElementById(labelId).style.color = statics.LABEL_HIGHLIGTH_COLOR_OFF;
        }        
     }
     
     /**
      * Highlight a dijit control's border when a field fails validation.
      * @param dijitId The HTML id of the control
      * @param on Boolean indicator saying whether the control should be highlighted with the ON color
      * true, or with the OFF color false 
      */
     function _highlightDijitControl(htmlId, on) {
        if(on) {
                //this works on the DOM node of the dijit control
                dijit.byId(htmlId).domNode.style.borderColor = statics.LABEL_HIGHLIGTH_COLOR_ON;
                //- TODO - use below with dojo 1.3, when we upgrade
                //dojo.style(htmlId, "border-color", statics.LABEL_HIGHLIGTH_COLOR_ON);
        } else {
                //leave it as specified by default
                dijit.byId(htmlId).domNode.style.borderColor = "";
        }        
     }  

    /**
     * Do all sorts of initialization on page load
     * @param queryString - (string) if set then use values from querystring
     *                    to initialize calculator
     */
    this.initCalc = function(queryString) {
        // set values from query string if present
        var qso, n, nt;
        if ( queryString && queryString.length > 0 ) {
            qso = dojo.queryToObject(queryString.substring(1));
            //console.log(dojo.toJson(qso));
            for (n in qso) {
                _setValue(n, qso[n]);
            }
            
        } else {
                // set default settlement date
                dijit.byId(this.formFields.SETTLEMENT_DATE).setValue(
                    formDefaults.item(this.formFields.SETTLEMENT_DATE)[0]);
        
                // set default maturity, last and next coupon dates
                dijit.byId(this.formFields.MATURITY_DATE).setValue(
                    formDefaults.item(this.formFields.MATURITY_DATE)[0]);
        
                dijit.byId(this.formFields.LAST_COUPON_DATE).setValue(
                    formDefaults.item(this.formFields.LAST_COUPON_DATE)[0]);
        
                dijit.byId(this.formFields.NEXT_COUPON_DATE).setValue(
                    formDefaults.item(this.formFields.NEXT_COUPON_DATE)[0]);
               
                dojo.byId(_elemIds.STATE_CHANGE_ID).style.display="none";
        }
    }
    
    
    

    /**
     *
     */
    this.toggleRows = function(value, elem1Id, elemFade1Id, elem2Id, elemFade2Id) {
        if ( value == 'cYield' ) {
            _toggleRows(elem1Id, elem2Id, elemFade1Id, elemFade2Id);
        }
        else {
            _toggleRows(elem2Id, elem1Id, elemFade2Id, elemFade1Id);
        }
    }

    /**
     * Show or hide default settings
     *
     * @param settingLables - array with 2 elements 
     *                 0 - show setting, 1 - hide settings
     */
    this.toggleSettings = function(settingLables) {

        // elements ids, maybe should not be hardcoded
        var l = "defaultSettingsLabel";
        var p = "defaultSettingPane";
        var t = "defaultSettingsTable";

        var tab = dojo.byId(t);

        var f;
        if ( tab.style.display == 'none' ) {

            f = dojo.fx.wipeIn({
                node: dojo.byId(p),
                duration: 700,
                beforeBegin: function() {
                    tab.style.display="";
                },
                onEnd: function() {
                    dojo.byId(l).innerHTML = settingLables[1];
                }
            });
        }
        else {
            f = dojo.fx.wipeOut({
                node: dojo.byId(p),
                duration: 700,
                onEnd: function() {
                    tab.style.display="none";
                    dojo.byId(l).innerHTML = settingLables[0];
                }
            });
        }
        f.play();
    }

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

    /**
     * Show result after calculation
     */
    function _showResult(whatWasCalculated) {
        var el = dojo.byId("resultTitlePane");
        var e, k, n;
       
        // update what was calculated
        
        // formFieldDispNames are defined on the xsp, thats not very good
        // solution, but it looks like this is the only one
        if ( whatWasCalculated == whatCalculateSC.YIELD )
           n =  formFieldDispNames.item(thisRef.formFields.YIELD);
        else    
           n =  formFieldDispNames.item(thisRef.formFields.CLEAN_PRICE);
            
        dojo.byId('whatWasCalculated').innerHTML = n;


        // update result table
        for ( e in thisRef.resultElemIds ) {
            k = thisRef.resultElemIds[e];
            //console.log(e + " " +  + " " + result.item(k));
            dojo.byId(k).innerHTML = result.item(k);
        }

        if ( !resultShown ) {
            var w = dojo.fx.wipeIn({
                node: el,
                duration: 700,
                beforeBegin: function() {
                    dojo.byId("resultTable").style.display="";
                },
                onEnd: function() {
                    resultShown = true;
                }
            });

            w.play();            
        }
        
        //check and flash changes only after the first calculation
        if(!firstCalculationDone) {
            prevResult = result;    
        } else {
            for ( e in thisRef.resultElemIds ) {
                elem = thisRef.resultElemIds[e];
                //console.log(e + " " +  + " " + result.item(elem));
                //console.log("Comparing old: " + prevResult.item(elem) + " and new: " + result.item(elem));
                
                if(prevResult.item(elem) == result.item(elem)) {
                        //do not flash
                } else {
                        //flash this cell
                        _flashCell(elem);
                        //set the value to the new one
                        prevResult.remove(elem);
                        prevResult.add(elem,result.item(elem));
                }
            }   
        }
    }
    
    /**
     * This function flashes a cell in the result table which contains a changed result to visually
     * display to the user that something has changed
     * @param cellId The id of the cell you wish to flash
     */
    function _flashCell(cellId) {
        var realCellId = cellId + "Cell";
        
        //console.log("highlight node: " + dojo.byId(realCellId));
        
        if(dojo.byId(realCellId) != null) {
           //highlight(dojo.byId(realCellId), "#ffa500", 1000);
           
           var anim = swx.fx.highlight({node: realCellId, color: "#ffa500", duration: 1000});
           anim.play();
        } else {
           //console.log("No element with id: " + realCellId);
        }
    }

    /**
     * Hide result pane and all elements shown after yield 
     * calculation
     */
    function _hideResult() {
        if ( resultShown ) {
            var el = dojo.byId("resultTitlePane");
            var w = dojo.fx.wipeOut({
                node: el,
                duration: 700,
                beforeBegin: function() {
                    dijit.byId("whatCalculate").setValue(whatCalculateSC.YIELD);
                },
                onEnd: function() {
                    dojo.byId("resultTable").style.display="none";
                    dojo.byId(_elemIds.STATE_CHANGE_ID).style.display="none";
                    resultShown = false;
                }
            });

            w.play();
        }
        else {
            dijit.byId("whatCalculate").setValue(whatCalculateSC.YIELD);
        }
        
    }

    /**
     * Validate values coming from the input form 
     * and fill messages if any validation errors occurs
     *
     * @param values values coming for the form (map)
     * @param basicValidationMsg (Dictionary)
     * @param advValidationMsgs (Dictionary)
     *
     * @return if everything ok then map with validate values
     *         if validation fails then returns null and set
     *         messages to be displayed
     */
    function _validate(values, basicValidationMsg, advValidationMsgs) {
        var validatedValues = {};
        var valueType; 
        var bMsg;                   // basic validation message
        _clearMessages();
        _clearDirtyFields();
        
        //console.log("dirtyFields before validation: " + dirtyFields.toString());
        
        // simple validation
        for ( var val in values ) {
            valueType = formDefaults.item(val)[1];
            bMsg = basicValidationMsg.item(val);
            
            //console.log(val + " " + values[val] + " " + valueType + " " + bMsg);

            validatedValues[val] = values[val];

            switch (valueType) {
                case fieldTypes.NUMBER:
                    if ( val != thisRef.formFields.CLEAN_PRICE 
                         && val != thisRef.formFields.YIELD 
                         && !_validateNumber(values[val]) ) {
                        messages.push([statics.ERROR, bMsg]);
                        dirtyFields.push(val);
                    }
                    break;

                case fieldTypes.DATE:
                    if ( !values[val] ) {
                        messages.push([statics.ERROR, bMsg]);
                        dirtyFields.push(val);
                    }
                    break;
            }
        }

        // validate the cleanPrice and the yield values depeding 
        // on the WhatCalculate value
        if ( values[thisRef.formFields.WHAT_CALCULATE] == whatCalculateSC.YIELD
             && !_validateNumber(values[thisRef.formFields.CLEAN_PRICE]) ) {
            messages.push([statics.ERROR, 
                          basicValidationMsg.item(thisRef.formFields.CLEAN_PRICE)]);
            dirtyFields.push(thisRef.formFields.CLEAN_PRICE);
        }
        else if ( values[thisRef.formFields.WHAT_CALCULATE] == whatCalculateSC.PRICE
             && !_validateNumber(values[thisRef.formFields.YIELD])  ) {
            messages.push([statics.ERROR, 
                           basicValidationMsg.item(thisRef.formFields.YIELD)]);
            dirtyFields.push(thisRef.formFields.YIELD);
        }

        // advanced validation, only if basic validation is ok
        var fun, date1, date2;
        if ( messages.length <= 0 ) {
            for ( val in thisRef.advValidationMsgKeys ) {

                fun = thisRef.advValidationMsgKeys[val][1];

                switch (thisRef.advValidationMsgKeys[val][0]) {
                    case thisRef.advValidationMsgKeys.LAST_COUPON_DATE_GREATER_NEXT_COUPON_DATE[0]:
                        date1 = values[thisRef.formFields.LAST_COUPON_DATE];
                        date2 = values[thisRef.formFields.NEXT_COUPON_DATE];    
                       break;
                    case thisRef.advValidationMsgKeys.NEXT_COUPON_DATE_GREATER_MATURITY_DATE[0]:
                        date1 = values[thisRef.formFields.NEXT_COUPON_DATE];
                        date2 = values[thisRef.formFields.MATURITY_DATE];
                       break;                    
                   
                    /*
                     * in  this case the last coupon date needs to be recalculated
                     * depending on the coupon frequency and the last coupon date
                     * the next coupon date will become the last coupon date
                     *
                    case thisRef.advValidationMsgKeys.SETTLEMENT_DATE_LESS_LAST_COUPON_DATE[0]:
                        date1 = values[thisRef.formFields.SETTLEMENT_DATE];
                        date2 = values[thisRef.formFields.LAST_COUPON_DATE];
                       break;
                    */
                    /*
                    case thisRef.advValidationMsgKeys.SETTLEMENT_DATE_GREATER_NEXT_COUPON_DATE[0]:
                        date1 = values[thisRef.formFields.SETTLEMENT_DATE];
                        date2 = values[thisRef.formFields.NEXT_COUPON_DATE];
                       break;
                    */
                }
                if ( fun(date1, date2) ) {
                    messages.push([statics.ERROR, 
                        advValidationMsgs.item(thisRef.advValidationMsgKeys[val][0])]);
                        
                    //console.log("adding error message for: " + thisRef.advValidationMsgKeys[val][0]);
                    
                    //mark fields in error depending on case, closely linked to this.advValidationMsgKeys 
                    switch(thisRef.advValidationMsgKeys[val][0]) {
                            case '1':
                                dirtyFields.push(thisRef.formFields.LAST_COUPON_DATE);
                                dirtyFields.push(thisRef.formFields.NEXT_COUPON_DATE);
                                break;
                            case '2':
                                dirtyFields.push(thisRef.formFields.NEXT_COUPON_DATE);
                                dirtyFields.push(thisRef.formFields.MATURITY_DATE);
                                break;
                    }
                }

            }

            // check if settlement date is less than last coupon date
            // in this case the next coupon date is the last coupon date
            // the last coupon date equals current last minus coupon frequency
            // check if settlement is before last coupon date minus frequency
            if ( validatedValues[thisRef.formFields.SETTLEMENT_DATE] < validatedValues[thisRef.formFields.LAST_COUPON_DATE] ) {
                validatedValues[thisRef.formFields.NEXT_COUPON_DATE] = validatedValues[thisRef.formFields.LAST_COUPON_DATE];
                validatedValues[thisRef.formFields.LAST_COUPON_DATE] = 
                    calc.calculateNextDate(validatedValues[thisRef.formFields.LAST_COUPON_DATE],
                                           validatedValues[thisRef.formFields.COUPON_FREQUENCY],
                                           calc.pubstatics.BACKWARDS);

                    if ( validatedValues[thisRef.formFields.LAST_COUPON_DATE] > validatedValues[thisRef.formFields.SETTLEMENT_DATE] ) {
                        messages.push([statics.ERROR, 
                            advValidationMsgs.item(thisRef.advValidationMsgKeys.SETTLEMENT_DATE_LESS_ISSUE_DATE[0])]);
                        dirtyFields.push(thisRef.formFields.SETTLEMENT_DATE);
                    }
            }

        }

        //console.log("dirtyFields AFTER validation: " + dirtyFields.toString());

        if ( messages.length > 0 )
            return null;

        return validatedValues;
        //return values;
    }

    /**
     * Check whether the number is a real valid number
     * @return number or 
     *         null if number is not valid number (not a nubmer or less than 0)
     */
    function _validateNumber(number) {
        //if ( isNaN(number) || number <= 0 )
        if ( isNaN(number) || number < 0 )
            return null;

        return number;
    }

    /**
     * Calculates number of days between dateFrom and dateTo
     * @param dateFrom (Date)
     * @param dateTo (Date)
     * @return int, number of days
     */
    function _dateDifference(dateFrom, dateTo) {
        return Math.ceil( (dateTo.getTime() - dateFrom.getTime())/statics.ONE_DAY );
    }

    /**
     * @return fraction of the year or 0 if dateFrom >= dateTo
     */
    function _yearFraction(dateFrom, dateTo) {
        var dateDiff = _dateDifference(dateFrom, dateTo);
        if ( dateDiff < 0 )
            return 0;

        return (dateDiff / 365).toFixed(2);
    }

    /**
     * Clear all messages
     */
    function _clearMessages() {
        var item = null;
        for (var i = 0; i < messages.length; i++) {
            item = messages.pop();
            item = null;
        }
        messages = [];
    }
    
    /**
     * Clear all dirty fields, those that have failed validation.
     */
    function _clearDirtyFields() {
        var item = null;
        for (var i = 0; i < dirtyFields.length; i++) {
            item = dirtyFields.pop();
            item = null;
        }
        dirtyFields = [];        
    }

    function _hideElements() {
        document.getElementById(_elemIds.messageElemId).style.display = 'none';
        document.getElementById(_elemIds.resultElemId).style.display = 'none';
    }

    function _updateYield(newYield) {
        var resultElem = document.getElementById(_elemIds.yieldElemId);
        resultElem.innerHTML = newYield;
    }

    /**
     * Increase elem.value by stepValue
     *
     * @param elem - dijit element (ValidationTextBox) whose value should be increased
     * @param stepValue = number, how much the currenct value should be 
     *                    increased
     */
    function _increaseValue(elem, stepValue) {
        var v = elem.getValue();
        //var vv = v * 1;
        var vv = parseFloat(v); 

        var zero = 0;

        var p = _getPrecision(stepValue); 
        if ( p == 0 ) p = 1;
        var vp = _getPrecision(vv); 


        if ( isNaN(v) || (vv-Math.abs(stepValue) <= 0 && stepValue < 0) ) 
            elem.setValue(zero.toFixed(p>vp?p:vp) +"");
        else {
            vv += stepValue;
            elem.setValue(vv.toFixed(p>vp?p:vp) +"");
        }

        // see clearForm method
        if ( elem.labelId )
            dojo.byId(elem.labelId).style.color = 'black';

    }

    /**
     * @param value - number,
     * @return number of decimals after comma in a value
     */
    function _getPrecision(value) {
        var ns = value.toString();
        var tokens = ns.split(".");

        if ( tokens.length == 1 )
            return 0;
        else
            return tokens[1].length;
    }

    /**
     * Show element with elIdShow and hide element with elIdHide
     *
     * @param elIdShow - string, id of the table row to be shown
     * @param elIdHide - string, id of the table row to bi hidden
     */
    function _toggleRows(elIdShow, elIdHide, elIdFadeIn, elIdFadeOut) {

        var fo = dojo.fadeOut({
            node: elIdFadeOut, 
            duration: 500,
            beforeBegin: function() {},
            onEnd: function() {
                dojo.byId(elIdShow).style.display='';
                dojo.byId(elIdHide).style.display='none';
            }
        });
    
 
        var fi = dojo.fadeIn({
            node: elIdFadeIn, 
            duration: 500,
            beforeBegin: function() {
                dojo.byId(elIdFadeIn).style.color=_getFadeColor();
            },
            onEnd: function() {
                dojo.byId(elIdFadeIn).style.color=statics.FADE_DONE_COLOR;
                thisRef.stateChanged();
            }
        });

        dojo.fx.chain([fo, fi]).play();

    
    }


    /**
     * Prepare and show dialog with error messages
     * Produce the following html
     * <div style="margin-left: 10px; margin-bottom: 3px;">messages</div>
     */
    function _showMessages() {
        var messElem = dojo.byId('messageDialogInfoDiv');
        var innerHtml = '';
        var message;

        for (var i = 0; i < messages.length; i++) {
            message = messages[i];
            innerHtml += '<div style="margin-left: 10px; margin-bottom: 3px;';
            
            if ( message[0] == statics.ERROR )
                innerHtml += 'color: ' + statics.ERROR_MSG_COLOR + '"';
            else
                innerHtml += '"';

            innerHtml +='>' + " " + message[1] + '</div>';
        }

        messElem.innerHTML = innerHtml;
        dijit.byId(_elemIds.MSG_DIALOG_ID).show();
    }
    
    /**
     * Go through all form controls and highlight those that have not passed validation and 
     * unhighlight those that have. 
     */
    function _markDirtyFields() {
        //console.log("Entering _markDirtyFields ..." + thisRef.formFields);
        //loop through all fields in the form
        for(var name in thisRef.formFields) {
                //console.log('Processing name: ' + thisRef.formFields[name]);
                nameVal = thisRef.formFields[name];
                isDirty = false;
                for (var i = 0; i < dirtyFields.length; i++) {
                    //console.log("Processing dirty field: " + dirtyFields[i]);
                    if(nameVal == dirtyFields[i]) {
                            isDirty = true;
                            break;
                    }
                }
                
                //check if fields was on dirty list
                if(isDirty) {
                    //console.log('++++ Field is dirty');
                    //highlight
                    _highlightLabel(nameVal, true);
                    _highlightDijitControl(nameVal, true);
                } else {
                   //console.log('---- Field is clean');
                   //remove highligth if one was applied previously
                    _highlightLabel(nameVal, false);
                    _highlightDijitControl(nameVal, false);                   
                }
        }
    }

    /**
     * Calculate default settlement date that as today plus 3 business days
     *
     * @return Date
     */
    function _calcDefaultSettlementDate() {
        return calc.calcDefaultSettlementDate();
    }

    /**
     * Calculate default maturity date as last coupon date + 2 years
     *
     * @return Date
     */
    function _calcDefaultMaturityDate() {
        var d = _calcDefaultLastCouponDate();
        d.setFullYear(d.getFullYear() + 2);
        return d;
    }
    
    /**
     * Calculate default next coupon date as last coupon date + 1 year
     *
     * @return Date
     */
    function _calcDefaultNextCouponDate() {
        var d = _calcDefaultLastCouponDate();
        d.setFullYear(d.getFullYear() + 1);
        return d;

    }

    /**
     * Calculate default last coupon date as first of the current month
     * 
     * @return Date
     */
    function _calcDefaultLastCouponDate() {
        var d = new Date();
        return calc.getFirstDay(d.getFullYear(), d.getMonth());
    }

    /**
     *
     */
    function _getFadeColor() {

        if ( !formLoaded ) {
            formLoaded = true;
            return statics.FADE_DONE_COLOR;
        }
        else  {
            return statics.FADE_IN_COLOR;
        }
    }

    /**
     * Find dijit element with a key and set value for it
     * Used to initialize gui from query string
     *
     * @key, string
     * @value, string
     */
    function _setValue(key, value) {
        var arr, d;
        if ( formDefaults.item(key) )  {  
            nt = formDefaults.item(key)[1];
            switch (nt) {
                case fieldTypes.NUMBER:
                    if ( !isNaN(value) )
                        dijit.byId(key).setValue(value);
                    break;

                case fieldTypes.DATE:
                    arr = value.split(".");
                    d = new Date();
                    
                    //specify radix to avoid 08 and 09 values be treated as octal and return 0 from parseInt
                    d.setFullYear(parseInt(arr[2], 10), parseInt(arr[1], 10)-1,
                                  parseInt(arr[0], 10));
                                  
                    dijit.byId(key).setValue(d);
                    break;

            }
        }


    }



}

/**
 * Bond calcualtor class which does only calculations
 * @param valueKeys (map), map formFields map from BondYieldCalculatorGUI
 * @param resultKeys (map) with keys in the result object
 */
function BondCalculator(valueKeys, resultKeys) {

    this.pubstatics = {
        BACKWARDS   : 1,
        FORWARDS    : 2
    }

    // --------------------------- private variables 

    var days = {
        SUNDAY      : 0,
        MONDAY      : 1,
        TUESDAY     : 2,
        WEDNESDAY   : 3,
        THURSDAY    : 4,
        FRIDAY      : 5,
        SATURDAY    : 6
    }

    // number of days used to calculate default settlement date
    var NO_OF_SETTLEMENT_DAYS = 3;

    var ONE_DAY = 1000 * 60 * 60 * 24;

    // how many digit after comma for results
    var PRECISION = 2;

    // how many digit after comma for calculated yield or clean price 
    var YIELD_PRECISION = 3;

    // form fields names
    var valueKeys = valueKeys;

    var resultKeys = resultKeys;

    // ------------------------ public methods
    /**
     * Calculate all possible value
     * This method assumes that all input values are valid
     *
     * @param values (map) with key 
     * 
     * @return Dictionary with calculated values, keys from resultKeys
     */
    this.calculate = function(values) {
        /*
        console.log('\n\n------------------------------------------------------------\n' +
                    '| Entering calculate ... values: ' + values.toSource());
         */    
        
        // Dictionary which keeps all the calculated values
        var result = new dojox.collections.Dictionary();
      
        var couponAmount = values[valueKeys.NOMINAL_VALUE] * 
                           values[valueKeys.COUPON_RATE]/100;

        var nominalValue = values[valueKeys.NOMINAL_VALUE];

        var F = values[valueKeys.COUPON_FREQUENCY];
        var DCM = values[valueKeys.DAY_COUNT_METHOD];

        var d1m1y1 = values[valueKeys.LAST_COUPON_DATE];
        var d2m2y2 = _minDate(values[valueKeys.SETTLEMENT_DATE],
                            values[valueKeys.MATURITY_DATE]);
        
        var d3m3y3 = _minDate(values[valueKeys.NEXT_COUPON_DATE], 
                              values[valueKeys.MATURITY_DATE]);

        var maturity = values[valueKeys.MATURITY_DATE];

        var setttlement_date = values[valueKeys.SETTLEMENT_DATE];

        var principal;
        var N;          // number of interest bearing days
        
        var A;          // basic accrued interest amount

        // --
        N = _getNumberOfInterestDays(d1m1y1, d2m2y2, DCM);

        A = _getAccruedInterestAmount(couponAmount, N, F, DCM, d1m1y1, d2m2y2, 
                                      d3m3y3, maturity);
                                      
        //console.log("NumberOfInterestDays N: " + N + " AccruedInterestAmount A = " + A);                                      
        
        var whatCalculate = values[valueKeys.WHAT_CALCULATE];
        var g = values[valueKeys.COUPON_RATE];
        var CP = values[valueKeys.CLEAN_PRICE];
        var yield = values[valueKeys.YIELD];
        var redemptionDate = values[valueKeys.MATURITY_DATE]; // not sure, check with Roberto
       
        // calculated yield or clean price
        var YP = _calculateYieldCleanPrice(whatCalculate, DCM, g, CP, yield,
                                           redemptionDate, setttlement_date); 

        // calculate principal
        if ( whatCalculate == whatCalculateSC.YIELD ) {
            principal = values[valueKeys.CLEAN_PRICE] * 
                         values[valueKeys.NOMINAL_VALUE]/100;
        }
        else {
            principal =  YP * values[valueKeys.NOMINAL_VALUE]/100;
        }

        // time to maturity in years
        var time2Maturity = _calculateRemaingLife(maturity, setttlement_date, DCM);

        var duration = _calculateDuration(nominalValue,
                whatCalculate == whatCalculateSC.YIELD
                ? YP : yield,
                g, F, 
                _formatNumber(time2Maturity, PRECISION));

        var modDuration = _calculateModifiedDuration(duration, 
            whatCalculate == whatCalculateSC.YIELD
            ? YP : yield
        );

        result.add(resultKeys.YIELD_PRICE, _formatNumber(YP, YIELD_PRECISION));
        result.add(resultKeys.NOMINAL_VALUE, 
            _formatNumber(values[valueKeys.NOMINAL_VALUE], PRECISION));
        result.add(resultKeys.DURATION, 
            _formatNumber(duration, PRECISION));
        result.add(resultKeys.PRINCIPAL, _formatNumber(principal, PRECISION));
        result.add(resultKeys.MODIFIED_DURATION, _formatNumber(modDuration, PRECISION));
        result.add(resultKeys.DAYS, N);
        result.add(resultKeys.ACC_INTEREST, _formatNumber(A, PRECISION));
        result.add(resultKeys.REMAINING_LIFE, 
            _formatNumber(time2Maturity, PRECISION));
        result.add(resultKeys.ACTUAL_VALUE, 
            _formatNumber(principal + A, PRECISION));

        /*
        console.log("Exiting calculate(): result = " + result.toSource());
        console.log('------------------------------------------------------------\n\n');
        */

        return result;
    }

    /**
     * Calculate default settlement date that as today plus 3 business days
     * @return Date
     */
    this.calcDefaultSettlementDate = function() {
        var date = new Date();
        var i = 1; 

        // T + 3
        while (i <= NO_OF_SETTLEMENT_DAYS) {
            date.setDate(date.getDate() + 1);

            if ( date.getDay() != days.SATURDAY && 
                 date.getDay() != days.SUNDAY )
                 i++;
        }

        return date;
    }

    /**
     * @see _getFirstDay
     */
    this.getFirstDay = function(yyyy, mm) {
        return _getFirstDay(yyyy, mm);
    }

    /**
     * Calculate the next date depending on the coupon frequecny backwards 
     * or forwards starting from the start date
     *
     * @param startDate (Date)
     * @param couponFrequency (string) one of the couponFrequencyCodes
     * @param direction (number) either pubstatics.BACKWARDS or pubstatics.FORWARDS
     * @return new date
     */
    this.calculateNextDate = function(startDate, couponFrequency, direction) {
        var newDate;
        var months = function(couponFrequency) {
            switch (couponFrequency) {
                case couponFrequencyCodes.YEARLY:
                    return 12;
                    break;
                case couponFrequencyCodes.SEMI_ANNUAL:
                    return 6;
                    break;
                case couponFrequencyCodes.QUATERLY:
                    return 3;
                    break;
                case couponFrequencyCodes.MONTHLY:
                    return 1;
                    break;
                case couponFrequencyCodes.THREE_TIMES_PER_YEAR:
                    return 4;
                    break;    
                case couponFrequencyCodes.EVERY_SECOND_YEAR:
                    return (2 * 12);
                    break;
                case couponFrequencyCodes.EVERY_THIRD_YEAR:
                    return (3 * 12);
                    break;
                case couponFrequencyCodes.EVERY_FOURTH_YEAR:
                    return (4 * 12);
                    break;   
                case couponFrequencyCodes.EVERY_FIFTH_YEAR:
                    return (5 * 12);
                    break;
                case couponFrequencyCodes.EVERY_SIXTH_YEAR:
                    return (6 * 12);
                    break;
                case couponFrequencyCodes.EVERY_SEVENTH_YEAR:
                    return (7 * 12);
                    break;
                case couponFrequencyCodes.EVERY_EIGTH_YEAR:
                    return (8 * 12);
                    break;
                case couponFrequencyCodes.EVERY_NINETH_YEAR:
                    return (9 * 12);
                    break;
                case couponFrequencyCodes.EVERY_TENTH_YEAR:
                    return (10 * 12);
                    break;
                case couponFrequencyCodes.EVERY_ELEVENTH_YEAR:
                    return (11 * 12);
                    break;
                case couponFrequencyCodes.EVERY_TWELVE_YEAR:
                    return (12 * 12);
                    break;
                case couponFrequencyCodes.EVERY_THIRTEENTH_YEAR:
                    return (13 * 12);
                    break;
                case couponFrequencyCodes.EVERY_FOURTEENTH_YEAR:
                    return (14 * 12);
                    break;
                case couponFrequencyCodes.EVERY_FIFTHTEENTH_YEAR:
                    return (15 * 12);
                    break; 
                default:
                    return -22;
            }
        }
        
        //calculate by adjusting the months
        if(months != -22) {
                newDate = dojo.date.add(startDate, "month",
                                    (direction = this.pubstatics.BACKWARDS
                                        ? - months(couponFrequency)
                                        : months(couponFrequency)) );
                return newDate;
        }
        
        var days = function(couponFrequency) {
            switch (couponFrequency) {
                case couponFrequencyCodes.ANYTIME:
                    //it could happen now if it is anytime
                    return 0;
                    break;
                case couponFrequencyCodes.FIVE_TIMES_PER_YEAR:
                    return 72;
                    break;
                case couponFrequencyCodes.SIX_TIMES_PER_YEAR:
                    return 60;
                    break;
                case couponFrequencyCodes.SEVEN_TIMES_PER_YEAR:
                    return 51;
                    break;
                case couponFrequencyCodes.EIGHT_TIMES_PER_YEAR:
                    return 45;
                    break;
                case couponFrequencyCodes.NINE_TIMES_PER_YEAR:
                    return 40;
                    break;
                case couponFrequencyCodes.TEN_TIMES_PER_YEAR:
                    return 36;
                    break;
                case couponFrequencyCodes.ELEVEN_TIMES_PER_YEAR:
                    return 33;
                    break;
                //it is not one of the default 
                default:
                    return -23;
            }
        }
        
        //calculate by adjusting the days
        if(days != -23) {
                newDate = dojo.date.add(startDate, "day",
                                    (direction = this.pubstatics.BACKWARDS
                                        ? - days(couponFrequency)
                                        : days(couponFrequency)) );
        }
        
        return newDate;
    }
    
    // ------------------------ private methods

    /**
     * Calculate number of accrued interest days based on d1m1y1
     * and d2m2y2
     * 
     * @param d1m1y1 (Date) date from
     * @param d2m2y2 (Date) date to
     * @param countMethod day count method, one of the dayCountMethodCodes
     *
     * @return number of days (number)
     */
     function _getNumberOfInterestDays(d1m1y1, d2m2y2, countMethod) {
             
        //console.log("Entering _getNumberOfInterestDays(): d1m1y1 = " + d1m1y1 + " d2m2y2 = " + d2m2y2 + " countMethod = " + countMethod);
             
        var n;  // number of days
        var d1, d1x, m1, y1;
        var d2, d2x, m2, y2;

        d1 = d1m1y1.getDate();
        m1 = d1m1y1.getMonth() + 1;
        y1 = d1m1y1.getFullYear();

        d2 = d2m2y2.getDate();
        m2 = d2m2y2.getMonth() + 1;
        y2 = d2m2y2.getFullYear();


        switch (countMethod) {

            case dayCountMethodCodes.GERMAN:
                d1x = _dayNumberGerman(d1, d1m1y1);
                d2x = _dayNumberGerman(d2, d2m2y2);
                n = (d2x - d1x) + 30 * (m2 - m1) + 360 * (y2 - y1);
                break;

            case dayCountMethodCodes.SPECIAL_GERMAN:
                d1x = d1 == 31 ? 30 : d1;
                d2x = d2 == 31 ? 30 : d2;
                n = (d2x - d1x) + 30 * (m2 - m1) + 360 * (y2 - y1);
                break;

            case dayCountMethodCodes.ENGLISH:
            case dayCountMethodCodes.FRENCH:
            case dayCountMethodCodes.ISMA_YEAR:
            case dayCountMethodCodes.ISMA_99_NORMAL:
            case dayCountMethodCodes.ISMA_99_ULTMO:
                n = _dateDifference(d1m1y1, d2m2y2);
                break;

            case dayCountMethodCodes.US:
                d1x = d1;
                d2x = d2;
                if ( _endFeb(d1m1y1) && _endFeb(d2m2y2) )
                    d2x = 30;
                if ( _endFeb(d1m1y1) )
                    d1x = 30;
                if ( d2x == 31 && d1x >= 30 )
                    d2x = 30;
                if ( d1x == 31 )
                    d1x = 30;
                n = (d2x - d1x) + 30 * (m2 - m1) + 360 * (y2 - y1);
                break;
            case dayCountMethodCodes.FLAT:
                n = 0;
                break;
            default:
                alert(" wrong day count method ");
        }

        //console.log("Exiting _getNumberOfInterestDays(): n = " + n)
        return n;
    }

    /**
     * Calculate accrued interest amount
     *
     * @param couponAmount (number)
     * @param N number of interest days (number)
     * @param F coupon frequency (number)
     * @param DCM day count method
     * @param d1m1y1 start of the interest period (Date)
     * @param d2m2y2 settlement date (Date)
     * @param d3m3y3 end of the interest period (Date)
     * @param maturity maturity date (Date)
     *
     * 
     * @return accrued interest amount (number)
     */
    function _getAccruedInterestAmount(couponAmount, N, F, DCM, d1m1y1, d2m2y2,
                                       d3m3y3, maturity) {
        /*
        console.log("Entering _getAccruedInterestAmount(): " +
                        " couponAmount = " + couponAmount +
                        " N = " + N +
                        " F = " + F +
                        " DCM = " + DCM +
                        " d1m1y1 = " + d1m1y1 +
                        " d2m2y2 = " + d2m2y2 +
                        " d3m3y3 = " + d3m3y3 +
                        " maturity = " + maturity);
                       
                        
        console.log("Exiting _getAccruedInterestAmount(): " + 
                        (couponAmount * _getAccruedInterestFactor(N, F, DCM, d1m1y1, d2m2y2, d3m3y3, maturity)));
        */                       
        return couponAmount * _getAccruedInterestFactor(N, F, DCM, d1m1y1, d2m2y2,
                                                        d3m3y3, maturity);
    }

    /**
     * Calculate basic accrued interest factor
     *
     * @param N number of interest days (number)
     * @param F coupon frequency (number)
     * @param DCM day count method
     * @param d1m1y1 start of the interest periond (Date)
     * @param d2m2y2 settlement date (Date)
     * @param d3m3y3 end of the interest periond (Date)
     * @param maturity maturity date (Date)
     *
     * @return basic accrued interes factor (number)
     */
    function _getAccruedInterestFactor(N, F, DCM, d1m1y1, d2m2y2, d3m3y3, maturity) {
        /*    
        console.log("Entering _getAccruedInterestFactor():" +
                        " N = " + N +
                        " F = " + F +
                        " DCM = " + DCM +
                        " d1m1y1 = " + d1m1y1 +
                        " d2m2y2 = " + d2m2y2 +
                        " d3m3y3 = " + d3m3y3 +
                        " maturity = " + maturity);
        */
        var aiFactor;
        var Y;

        switch (DCM) {
            case dayCountMethodCodes.GERMAN:
            case dayCountMethodCodes.SPECIAL_GERMAN:
            case dayCountMethodCodes.FRENCH:
            case dayCountMethodCodes.US:
                aiFactor = N / 360;
                break;
            case dayCountMethodCodes.ENGLISH:
                aiFactor = N / 365;
                break;
            case dayCountMethodCodes.ISMA_YEAR:
                Y = _calcY(F, d1m1y1, d3m3y3);
                aiFactor = N / Y;
                break;
            case dayCountMethodCodes.ISMA_99_NORMAL:
            case dayCountMethodCodes.ISMA_99_ULTMO:
                aiFactor = _calcAIFactorISMA99(N, F, DCM, d1m1y1, d2m2y2, d3m3y3, maturity);
                break;
            case dayCountMethodCodes.FLAT:
                aiFactor = 0;
                break;

        }
        
        //console.log("Exiting _getAccruedInterestFactor(): aiFactor = " + aiFactor);
        
        return aiFactor;
    }

    /** 
     * Calculate Y value used by ISMA-Year method to calculate ai factor
     *
     * @param F coupon frequency (number)
     * @param d1m1y1 start of the interest period (Date)
     * @param d3m3y3 end of the interest period (Date)
     * 
     * @return number
     */
    function _calcY(F, d1m1y1, d3m3y3) {
        var y, i, d;
        var y1 = d1m1y1.getFullYear();
        var y3 = d3m3y3.getFullYear();

        if ( F == couponFrequencyCodes.YEARLY ) {
            i = _dateDifference(d1m1y1, d3m3y3)
            if ( i == 365 || i == 366 )
                y = i;
            else {
                y = 365;
                for (i = y1; i <= y3; i++) {
                    d = _getLastFeb(i);
                    if ( d.getDate() == 29 && d > d1m1y1 && d <= d3m3y3 ) {
                        y = 366;
                        break;
                    }
                }
            }
        }
        else {
            if ( _isLeapYear(d3m3y3.getFullYear()) )
                y = 366;
            else
                y = 365;
        }
        return y;
    }

    /**
     * Calculate accrued interest factor for ISMA 99 method
     *
     * @param N number of interest days (number)
     * @param F coupon frequency (number)
     * @param DCM day count method
     * @param d1m1y1 start of the interest periond (Date)
     * @param d2m2y2 settlement date (Date)
     * @param d3m3y3 end of the interest periond (Date)
     * @param maturity maturity date (Date)
     *
     * @retrun number
     */
    function _calcAIFactorISMA99(N, F, DCM, d1m1y1, d2m2y2, d3m3y3, maturity) {
        var aifactor;

        var d1, m1, y3, d3, m3, y3;

        d1 = d1m1y1.getDate();
        m1 = d1m1y1.getMonth() + 1;
        y1 = d1m1y1.getFullYear();

        d3 = d3m3y3.getDate();
        m3 = d3m3y3.getMonth() + 1;
        y3 = d3m3y3.getFullYear();


        var C;

        // because possible coupon frequency is 1,2,3,12
        var periodic = true;

        var regular = false;

        // because period = true
        var L = 12 / F;     // regular period length in months
        var Fx = F;     // applicable coupon frequency

        
        // check whether reqular or not
        if ( ((y3 - y1) * 12 + (m3 - m1)) == L ) {
            if ( DCM = dayCountMethodCodes.ISMA_99_NORMAL ) {
                if ( d1 == d3 ) 
                    regular = true;
                // check situation when 31 of the given month
                // does not exist
                else if ( _nextMonth(y1, m1, d3) && _lastDayOfMonth(d1m1y1) )
                    regular = true;
                else if ( _nextMonth(y3, m3, d1) && _lastDayOfMonth(d3m3y3) )
                    regular = true;
            }
            else  { // isma ultimo
                if ( _lastDayOfMonth(d1m1y1) && _lastDayOfMonth(d3m3y3) )
                    regular = true;
            }
        }



        if ( regular ) {
            C = _dateDifference(d1m1y1, d3m3y3);
            aifactor =  ( 1 / Fx) * (N / C);
        }
        else {          // generate notional periods
            var direction;
            var anchor;         // start date for notional periods
            var ad, am, ay;     // anchor day, month, year 
            var target;         // end of the notional period loop

            var wy, wm;         // "working dates" in notional period loop
            var currc, i, nextc, nx, cx;          // temp values
            
            
            aifactor = 0;

            if ( d3m3y3 == maturity ) {     // forwards
                direction = 1;
                anchor = d1m1y1;
                ay = y1; am = m1; ad = d1;
                target = d3m3y3;
            }
            else {                          // backwards
                direction = -1;
                anchor = d3m3y3;
                ay = y3; am = m3; ad = d3;
                target = d1m1y1;
            }
      
            currc = anchor;
            i = 0;

            while (direction * (currc - target) < 0) {
                i = i + direction;
                wy = _getNewYear(ay, am, (i * L));
                wm = _getNewMonth(am, (i * L));

                if ( DCM == dayCountMethodCodes.ISMA_99_NORMAL )  {
                    if ( _nextMonth(wy, wm, ad) )
                        nextc = _getLastDay(wy, wm);
                    else {
                        nextc = new Date();
                        nextc.setFullYear(wy, wm, ad);
                    }
                }
                else {
                    nextc = _getLastDay(wy, wm);
                }

                nx = _min(d2m2y2, _max(nextc, currc)) - _max(d1m1y1, _min(currc, nextc)); 
                cx = direction * (nextc - currc);

                if ( nx > 0 )
                    aifactor = aifactor + (nx / cx);

                currc = nextc;
            }
            
            aifactor = aifactor / Fx;
        }

        return aifactor;
    }

    /**
     * Return min of the two dates
     *
     * @param date1 (Date)
     * @param date2 (Date)
     *
     * @return date1 if date1 < date2
     *         date2 if date1 >= date2
     */
    function _minDate(date1, date2) {
        return date1 < date2 ? date1 : date2;
    }

    /**
     * Get day number from date for GERMAN method
     * @param dn (number) day number
     * @param date (Date)
     @ return day number 0-30
     */
    function _dayNumberGerman(dn, date) {
        if ( dn == 31 )
            return 30;
        else if ( _endFeb(date) )
            return 30;
        else
            return dn;
    }

    /**
     * Check whether date is the last day of february
     * @param date (Date)
     * @return true is date is the last day of february
     *         false otherwise
     */
    function _endFeb(date) {
        var d = new Date();
        d.setMonth(date.getMonth());
        d.setFullYear(date.getFullYear());
        d.setDate(date.getDate() + 1);
        
        if ( d.getDate() == 1 && d.getMonth() + 1 == 3 )
            return true;
        else
            return false;
    }

    /**
     * Calculates number of days between dateFrom and dateTo
     * @param dateFrom (Date)
     * @param dateTo (Date)
     * @return int, number of days
     */
    function _dateDifference(dateFrom, dateTo) {
        return Math.ceil( (dateTo.getTime() - dateFrom.getTime())/ONE_DAY );
    }

    /**
     * Return number with fixed number of digit after comma
     * @param value (number)
     */
    function _formatNumber(value, precision) {
        if ( typeof(value) == 'string' )
            return parseFloat(value).toFixed(precision);
        else
            return value.toFixed(precision);
    }

    /**
     * Check if year is a leap year
     * @param year format yyyy (number)
     * @return true if year is leap year 
     *         false otherwise
     */
    function _isLeapYear(year) {
        var isLeapYear = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
        return isLeapYear;
    }

    /**
     * Calculate day representing last day of Feb for a given year
     *
     * @param year (number) yyyy format
     * @return (Date) that represent last day of Feb for a given year
     */
    function _getLastFeb(year) {
        var d = new Date();
        d.setFullYear(year, 2, 1)
        d.setDate(d.getDate() - 1);
        return d;
    }
   
    /**
     * Check if 1st of a given month, m1, plus d3 day is still 
     * in the same month
     *
     * @param y1 year yyyy (number)
     * @param m1 month (number) (0-11) 
     * @param d3 day (number) (1-31)
     *
     * @return true if 1st of m1 plus d3 days is NOT in the same month
     *         false otherwise
     */
    function _nextMonth(y1, m1, d3) {
        var d = new Date();
        d.setFullYear(y1, m1, 1);
        d.setDate(d.getDate() + d3);
        return d.getMonth() != m1;
    }
   
    /**
     * Check if date is the last day of a month
     *
     * @param date (Date)
     * 
     * @return true is date is the last day of a month
     *         false otherwise
     */
    function _lastDayOfMonth(date) {
        var d = new Date();
        d.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
        d.setDate(d.getDate() + 1);
        return d.getDate() == 1;
    }

    /**
     * @return date (Date) representing last day of the yyyy.mm
     */
    function _getLastDay(yyyy, mm) {
        var d = new Date();
        d.setFullYear(yyyy, mm + 1, 1);
        d.setDate(d.getDate() - 1);
        return d;
    }

    /**
     * @return date (Date) representing first day of the yyyy.mm
     */
    function _getFirstDay(yyyy, mm) {
        var d = new Date();
        d.setFullYear(yyyy, mm, 1);
        return d;
    }


    /**
     * Get new year starting from mm.yyyy going +/- num months
     * @param yyyy year (number)
     * @param mm month (number)
     * @param num number of months (number)
     * @return year yyyy (number)
     */
    function _getNewYear(yyyy, mm, num) {
        var nm = mm + num;
        var ny;

        if ( nm > 0 ) {
           ny = yyyy + _divInt((nm-1), 12); 
        }
        else {
            ny = yyyy - 1 + _divInt(nm, 12); 
        }
        return ny;
    }
   
    /**
     * Get new month +/- num months
     * @param mm (number) month
     * @param num (number) number of month to be added/deleted
     * @return new month number (number)
     */
    function _getNewMonth(mm, num) {
        var nm = mm + num;

        if ( nm > 0 ) 
            return (nm - 1) % 12 + 1;
        else if ( nm == 0 )
            return nm;
        else
            return 12 + (nm % 12);    
    }
    
    /**
     * Divide to integers int1 / int2
     * @param int1 (number) integer 1
     * @param int2 (nubmer) interger 2
     * @return int1/int2 (number)
     */
    function _divInt(int1, int2) {
        var reminder = int1 % int2;
        var quotient = (int1 - reminder) / int2;

        if ( quotient >= 0 )
            quotient = Math.floor(quotient);
        else
            quotient = Math.ceil(quotient);

        return quotient;
    }

    /**
     * @param date1 (Date)
     * @param date2 (Date)
     * @return max date1 and date2
     */
    function _max(date1, date2) {
        return date1 > date2 ? date1 : date2;
    }

    /**
     * @param date1 (Date)
     * @param date2 (Date)
     * @return min date1 and date2
     */
    function _min(date1, date2) {
        return date1 < date2 ? date1 : date2;
    }
   
    /**
     *
     * @param whatCalculate (string) yield or clean price
     * @param DCM day count mehtod (string)
     * @param g (number) coupon rate 
     * @param CP (number) clean price
     * @param yield (number) yield
     * @redemptionDate (Date) redemption date 
     *
     * @return yield or clean price (number)
     */
    function _calculateYieldCleanPrice(whatCalculate, DCM, g, CP, yield,
                                       redemptionDate, setttlement_date) {
        
        /*
        console.log("Entering _calculateYieldCleanPrice():" +
                        " whatCalculate = " + 1 +
                        " DCM = " + DCM +
                        " g = " + g +
                        " CP = " + CP +
                        " yield = " + yield +
                        " redemptionDate = " + redemptionDate +
                        " setttlement_date = " + setttlement_date)
        */
        var ycp;


        //var d1m1y1 = new Date();        // today
        //d1m1y1.setHours(0,0,0,0);

        var d1m1y1 = setttlement_date;        // today
        var d2m2y2 = redemptionDate;

        var c = 100;
        var gn = parseFloat(g);
        var cp = parseFloat(CP);

        var L,D,Y;

        var yvalues = new dojox.collections.Dictionary();
        yvalues.add(dayCountMethodCodes.GERMAN, 360);
        yvalues.add(dayCountMethodCodes.SPECIAL_GERMAN, 360);
        yvalues.add(dayCountMethodCodes.ENGLISH, 365);
        yvalues.add(dayCountMethodCodes.FRENCH, 360);
        yvalues.add(dayCountMethodCodes.US, 360);

        // not used
        yvalues.add(dayCountMethodCodes.ISMA_YEAR, 365);
        yvalues.add(dayCountMethodCodes.ISMA_99_NORMAL, 365);
        yvalues.add(dayCountMethodCodes.ISMA_99_ULTMO, 365);
        
        // redemption date reached use current yield to maturity formula
        if ( d1m1y1 >= d2m2y2 ) {
            switch (whatCalculate) {
                case whatCalculateSC.YIELD:
                    ycp = g / CP * 100;
                    break;
                case whatCalculateSC.PRICE:
                    ycp = g / yield * 100;
                    break;
            }
        }
        // redemption date not reached 
        else {  
            D = _getNumberOfInterestDays(d1m1y1, d2m2y2, DCM);
            Y = yvalues.item(DCM);

            switch (DCM) {
                case dayCountMethodCodes.GERMAN:
                case dayCountMethodCodes.SPECIAL_GERMAN:
                case dayCountMethodCodes.ENGLISH:
                case dayCountMethodCodes.FRENCH:
                case dayCountMethodCodes.US:
                    L = D / Y;
                    break;

                case dayCountMethodCodes.ISMA_YEAR:
                case dayCountMethodCodes.ISMA_99_NORMAL:
                case dayCountMethodCodes.ISMA_99_ULTMO:
                    L = _calculateLISMA(d1m1y1, d2m2y2, DCM);
                    break;

                default:
                    L = 0;
            }
       

            // yield or clean price 
            if ( whatCalculate == whatCalculateSC.YIELD ) {
                ycp = (gn + ((c - cp) / L)) / ((c + cp) / 2) * 100;
            }
            else if ( whatCalculate == whatCalculateSC.PRICE ) {
                var LY_200 = L * yield / 200;
                /*
                alert('L: ' + L + '\n' 
                      + 'LY_200: ' + LY_200 + '\n'
                      + 'Lg : ' + (L*gn));
                */
                ycp = ((L * gn) + c * (1 - LY_200) ) / (1 + LY_200);
            }
            else {
                ycp = -100000;
            }

            /* 
            alert(
                 ' g: ' + gn + ' ' +typeof(gn) + '\n' 
               + ' CP: ' + cp + ' ' +typeof(cp) + '\n'
               + ' C: ' + c + ' ' + typeof(c) + '\n'
               + ' yield: ' + ycp + ' ' +typeof(ycp) + '\n'
               + ' L: ' + L + ' ' + typeof(L) + '\n'
               + ' D: ' + D + ' ' + typeof(D) + '\n'
               + ' Y: ' + Y + ' ' + typeof(Y) + '\n'
               + (gn + cp)

            );
            */
        }
        
        return ycp;
    }

    /**
     * Calculate L for yield form ISMA methods
     *
     * @param d1m1y1 (Date)
     * @param d2m2y2 (Date)
     * @param DCM day count method 
     * 
     * @return (number) L
     */
    function _calculateLISMA(d1m1y1, d2m2y2, DCM) {
        var years = 0, part = 0;
        var d1 = new Date(), d11 = new Date();
        var Y, L;
        var lf; // last feb date

        d1.setHours(0,0,0,0);
        d11.setHours(0,0,0,0);

        d1.setFullYear(d2m2y2.getFullYear(), d2m2y2.getMonth(), d2m2y2.getDate()); 
        for (;;) {

            d11.setFullYear(d1.getFullYear(), d1.getMonth(), d1.getDate());
            d1.setFullYear(d2m2y2.getFullYear() - (years + 1));


            if ( DCM == dayCountMethodCodes.ISMA_99_ULTMO && _lastDayOfMonth(d2m2y2) 
                    && d2m2y2.getMonth() == 1 ) {
                lf = _getLastFeb(d1.getFullYear());
                d1.setFullYear(lf.getFullYear(), lf.getMonth(), lf.getDate());
            }

            if ( d1m1y1 > d1 )
                break;

            years++;

        }

        Y = _dateDifference(d1, d11);
        part = _dateDifference(d1m1y1, d11);

        L = years + (part/Y);    

        return L;
    }

    /**
     * Calculate remaining life of the bond
     * @param maturity (Date) maturity date
     * @param settlement_date (Date) 
     * @param DCM day count method 
     * @return number as remainig life of a bond
     */
    function _calculateRemaingLife(maturity, settlement_date, DCM) {
            
        /*
        console.log(" _calculateRemainingLife():" +
                        " maturity = " + maturity +
                        " settlement_date = " + settlement_date +
                        " DCM = " + DCM);
        */
    
        // remaining life
        var remLife;
        
        // number of days to maturity
        var dtm = _getNumberOfInterestDays(settlement_date, maturity, DCM);

        switch (DCM) {
            case dayCountMethodCodes.GERMAN:
            case dayCountMethodCodes.SPECIAL_GERMAN:
            case dayCountMethodCodes.FRENCH:
            case dayCountMethodCodes.US:
                remLife = dtm / 360;
                break;
            case dayCountMethodCodes.ENGLISH:
                remLife = dtm / 365;
                break;

            case dayCountMethodCodes.ISMA_YEAR:
            case dayCountMethodCodes.ISMA_99_NORMAL:
            case dayCountMethodCodes.ISMA_99_ULTMO:
                remLife = _calcRemLifeISMA(settlement_date, maturity);
                break;

            default:
                remLife = 0.0;
        }
        
        //console.log("Exiting _calculateRemainingLife(): remLife = " + remLife);

        return remLife;
    }

    
    /**
     * Calculate remaining life of a bond for ISMA methods
     * 
     * @param settlement_date (Date)
     * @param maturity (Date)
     * 
     * @return (number) number of years between settlement_date and maturity 
     *
     */
    function _calcRemLifeISMA(settlement_date, maturity) {

        // remaining life
        var l;

        var d = new Date();
        d.setHours(0, 0, 0, 0);
        var d2 = new Date();
        d2.setHours(0, 0, 0, 0);

        d.setFullYear(maturity.getFullYear(), maturity.getMonth(),
                      maturity.getDate());
       
        for ( var i = 0; ;i++ ) {
            d.setFullYear(d.getFullYear() - 1);
            if ( settlement_date >= d )
                break;
        }

        d2.setFullYear(d.getFullYear() + 1, d.getMonth(),
                       d.getDate()); 

        var ndy = _dateDifference(d, d2);       // nubmer of days in the current year
        var nd = _dateDifference(settlement_date, d2); // from settlement date to the end of the currenct period

        l = i + (nd / ndy);

        return l;

    }

    /**
     * Calculate Macaulay's duration of the bond
     * 
     * @param nominalValue (number) nominal value of the bond
     * @param yield (number) bond yield on percent
     * @param couponRate (number) bond coupon rate in percent
     * @param couponFrequency (number)
     * @param timeToMaturity (number) time to maturity in year
     *
     */
    function _calculateDuration(nominalValue, yield, couponRate, couponFrequency, timeToMaturity) {
        /*    
        console.log("Entering _calculateDuration():" +
                        " nominalValue = " + nominalValue +
                        " yield = " + yield + 
                        " couponRate = " + couponRate +
                        " couponFrequency = " + couponFrequency +
                        " timeToMaturity = " + timeToMaturity);
        */
        
        //if the frequency is every couple of year calculate it what it is for a year
            
        var m = _convertToNumberNonNumericCouponFrequency(couponFrequency), n = timeToMaturity;
        var i = couponRate / 100;
        var F = nominalValue;
        var ytm = yield / 100;

        /*
        console.log('ytm: ' + ytm + '\n'
              + 'F: ' + F + '\n'
              + 'i: ' + i + '\n'
              + 'n: ' + n + '\n'
              + 'm: ' + m + '\n'
                );
         */

        var pvb = 0;            // present value of the bond
        var md = 0;             // macaulay duration

        var nm = n * m;
        var sumpart;
        var t = n - Math.floor(n);

        if ( !t ) { 
            var fullPeriod = true;
            t = 1;
        }

        //alert('t ' + t);
        //alert('n - Math.floor(n): ' + (n - Math.floor(n)));
        for (var j = 1; j <= Math.ceil(nm); j++) {
            sumpart = ((i * F / m )) / Math.pow((1 + (ytm/m)), t);
            //alert(t + ' ' + sumpart);
            pvb += sumpart;
            md += t * sumpart;

            if ( fullPeriod )
                t++;
            else
                t +=  1 / m;

        }

        sumpart = F / Math.pow((1 + (ytm/m)), nm);

        pvb += sumpart;
        md += nm * sumpart;

        md = (md / pvb) / m;

        //console.log('pvb: ' + pvb + ' md: ' + md);

        return md;
    }
    
    /**
     * Some bonds pay dividends not per year but every 2, 3, 4 etc years which means that they have
     * no occurrence within a year, but to calculate duration we need a number. 
     */
     function _convertToNumberNonNumericCouponFrequency(/* String */ couponFrequency) {
             
             //console.log("Entering _convertToNumberNonNumericCouponFrequency(): couponFrequency = " + couponFrequency);
             
             var out;
             switch(couponFrequency) {
                     case couponFrequencyCodes.EVERY_SECOND_YEAR:
                        out = 1 / 2;
                        break;
                     case couponFrequencyCodes.EVERY_THIRD_YEAR:
                        out = 1 / 3;
                        break;
                     case couponFrequencyCodes.EVERY_FOURTH_YEAR:
                        out = 1 / 4;
                        break;
                     case couponFrequencyCodes.EVERY_FIFTH_YEAR:
                        out = 1 / 5;
                        break;
                     case couponFrequencyCodes.EVERY_SIXTH_YEAR:
                        out = 1 / 6;
                        break;
                     case couponFrequencyCodes.EVERY_SEVENTH_YEAR:
                        out = 1 / 7;
                        break;
                     case couponFrequencyCodes.EVERY_EIGTH_YEAR:
                        out = 1 / 8;
                        break;
                     case couponFrequencyCodes.EVERY_NINETH_YEAR:
                        out = 1 / 9;
                        break;
                     case couponFrequencyCodes.EVERY_TENTH_YEAR:
                        out = 1 / 10;
                        break;
                     case couponFrequencyCodes.EVERY_ELEVENTH_YEAR:
                        out = 1 / 11;
                        break;
                     case couponFrequencyCodes.EVERY_TWELVE_YEAR:
                        out = 1 / 12;
                        break;
                     case couponFrequencyCodes.EVERY_THIRTEENTH_YEAR:
                        out = 1 / 13;
                        break;
                     case couponFrequencyCodes.EVERY_FOURTEENTH_YEAR:
                        out = 1 / 14;
                        break;
                     case couponFrequencyCodes.EVERY_FIFTHTEENTH_YEAR:
                        out = 1 / 15;
                        break;
                     case couponFrequencyCodes.ANYTIME:                        
                        out = 1; //make this annual
                        break;
                     case couponFrequencyCodes.IRREGULAR_RECCURENCE:
                        out = 1; //make this annual
                        break;
                     case couponFrequencyCodes.NO_OCCURENCE:
                        out = 0;
                        break;
                     default:
                        out = couponFrequency;
             }
             
             //console.log("Entering _convertToNumberNonNumericCouponFrequency(): out = " + out);
             return out;
     }

    /**
     * Calculate modified duration as duration/(1+yield);
     * 
     * @param duration (nubmer), bond duration
     * @param yield (number), bond yield in percent
     *
     * @return number, modified duration
     */
    function _calculateModifiedDuration(duration, yield) {
        /*
        console.log("Entering _calculateModifiedDuration():" +
                        " duration = " + duration +
                        " yield = " + yield);
        console.log("Exiting _calculateModifiedDuration(): " + (duration / (1 + (yield / 100))));
        */
        return duration / (1 + (yield / 100));
    }

}
