// following are for using spidermonkey from command line.
// 
// see: http://www.thefrontside.net/blog/learning_javascript_from_the_command_line
// 
/**
options( 'strict' );
options( 'werror' );
load( 'jquery-1.3.2.min.js' );
load( 'jquery.flot.js' );
load( 'query-ui-1.7.2.custom.min.js' );
load( 'utility_functions.js' );
load( 'selectToUISlider.jQuery.js' );
var alert = function( message ){ 
        print( message ); 
}
*/

var py = 1; // price of good y fixed at 1 and not changed anywhere
var demandCurvePoints = new Array();
var demandCurveSmoothed = new Array();
var budgetConstraints = new Array();
var budgetConstraintOptima = new Array();

var indifferenceCurves = new Array();
var utility = getUtilityFunction( 0.8, 0.5 );

/**
 * optimal x,y consumption given our current utility function (which
 * is the global variable utility).
 * 
 * @param px - price of good 1
 * @param py - price of good 2 (in this sim this is always 1)
 * @param income - money income
 * @return array with [0] = optimal x [1] = optimal y
*/
function getOptimum(  px, py, income ){
        var px_py = px/py;
        var dx = utility.getDemand( px_py, income );
        var dy = (income - dx*px)/py;
        return [ dx, dy ];              
}

/**
 * Simple callback for the sort routines.
 *@param a - array
 *@param b - array
 *@return a[0] - b[0] - so positive if a[0] > b[0] and so on.
*/
function compareFirstArrayValues( a, b ){
        return a[0] - b[0];
}        

/**
 * Create a single indifference curve including the point optimum
 * and push it on to the list of indifference curves
 * @param optimum x,y pair giving the optimum combo for 
 *   some price level
*/
function addIndifferenceCurve( optimum ){
        var u = utility.getUtility( optimum[0], optimum[1] );
        /**
         * Check if we already have this one
        */
        for( p in indifferenceCurves ){
             var ic = indifferenceCurves[p];
             if( Math.abs( ic.utility, u ) < 0.01 ){
                     return;
             }
        }
        
        ic = {
            data:   new Array( optimum ),
            lines:  { show: true, fill: false, lineWidth:0.3 },
            color:  "lightgrey",
            utility: u
        }
        var x = 0.1;
        do{
                y = utility.getIndifferentY( u, x );
                ic.data.push( [ x, y ] );
                x += 0.01;
        } while ( x < 20.0 );
        ic.data.sort( compareFirstArrayValues );
        indifferenceCurves.push( ic );
}

/**
 * FIXME we only need the first and last points here
 * Given the selected pounts on the demand curve, return a set of points that
 * we can use to produce a smoothed curve through them.
 *
 * @param demandPoints a Data class dataset with the points, the income for that demand, and colo[r].
 * @return an array of (price,demand for x) points 0.05 x's apart on the demand curve
*/
function smoothOutDemandCurve( demandPoints ){
        var endP = demandPoints.data[0];
        var len = demandPoints.data.length-1;
        var startP = demandPoints.data[ len ];
        data = new Array();
        data.push( endP );
        var px = startP[1];
        var x_intercept = 0;
        var y_intercept = 0;
        var optimum  = 0;
        do{
                px += 0.01;
                if( px < endP[1] ){
                        x_intercept = demandPoints.income / px;
                        y_intercept = demandPoints.income / py; // py1 == 1 implicitly
                        optimum  = getOptimum( px, py, demandPoints.income );
                        point = [ optimum[0], px/py ];
                        data.push( point );
                }
        } while( ( px ) < endP[1] );
        data.push( startP );
        return data.sort( compareFirstArrayValues );
}

/**
 * Add this point to the demand curve points. If there is now more
 * than one point, add a smoothed demand curve between the 
 * two extreme points to the set of such curves, 
 * for some optimum.
 * 
 * @param income - current income level
 * @param px - current price of x
 * @param optimum - (x,y) pair from utility function
 * @param color - 
*/
function addToDemandCurves( income, px, optimum, color ){
        px_py = px / py;
        point = [ optimum[0], px_py ];
        for( i in demandCurvePoints ){
                dcPoints = demandCurvePoints[i];
                dcSmooth = demandCurveSmoothed[i];
                if( dcPoints.income == income ){
                        dcPoints.data.push( point );
                        dcPoints.data.sort( compareFirstArrayValues );
                        if( dcPoints.data.length > 1 ){
                                dcSmooth.data = smoothOutDemandCurve( dcPoints );
                        }
                        return;
                }
        }
        dc = {
            data:  new Array( point ),
            points: { show: true },
            color:  color,
            income: income
        }
        demandCurvePoints.push( dc );
        dc = {
            data:  new Array(),
            lines:  { show: true, fill:false, lineWidth: 1  },
            color:  color,
            income: income,
            label:  "&#163;"+income
        }
        demandCurveSmoothed.push( dc )
}

function mergeDemand(){
        out = new Array();
        for( i in demandCurvePoints ){
                out.push( demandCurveSmoothed[i] );        
                out.push( demandCurvePoints[i] );        
        }
        return out;        
}

function mergeBudgetConstraints(){
       out = new Array();
       for( i in budgetConstraints ){
               out.push( budgetConstraints[i] );
               out.push( budgetConstraintOptima[i] );
               out.push( indifferenceCurves[i] );
       }
       return out;
}
        
function getColourForIncome( income ){
        for( i in budgetConstraints ){
                bc = budgetConstraints[i];
                if( bc.income == income ){
                        return bc.color;       
                }
        }
        color = budgetConstraints.length;
        return color; 
}

function makeBCAndDemand( income, px, drawBC ){
        var x_intercept = income / px;
        var y_intercept = income / py; // py1 == 1 implicitly
        var optimum  = getOptimum( px, py, income );
        var bc = new Array( [ 0, y_intercept ], optimum, [ x_intercept, 0 ] );
        var color = getColourForIncome( income );
        addToDemandCurves( income, px, optimum, color );
        if( drawBC ){
                var a = {
                    income: income,
                    data: bc,
                    lines: { show: true, fill: false, lineWidth: 1 },
                    color: color,
                    px:  px,
                    py:  py
                };
                var o = {
                    income: income,
                    data: new Array( optimum ),
                    points: { show: true },
                    color: color,
                };
                budgetConstraints.push( a );       
                budgetConstraintOptima.push( o );
                addIndifferenceCurve( optimum );
        }
}

/**
 * For when the utility function parameters have changed.
 * Keep the budget constraints but redraw everything else.
 *
 * @param e - elasticity of substitution
 * @param a - share parameter 
*/
function redrawIndifferenceAndDemand( e, a ){
        // idiot check for no change
        if(( utility.e == e ) && ( utility.a == a )){
                return;       
        }
        utility = getUtilityFunction( e, a );
        demandCurvePoints = new Array();
        demandCurveSmoothed = new Array();
        budgetConstraintOptima = new Array();
        indifferenceCurves = new Array();
        for( b in budgetConstraints ){
                var bc = budgetConstraints[b];
                var optimum  = getOptimum( bc.px, bc.py, bc.income );
                var o = {
                    income: income,
                    data:   new Array( optimum ),
                    points: { show: true },
                    color:  bc.color
                };
                addToDemandCurves( bc.income, bc.px, optimum, bc.color );
                budgetConstraintOptima.push( o );
                addIndifferenceCurve( optimum );
        }
}

/**
 * FIXME don't really need this?? 
*/
function makeBCDataset( income, px ){
        makeBCAndDemand( income, px, 1 );
}

/**
 * Extract the marginal rate of subsitution from the datapoint that's
 * currently been hovered over, and push it into a tooltip.
 * Multiple problems here: tooltip wrong place, mr not always accurate..
 * If you change the graph sizes you'll need to change the calculations
 * of the hover top and left points.
*/
function calcAndDisplayMRS( event_pos, datapoint, data ){
        for( i in data ){
                if( (data[i][0] == datapoint[0]) &&        
                    (data[i][1] == datapoint[1])){
                        if( i > 0 ){
                                var p1= data[i];
                                var p0 = data[i-1];
                                var mr = (p1[1] - p0[1])/(p0[0]-p1[0]);
                                var prec = mr < 0.25 ? 3 : 2;
                                var mrs = mr.toFixed( prec );
                                var s = "MRS here: <span style='font-weight:bold'>"+mrs+"</span>&nbsp;bars of chocolate per packet of crisps ";      
                                var chart_pos = $("#bc_chart").offset(); 
                                //
                                // FIXME:
                                // the X,Ys here are really messed up. 14.5 is the approx pixels
                                // per chocolate bar on the y-axis, by trial and error
                                //
                                var startx = chart_pos.left + 60;
                                var starty = chart_pos.top + 290;
                                $( '<div id="tooltip">' + s + '</div>' ).css( {
                                    position: 'absolute',
                                    display: 'none',
                                    top: starty - ( 14.5 * event_pos.y ),
                                    left: event_pos.x + 5 + startx,
                                    border: '1px solid #fdd',
                                    padding: '2px',
                                    'background-color': '#b8dbff',
                                    opacity: 0.80
                                }).appendTo( "body" ).fadeIn(200);
                                
                                // showTooltip( pos.x, pos.y, s );
                        }
                }
        }
}

/**
 * Merge the budget constraints, optimal position points and indifference curves
 * into one big dataset and plot as a line char
 * @return the chart object
*/
function drawBudgetConstraint(){
        var chart = $.plot( $("#bc_chart"), 
            mergeBudgetConstraints(),
               {
                   yaxis: { min: 0, max: 20 },   
                   xaxis: { min: 0, max: 20 },
                   grid: { hoverable: true
                   }
               }
            );
        // bind the MR display to the hover event:
        // 
        $("#bc_chart").bind( "plothover", 
                function( event, pos, item ){
                    $("#tooltip").remove();
                    // if we're hovering over a curve...
                    if( item ){
                            //alert( event.toSource() );
                            calcAndDisplayMRS( pos, item.datapoint, item.series.data );
                    }
        } );

        return chart;
}

/**
 * Merge all the demand curves and the individual
 * optimal points into one big array and plot as a line chart
 * @return the chart object
*/
function drawDemandCurve(){
        var demand = mergeDemand();
        var chart = $.plot( $("#demand_chart"),
            demand,
               {
                   yaxis: { min: 0, max: 20 },   
                   xaxis: { min: 0, max: 20 }     
               }
              );
        return chart;
}

/**
 * simple function the strips out leading pound signs and 
 * trailing pence signs
 * @param s a string from one of the income/price dropdowns
 * @return same converted to a pounds number
*/
function optToValue( s ){
        var p = s.indexOf( "p" );
        if( p >= 0 ){
                s = s.substring( 0, p );
                s /= 100.0;
        } else {
                s = s.substring( 1 ); // strip &#163;
        }
        return s * 1; 
}

/**
 * extract a value from one of the income/price dropdowns
 * @param which Dom selector (id, class) for the dropdown
 * @return a number for the pound value in the dropdown
*/
function extractValue( which ){
        var s = $( "select"+which ).val();
        return optToValue( s );
}

/**
 * Callback function for the top two dropdowns. Extract the selected income
 * and price, create a new budget constraint and demand curve point set and draw
 * both charts.
 * @param event - jquery data - not actually used
 * @param ui - jquery data - not actually used
*/
function showBCAndDemand( event, ui ){
        var income = extractValue( "#income" );
        var px = extractValue( "#price_x" )
        makeBCDataset( income, px );
        bcchart = drawBudgetConstraint();
        dcchart = drawDemandCurve();
}

/**
 * Callback function for the bottom two utility function dropdowns. 
 * Extract the selected share parameter
 * and elasticity of subsitution, 
 * Then keep the budget constraints but recalculate all the optimum points
 * and hence redraw the demand curves and make the two charts.
 * @param event - jquery data - not actually used
 * @param ui - jquery data - not actually used
*/
function redrawOptimaAndDemand( event, ui ){
        var share = $( "select#share" ).val();
        var es = $( "select#es" ).val();
        redrawIndifferenceAndDemand( es, share );             
        bcchart = drawBudgetConstraint();
        dcchart = drawDemandCurve();
}

function dropdownAlert(){  }

/**
 * The four sliders for utility price and income. These are select element sliders
 * from here: http://www.filamentgroup.com/lab/update_jquery_ui_slider_from_a_select_element_now_with_aria_support/
*/


function drawESSlider(){
        $( 'select#es' ).selectToUISlider(
        {
             sliderOptions: {
                    change: redrawOptimaAndDemand 
             }
        });
}

function drawShareSlider(){
        $( 'select#share' ).selectToUISlider(
        {
             sliderOptions: {
                    change: redrawOptimaAndDemand 
             }
        });
}

function drawPxSlider(){
        $('select#price_x').selectToUISlider(
        {
             sliderOptions: {
                    
                    change: showBCAndDemand 
             }
        });
}
 


function drawIncomeSlider(){
        $('select#income').selectToUISlider(
        {
             
             sliderOptions: {
                    change: showBCAndDemand
         }
        });
}  

/**
 * Clear everything and start again with the last selected values for income, etc.
*/
function clearAll(){
        demandCurvePoints = new Array();
        demandCurveSmoothed = new Array();
        budgetConstraintOptima = new Array();
        indifferenceCurves = new Array();
        budgetConstraints = new Array();

        var income = extractValue( "#income" );
        var px = extractValue( "#price_x" )
        var share = $( "select#share" ).val();
        var es = $( "select#es" ).val();
                
        indifferenceCurves = new Array();
        utility = getUtilityFunction( es, share );
        makeBCDataset( income, px );
        drawBudgetConstraint();
        drawDemandCurve();
}
