/*
 * HECToR AU Calculator
 * ====================
 *
 * Functions to calculate HECToR AU usage and cost based
 * on job size profiles.
 *
 * AU rates and costs are read off the tables on the page.
 * Queue and memory limits are set in the calculateAUs
 * function.
 * 
 * A. R. Turner, EPCC 2009
 */
 
/*
 * setDefault
 * ==========
 * 
 * Set the default values. This only sets the default phase, 
 * parallel processes, job time and number of jobs.
 * 
 * This is only called when the page is loaded. 
 */
function setDefault() {
	
	// Reset the funding council to partner
	$("#setFunding").val("partner");

    // Reset the phase to the current one
    $("#setPhase").val("phase3");

    // The default number of processes
    $("*[id^=npros]").val("1");

    // The default time
    $("*[id^=time]").val("1");

    // The default number of jobs
    $("*[id^=njob]").val("1");
}

/*
 * phaseChange
 * ===========
 * 
 * Set the input boxes based on the selected phase. This 
 * only populates the pulldowns for the number of cores
 * per node.
 * 
 * This is called everytime the value of the Phase pulldown
 * is changed.
 */
function phaseChange() {

	// Are we a partner research council or not?
	var funding = $("#setFunding").val();
	
    // Set up the input boxes for the selected phase
    var phase = $("#setPhase").val();

    if (phase == "phase2b") {

      // Number of cores per node read from table cell
      $("*[id^=ncore]").empty();
      coresPerNode = parseInt($('#phase2bCores').text());
      for (i = 1; i <= coresPerNode; i++) {
        $("*[id^=ncore]").append('<option value="' + i + '">' + i + '</option>');
      }
      $("*[id^=ncore]").val(coresPerNode);

    } else if (phase == "phase3") {

      // Number of cores per node read from table cell
      $("*[id^=ncore]").empty();
      coresPerNode = parseInt($('#phase3Cores').text());
      for (i = 1; i <= coresPerNode; i++) {
        $("*[id^=ncore]").append('<option value="' + i + '">' + i + '</option>');
      }
      $("*[id^=ncore]").val(coresPerNode);
    }

    // Recalculate the values based on these changes
    calculateAll();
}

/*
 * calculateAll
 * ============
 * 
 * This function fills the various fields on the page based on the
 * current values in all the input elements.
 * 
 * It is called every time the value in an input element is changed.
 */
function calculateAll() {

   // Empty the error messages
   $("#nprosMessage").empty();
   $("#timeMessage").empty();


   // Calculate per job values for each of the job types
   var nBig = calculateAUs("Big");
   var nTyp = calculateAUs("Typ");
   var nSmall = calculateAUs("Small");

   // Get the fraction of each job type
   var nTot = nBig + nTyp + nSmall;
   var fracBig = nBig / nTot * 100;
   var fracTyp = nTyp / nTot * 100;
   var fracSmall = nSmall / nTot * 100;
   
   // Calculate the total AUs
   var ausBig = parseInt($('#ausBig').text());
   var ausTyp = parseInt($('#ausTyp').text());
   var ausSmall = parseInt($('#ausSmall').text());
   
   var totAUs = nBig*ausBig + nTyp*ausTyp + nSmall*ausSmall;
   
   $("#totAUs").text(numberFormat(totAUs));
   
   // Calculate the total cost
   var costBig = parseFloat($('#costBig').text());
   var costTyp = parseFloat($('#costTyp').text());
   var costSmall = parseFloat($('#costSmall').text());
   
   var totCost = nBig*costBig + nTyp*costTyp + nSmall*costSmall;
   
   $("#totCost").text(numberFormat(totCost.toFixed(2)));

}

/*
 * calculatesAUs
 * =============
 * 
 * Calculate the values for each job of the specified type.
 * The argument "set" is the root of the job set to calculate 
 * the values for (i.e. "Big", "Typ" or "Small").
 * 
 * This is called by the calculateAll function.
 */
function calculateAUs(set) {
	
	// Are we a partner research council or not?
	var funding = $("#setFunding").val();

   // Set the selected phase
   var phase = $("#setPhase").val();
   
   // Initial values of phase-specific variables
   // Amount of memory per node (GB)
   var nodeMem = 8;
   // AUs per core hour
   var auRate = 7.5;
   // Cost per AU (GBP)
   var auCost = 0.01371;
   // Maximum job size
   var maxNodes = 4096;
   // Maximum job length
   var maxTime = 12;
   
   if (phase == "phase2b") {
	 coresPerNode = parseInt($('#phase2bCores').text());
	 auRate = parseFloat($('#phase2bRate').text());
	 if (funding == "partner") {
		 auCost = parseFloat($('#partPhase2CostXT').text());
	 } else {
		 auCost = parseFloat($('#otherPhase2CostXT').text());
	 }
	 nodeMem = parseFloat($('#phase2bMem').text());
	 maxTime = parseFloat($('#phase2bMaxTime').text());
     maxNodes = parseInt($('#phase2bMaxNode').text());
   } else if (phase == "phase3") {	   
	 coresPerNode = parseInt($('#phase3Cores').text());
	 auRate = parseFloat($('#phase3Rate').text());
	 if (funding == "partner") {
		 auCost = parseFloat($('#partPhase3CostXT').text());
	 } else {
		 auCost = parseFloat($('#otherPhase3CostXT').text());
	 }
	 nodeMem = parseFloat($('#phase3Mem').text());
	 maxTime = parseFloat($('#phase3MaxTime').text());
     maxNodes = parseInt($('#phase3MaxNode').text());
   }


   // Get the input values
   var search = "*[id^=npros" + set + "]";
   var npros = parseInt($(search).val());
   search = "*[id^=ncore" + set + "]";
   var ncore = parseInt($(search).val());
   search = "*[id^=time" + set + "]";
   var time = parseFloat($(search).val());
   search = "*[id^=njob" + set + "]";
   var njobs = parseInt($(search).val());

   // Check that we have not exceeded maximum time
   if (time > maxTime) {
     time = maxTime
     var search = "*[id^=time" + set + "]";
     $(search).val(time);
     $("#timeMessage").text("Job too long - reset to maximum value");
   }

   // Calculate the number of nodes this job will use
   var nnode = npros / ncore;
   nnode = Math.ceil(nnode);
   // Check we are within the limits
   if (nnode > maxNodes) {
	     nnode = maxNodes;
	     npros = nnode * ncore;
	     var search = "*[id^=npros" + set + "]";
	     $(search).val(npros);
	     $("#nprosMessage").append("<br />Too many processes - reset to maximum value");
   }
   search = "#nnode" + set;
   $(search).text(nnode);

   // Calculate the amount of memory this job will have
   var mem = nnode * nodeMem;
   search = "#tmem" + set;
   $(search).text(mem + " GB");

   // Calculate the amount of memory each process will have
   var pmem = mem / npros;
   search = "#mem" + set;
   $(search).text(pmem.toFixed(2) + " GB");

   // Calculate the number of AUs this job will use
   var aus = npros * time * auRate;
   aus = nnode * coresPerNode * time * auRate;
   search = "#aus" + set;
   $(search).text(aus.toFixed(0));

   // Calculate the notional cost of this job
   var cost = aus * auCost;
   search = "#cost" + set;
   $(search).text(cost.toFixed(2));

   // Calculate total time used by these jobs
   var totalTime = time * njobs;
   search = "#ttime" + set;
   $(search).text(totalTime);

   // Return the number of jobs of this type
   return njobs;

}

/*
 * numberFormat
 * ============
 * 
 * This function formats numbers by adding commas every
 * three digits.
 */
function numberFormat(nStr) {
  nStr += '';
  x = nStr.split('.');
  x1 = x[0];
  x2 = x.length > 1 ? '.' + x[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1))
    x1 = x1.replace(rgx, '$1' + ',' + '$2');
  return x1 + x2;
}

/*
 * stripNonNumeric
 * ===============
 * 
 * This function removes non-numeric characters from the supplied string
 * 
 */
function stripNonNumeric(str) {
  str += '';
  var rgx = /^\d|\.|-$/;
  var out = '';
  for( var i = 0; i < str.length; i++ ){
    if( rgx.test( str.charAt(i) ) ){
      if( !( ( str.charAt(i) == '.' && out.indexOf( '.' ) != -1 ) ||
             ( str.charAt(i) == '-' && out.length != 0 ) ) ){
        out += str.charAt(i);
      }
    }
  }
  return out;
}

