﻿// Preventive math engine
//
// February 2009
// Author: Matt Pappas
// My thanks to Joseph Lust, who first outlined the Prototype-centric architecture of this engine
//
// uses Prototype 1.6.0 Library for array operations
// <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script>

// Build arrays for lookups, using Prototype

function phStruct() // Store properties of a Preventive Health Object
{
    this.Msg = null;    this.customMsg = null;
    this.Lnk = null;
    this.UIdx = null;   this.WIdx = null;   this.MIdx = null; 	// Indices
    this.UVal = null;   this.WVal = null;   this.MVal = null; 	// Values
    this.showId = null; 										// Element id for prerendered values
    
    this.getMVal = function (age) {
        // Retrieve the value for a given age
        if(age >= this.MIdx.min() && age <= this.MIdx.max()) {
            return this.MVal[age-this.MIdx.min()];
        }
        else { return false; } // Error: Value out of range
    }
    this.getWVal = function (age) {
        // Retrieve the value for a given age
        if(age >= this.WIdx.min() && age <= this.WIdx.max()) {
            return this.WVal[age-this.WIdx.min()];
        }
        else { return false; } // Error: Value out of range
    }     
    this.insertRow = function (age,sex,customVal) {
        // Place the row for this age dynamically in the table
        // Ignore if outside age range
        if(arguments.length==3) {
        // auxilary message parameter sent, evaluate it in customMsg template      
            this.Msg = this.customMsg.interpolate({
                        value: customVal
                        });
        }       
        switch(sex) // Must exist for Sex->Age->Range
        {
            case 'Man':
            if(this.MIdx && this.getMVal(age)) {insertRow(this.Msg,this.getMVal(age),this.Lnk); }
            break;
            
            case 'Woman':
            if(this.WIdx && this.getWVal(age)) {insertRow(this.Msg,this.getWVal(age),this.Lnk); }
            break;
        }
    }
    this.showRow = function (age,sex) {
    // Intended for use with static values, that is, values that do NOT link to a table of lookup values
    // Dynamic with respect to age and sex
        switch(sex)
        {
            case 'Man':
            if(this.MIdx) {
                if(age>=this.MIdx.min() && age<=this.MIdx.max()) { $(this.showId).style.display='block'; }}
            break;
            
            case 'Woman':
            if(this.WIdx) {
                if(age>=this.WIdx.min() && age<=this.WIdx.max()) { $(this.showId).style.display='block'; }}
            break;
        }    
    }
    
}

////////////////////////////
//     MAIN FUNCTIONS     //
////////////////////////////

// Globals
var tblID = 'RankedResults';

function insertSelect()
{
    this.values = null;     this.names = null;
    // Methods
    this.setHeightRange = function(a,b)  { // Create height range
       this.values= $A($R(a,b));
       this.values.each( function(val,idx) {    this.names.push(Math.round(val/12)+"' "+val%12+'"');  });
    }
    this.setAgeRange = function(a,b)  { // Create age range
       this.values= $A($R(a,b));
       this.names = this.values.clone();
    }
    // Render
    this.renderHTML = function(id,className) {
        // Create element first
        $('age_li').insert('<select class="#{cVal}" id="#{id}"></select>'.interpolate({id: id,cVal: className})); // Header
        // Now fill it
        if(Prototype.Browser.IE) { // IE - not W3C  
            for(var n=0,max=this.values.size(); n<max; n++) {
                var optn = document.createElement("OPTION");
                optn.text = this.values[n];
                optn.value = this.names[n];
                id.options.add(optn, pos);
            var select = document.forms.demographicsform.elements.selectName;
            select.options[select.options.length] = new Option('text', 'value');
            };               
        }
        else { // Firefox, Safari, other W3C-compliant browsers
            for(var n=0,max=this.values.size(); n<max; n++) {
                $(id).insert({bottom: '<option value="#{val}">#{nm}</option>'.interpolate({val: this.values[n], nm: this.names[n]}) }); // Header
            };
        }
    }   
}


// Hide static rows until next run. Prevent suggestion accrual between runs.
function hideStatic()
{
    $$('.staticRow').each(function(id) {$(id).style.display='none'});   // Hide static
}

// Basic helper, just flip .style.display to block
function showBlock(arr) {   arr.each(function(n) {$(n).style.display = 'block';});  }

// Basic helper, just flip .style.display to none
function hideBlock(arr) {   arr.each(function(n) {$(n).style.display = 'none';});   }

function insertRow(msg,val,lnk)  // Insert a row
{
    // dynamically fill in values to make a body row for injection (3 strings -> msg, val, lnk)
    var id_rt = 'dyn_';    
    var id = 'dyn_'+$$('.dynamicRow').size(); // Autonumber based on existing dynamics
    $(tblID).down('tr').insert({
        after: '<tr class="dynamicRow" id="#{id}" style="cursor:pointer;"><td class="ResultsLeftCell">#{msg}</td><td class="ResultsRightCell">#{val}</td></tr>'.interpolate({
            id: id,
            msg: msg,
            val: val,
            lnk: lnk
        })
    })
    $(id).style.display = 'block'; // Show now
	$(id).LinkTo = lnk;
	$(id).LinkFrom = id;
}

// Calculate BMI - units of inches and lbs
function calcBMI(height,weight)
{
    this.height = height;   // Store user parameters in object
    this.weight = weight;
    
    this.getBMI = function ()
    {
        return (0.45359237/Math.pow(0.0254,2))*this.weight/Math.pow(this.height,2);
    }
    
    this.getWeightLoss = function (targetBMI)	//Rounded to the 5 pound interval above actual loss required
    {
        return  (5*Math.ceil((this.getBMI()-targetBMI)*Math.pow(this.height,2)/(0.45359237/Math.pow(0.0254,2))/5));
    }
}

///////////////////////////////////
//     PREVENTIVE MATH LOGIC     //
///////////////////////////////////

// Use above functions and user inputs to display preventive health calculation

// Each variable corresponds to the age at which the USPSTF recommends starting
// (or ending) each service for men or women - indicated in the variable name		

// Calculate all values, then display
function calculate()
{
    // Assign form inputs to variables for ease of use.
    var CurrentAge              =$('Age').value;
    var CurrentSex              =$('Sex').value;
    var CurrentSmokingStatus    =$('SmokingStatus').value;
	if ($('navClinicians').down().hasClassName('selected')) {
		// Skip height and weight on clinician calculator; these aren't (yet?) meaningful here
	}
	else {
		var CurrentHeight           =$('Height').value;
	    var CurrentWeight           =$('Weight').value;
	}

    // Show entries that are independent of age, sex, or specifics   
    var showArr = $w('RankedResults RankedResultsTop UnrankedResults UnrankedResultsTop Immunizations DepressionScreening DiabetesScreening DietaryCounseling TDBooster');			
    var hideArr = $w('whiteSpace');
   
    hideBlock(hideArr);	                                     // Hide whitespace element
    hideStatic();                                            // Don't show old static interventions
    $$('.dynamicRow').each(function(id) {$(id).remove()});   // Remove all dynamically created rows from earlier runs
    showBlock(showArr);                                      // Display items that apply to all comers

    // Show non-life-extending interventions that are dependent on user age and sex
    ChlamydiaScreening.showRow(CurrentAge,CurrentSex);
    FolicAcid.showRow(CurrentAge,CurrentSex);
    OsteoporosisScreening.showRow(CurrentAge,CurrentSex);
    HearingScreening.showRow(CurrentAge,CurrentSex);
    VisionScreening.showRow(CurrentAge,CurrentSex);
    BRCATesting.showRow(CurrentAge,CurrentSex);
   
    // Dynamically build values for given lookup tables
    
    // Dependent on age but not sex
    HTNScreening.insertRow(CurrentAge,CurrentSex);
    AlcoholScreening.insertRow(CurrentAge,CurrentSex);
    FluShot.insertRow(CurrentAge,CurrentSex);
    Aspirin.insertRow(CurrentAge,CurrentSex);
    CholesterolScreening.insertRow(CurrentAge,CurrentSex);
    CRCScreening.insertRow(CurrentAge,CurrentSex);
    PneumococcalVaccine.insertRow(CurrentAge,CurrentSex);

    // Dependent upon both age and sex
    CervicalCancerScreening.insertRow(CurrentAge,CurrentSex);
    BreastCancerScreening.insertRow(CurrentAge,CurrentSex);
	PreventiveChemotherapy.insertRow(CurrentAge,CurrentSex);
    CalciumSupplementation.insertRow(CurrentAge,CurrentSex);

	// If we're in the calculator for Individuals, these interventions will change with diagnostic information.
	// Otherwise, evaluate using diagnostic information
	if ($('navIndividuals').down().hasClassName('selected')) {
		
		var UseDiagnosticInformation = $('DiagnosticInformation').value;
		if (UseDiagnosticInformation == 'Yes') {
			// Quit smoking if smoker.  Non-smokers have no entry.
			if(CurrentSmokingStatus=='Yes') { SmokingCessationSmoker.insertRow(CurrentAge,CurrentSex); }

			// Determine BMI and show weight loss, if applicable
			var BMI = new calcBMI(CurrentHeight,CurrentWeight);		// make object
			if (BMI.getBMI()>=35) { // 35+
				BMI35toUnder30.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(29.9));
				BMI35toUnder35.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(34.9));
	    	}
	    	else    {
	        	if(BMI.getBMI()>=30) { // [30,35)
					BMI30toUnder30.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(29.9));   
	        	}
	        	else    { // <30
	            	$('WeightLossUnranked').style.display = 'block';
	        	}
	    	}
		}
		else {
			// We're on the individual calculator and don't want to use diagnostic information
			SmokingCessationAllPatients.insertRow(CurrentAge,CurrentSex);
			ObesityScreening.insertRow(CurrentAge,CurrentSex);
		}
		
	}
	else {
		// Quit smoking if smoker		
		if(CurrentSmokingStatus=='Yes') { SmokingCessationSmoker.insertRow(CurrentAge,CurrentSex); }
			
		// Determine BMI
		if ($('navClinicians').down().hasClassName('selected')) {
			// Skip height and weight on clinician calculator; these aren't (yet?) meaningful here
		}
		else {
			var BMI = new calcBMI(CurrentHeight,CurrentWeight);		// make object
			if (BMI.getBMI()>=35) { // 35+
				BMI35toUnder30.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(29.9));
				BMI35toUnder35.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(34.9));
    		}
    		else    {
        		if(BMI.getBMI()>=30) { // [30,35)
					BMI30toUnder30.insertRow(CurrentAge,CurrentSex,BMI.getWeightLoss(29.9));   
        		}
        		else    { // <30
            		$('WeightLossUnranked').style.display = 'block';
        		}
    		}
		}
	}
    
	
	// Now sort rows into descending order based on days of life saved
	var keyArr = [];
	var valMap = [];
	
	$(tblID).getElementsBySelector('.dynamicRow').each(
	function (id,idx) {
	    keyArr.push(id.id);
	    valMap[id.id]=parseInt(id.down('.ResultsRightCell').innerHTML);
	});
    // Sort - reorder keys by values they map to
    var keyArrSorted =	keyArr.clone().sort(function(a,b){  return valMap[a]-valMap[b]; }).reverse();

    // Reorder entries in the table - copy all to sorter array, then update    
    var htmlArrSorted = [];
	var linkToMap = [];
    $(keyArrSorted).each(   function (id,idx) {
		htmlArrSorted.push($(id).innerHTML);
		linkToMap[idx] = $(keyArrSorted[idx]).LinkTo;
	});
	$(keyArr).each(   function (id,idx) {
		$(id).update(htmlArrSorted[idx]);
		new FancyZoomModified($(id),linkToMap[idx]);
	});
}
