// Created by Gary Fisk in November-December of 2023. 
// Updated to concise inputs in February, 2024
// Updated to include more input in a single field: 2026
// The purpose is to provide a more open way to create flexible recipes. 
// User inputs enable greater control over the calcuations than the previous flex version. 
// License: Creative Commons Attribution 4.0 International
// https://creativecommons.org/licenses/by/4.0/

// *** Basic conversion functions
function gallonsToLiters(gallons){
	return gallons*3.78541178;
	}
function litersToGallons(liters) {
	return liters*0.264172;
	}
function kilogramsToPounds(kilograms) {
	return kilograms*2.20462;
	}
function SGToPlato(SG) {
	return (135.997 * Math.pow(SG,3)) - (630.272 * Math.pow(SG,2)) + (1111.14 * SG) - 616.868;
	}
function PlatoToSG (Plato) {
	return 1 + (Plato / (258.6 - ((Plato/258.2) * 227.1)));
	}
function gramsToOunces(grams) {
	return grams*0.035274;
	} 
function barrelsToLiters(barrels) {
	return gallonsToLiters(barrels * 31);
	} 
function celsiusToFahrenheit(celsius){
    return celsius*(9/5)+32;
	}
function poundsToKilograms(pounds) {
    return pounds*0.453592;
}
function calculate() {
// Create variables
	var output = ""; 
	var SG = 0; // Variable for original gravity as specific gravity
	var Plato = 0; // Variable for original gravity as degrees plato
	var GravityUnit = 0; // Variable for tracking type of gravity units
	var IBUtemp = 0; // A temporary variable for each hops IBU values
	var IBUcheck = 0; // This variable is a check to see if IBUs are correct; inputs = outputs
	var GravityPoints = 0; // This is used as part of estimating a 15% boil off rate for hop utilization
	var EstBoilGravity = 0; // Part of estimating IBUs at different time points
	var EstBoilVolume = 0; // Part of estimating IBUs at different time points
	var TempForm = ""; // A temporary variable for holding forms
	var TempArray = []; // A temporary array for holding malt and hop values
	var TempOutput = ""; // Temporary output variable
	var Vol = 0; // Desired volume, numeric without units
	var Gallons = 0; // Variable for volume in gallons
	var Liters = 0; // Variable for volume in liters
	// Is the following variable obsolete now? 
	var MeasureUnit = 0; // Measurement system: US or Metric; US is set as default later if this is unspecified
	var SpMaltTotal = 0; // Variable for adding up the special malt percentages
	var ColorUnit = 0; // Variable for the color unit values 0 = SRM, 1 = MCU, 2 = EBC
	var TotalWeight = 0; // Variable that sums all weights
	var PercentCheck = 0; // An error tracking variable for the total percentages
	var MCU = 0; // Variable for malt color units value
	var SRM = 0; // Variable for standard reference method color value
	var MaltSugar = []; // Array for malt and sugar values
	var Hops = []; // Array for hop values
	var Spice = []; // Array for late kettle additions like spices
	var DryHops = []; // Array for dry hop values
	var TotalHopGrams = 0; // Sums hops weights for the boil (not including dry hop)
	var DryHopWeight = 0; // Calculated value for dry hop weight
	var YeastType = 0; // Yeast 0 = ale, 1 = lager, with ale default
	var MashEfficiency = 0; // Mash efficiency
	var CO2desired = 0; // Priming volumes of CO2
	var FermTemp = 0; // Priming highest fermentation temperature 
	var Title = "My Beer"; //Variable for holding the beer name 
	var Vol = 0; // Volume target 
	var DME = 0; // Default for dry malt extract weight additions
	var LME = 0; // Default for liquid malt extract weight additions

// Load recipe values for OG, malt, etc. 
    var TempForm = document.getElementById("MainField"); // Get data from textarea field MainField
	var TempArray = TempForm.elements[0].value.split(/\r?\n/); //Create a temporary array splitting on new lines
	console.log("TempArray: ",TempArray); // TempArray to the console
	for (i = 0; i < TempArray.length; i++) {
		if (TempArray[i].search(/[oO][gG]:/) > -1) { // Set OG value
			TempArray[i] = TempArray[i].replaceAll(/[Oo][Gg]: /g,""); //Remove the OG: tag and space, case insensitive regex
			var OG = parseFloat(TempArray[i]); // Assign OG variable; Convert string to number
		}
		if (TempArray[i].search(/[mM][aA][lL][tT]:/) > -1) { // Set malt values
			TempArray[i] = TempArray[i].replaceAll(/[mM][aA][lL][tT]: /g,""); //Replace the m: tag and space
			var TempArray2 = TempArray[i].split(","); // Create a second array
			for (j = 1; j < 4; j++) {
				TempArray2[j] = parseFloat(TempArray2[j]); // Convert numbers imported as string values to numerical; Starts at position 1
			}
			TempArray2.splice(1, 0, 0); // Add a 0 to array position 1 for sugar type (0 = malt)
			MaltSugar.push(TempArray2); // Add these current values to the MaltSugar array
		}
		if (TempArray[i].search(/[sS][uU][gG][aA][rR]:/) > -1) { // Set sugar values
			TempArray[i] = TempArray[i].replaceAll(/[sS][uU][gG][aA][rR]: /g,""); //Replace the s: tag and space
			var TempArray2 = TempArray[i].split(","); // Create a second array
			for (j = 1; j < 4; j++) {
				TempArray2[j] = parseFloat(TempArray2[j]); // Convert numbers imported as string values to numerical; Starts at position 1
			}
			TempArray2.splice(1, 0, 1); // Add a 1 to array position 1 for sugar type (1 = sugar)
			MaltSugar.push(TempArray2); // Add these current values to the MaltSugar array
		}
		if (TempArray[i].search(/[iI][bB][uU]:/) > -1) { // Set IBU values
			TempArray[i] = TempArray[i].replaceAll(/[iI][bB][uU]: /g,""); //Replace the IBU: tag and space
			var IBU = TempArray[i]; // Add the current values to the IBU variable
		}
		if (TempArray[i].search(/[hH][oO][pP]:/) > -1) { // Set hop values
			TempArray[i] = TempArray[i].replaceAll(/[hH][oO][pP]: /g,""); //Replace the h: tag and space
			var TempArray2 = TempArray[i].split(","); // Create a second array
			for (j = 1; j < 4; j++) {
				TempArray2[j] = parseFloat(TempArray2[j]); // Convert numbers imported as string values to numerical; Starts at position 1
			}			
			Hops.push(TempArray2); // Add these current values to the hops array
		}
		if (TempArray[i].search(/[lL][aA][tT][eE][bB][oO][iI][lL]:/) > -1) { // Set late boil values
			TempArray[i] = TempArray[i].replaceAll(/[lL][aA][tT][eE][bB][oO][iI][lL]: /g,""); //Replace the lateboil: tag and space 
			var TempArray2 = TempArray[i].split(","); // Create a second array
			TempArray2[1] = parseFloat(TempArray2[1]); // Convert numbers imported as string values to numerical; Starts at position 1
			Spice.push(TempArray2); // Add these current values to the spice array
		}
		if (TempArray[i].search(/[dD][rR][yY]:/) > -1) { // Set dry hop values
			TempArray[i] = TempArray[i].replaceAll(/[dD][rR][yY]: /g,""); //Replace the dry: tag and space
			var TempArray2 = TempArray[i].split(","); // Create a second array
			TempArray2[1] = parseFloat(TempArray2[1]); // Convert numbers imported as string values to numerical; Starts at position 1
			DryHops.push(TempArray2); // Add these current values to the dry hop array
		}
		if (TempArray[i].search(/[yY][eE][aA][sS][tT]:/) > -1) { // Enter the yeast type 
			TempArray[i] = TempArray[i].replaceAll(/[yY][eE][aA][sS][tT]: /g,""); //Replace the yeast: tag and space 
			if (TempArray[i] == "ale") {
				var YeastType = 0; // Yeast type 0 for ale yeast
			} 
			else if (TempArray[i] == "lager") {
				var YeastType = 1; // Yeast type 1 for lager yeast
			} 
			else {
				var YeastType = 2; // Error message
			}
		}
		if (TempArray[i].search(/[vV][oO][lL]:/) > -1) { //Obtain the desired volume
			TempArray[i] = TempArray[i].replaceAll(/[vV][oO][lL]: /g,""); //Replace the vol: tag and space 
			var Vol = parseFloat(TempArray[i]); // Volume
		}
		if (TempArray[i].search(/[sS][yY][sS][tT][eE][mM]:/) > -1) { //Measurement system, US or metric
			TempArray[i] = TempArray[i].replaceAll(/[sS][yY][sS][tT][eE][mM]: /g,""); //Replace the system: tag and space 
			var MeasureUnit = TempArray[i]; // Measurement units
		}
		if (TempArray[i].search(/[eE][fF][fF]:/) > -1) {
			TempArray[i] = TempArray[i].replaceAll(/[eE][fF][fF]: /g,""); //Replace eff: tag and space
			var MashEfficiency = parseFloat(TempArray[i]); // Set mash efficiency
		}
		if (TempArray[i].search(/[pP][rR][iI][mM][eE]:/) > -1) {
			TempArray[i] = TempArray[i].replaceAll(/[pP][rR][iI][mM][eE]: /g,""); //Replace prime: tag and space
			var TempArray2 = TempArray[i].split(","); // Create a second array
			var CO2desired = parseFloat(TempArray2[0]); // Desired volumes of CO2
			var FermTemp = parseFloat(TempArray2[1]); // Highest fermentation temperature
		}
		if (TempArray[i].search(/[nN][aA][mM][eE]:/) > -1) {
			TempArray[i] = TempArray[i].replaceAll(/[nN][aA][mM][eE]: /g,""); //Replace name: tag
			var Title = TempArray[i]; // Beer name
		}
		if (TempArray[i].search(/[dD][mM][eE]:/) > -1) {
			TempArray[i] = TempArray[i].replaceAll(/[dD][mM][eE]: /g,""); //Replace DME: tag and space
			var DME = parseFloat(TempArray[i]); // Fixed weight of DME for partial mash
		}
		if (TempArray[i].search(/[lL][mM][eE]:/) > -1) {
			TempArray[i] = TempArray[i].replaceAll(/[lL][mM][eE]: /g,""); //Replace DME: tag and space
			var LME = parseFloat(TempArray[i]); // Fixed weight of LME for partial mash
		}
	}
// Use OG to determine the SG and Plato variables, with necessary conversions
// OG values less than 1.2 are assumed to be in specific gravity units. Conversely, values over 1.2 are interpreted as degrees plato.     
    if (OG < 1.2) {
		var Plato = SGToPlato(OG); // For OG in specific gravity units 
		var SG = OG; 
    } 
    else {
		var Plato = OG; // For OG in Plato units
		var SG = PlatoToSG(OG); 
    }
// Get the volume values entered into the proper variables. A US beer barrel is 31 gallons
	if (MeasureUnit == 0) {  // MeasureUnit = 0 means no measurment system was entered
		var output = output + "*** The desired measurement system is not specified. US is used as a default. ***\r\n";
		var MeasureUnit = "US"; 
	}
	var MeasureUnit = MeasureUnit.toUpperCase(); // Convert text to upper case for consistency
    if (MeasureUnit == "METRIC") {
		var Liters = Vol; 
		var Gallons = litersToGallons(Vol);
		} 
	else if (MeasureUnit == "US") {
		var Liters = gallonsToLiters(Vol); 
		var Gallons = Vol;
		}
	else if (MeasureUnit == "BARREL") {
		var Liters = gallonsToLiters(Vol * 31); 
		var Gallons = (Vol * 31);
	}
	else { //This is for all other error inputs.  
		MeasureUnit == "US";
		var output = output + "*** The desired measurement system is unclear. US units are used as a default. ***\r\n";
		var MeasureUnit = "US"; 
		var Liters = gallonsToLiters(Vol); 
		var Gallons = Vol;
	}
// Checks for missing values, sets defaults if needed
	if (MashEfficiency == 0) {
		var output = output + "*** The mash efficiency input is missing. 75% is used as a default value.\r\n";
		var MashEfficiency = 75;
	}
	if (CO2desired == 0) {
		var output = output + "*** The target CO2 input is missing. 2.5 is used as a default value.\r\n";
		var CO2desired = 2.5;
	}
	if (FermTemp == 0) {
		var output = output + "*** The highest fermentation temperature is missing. 72F/22C is used as a default value.\r\n"; 
		if (MeasureUnit == "US") {
			var FermTemp = 72; 
		}
		else {
			var FermTemp = 22;
		}
	}
	if (Vol == 0) {
		var output = output + "*** The volume target is missing. 5.0 is used as a default.\r\n"; 
		var Vol = 5.0; 
	}
// Check for unusual values
    if (SG > 1.2) {
		var output = output + "*** A specific gravity value larger than 1.200 is unusual. Check the data entry. ***\r\n";
	} 
    if (Plato < 2) {
		var output = output + "*** A plato value smaller than 2.0 is unusual. Check the data entry. ***\r\n";
	}
	if (Vol <= 0 ) {
		var output = output + "*** The volume must be greater than zero. Check the data entry. ***\r\n"; 
	}
    if (MashEfficiency > 100) {
		var output = output + "*** A mash efficiency over 100% is not possible. Check the data entry. ***\r\n";
	}
    if (MashEfficiency < 50) {
		var output = output + "*** A mash efficiency below 50% seems unusual. Check the data entry. ***\r\n";
    }
    if (SpMaltTotal > 100) {
		var output = output + "*** Error: The total malt percentage is over 100%. Check the data entry. ***\r\n";
    }
	for (i=0; i < MaltSugar.length; i++) {
		if (MaltSugar[i][4] <= 1.0) {
			var output = output + "*** Error: Yield value of less than 1% in a malt or sugar field. ***\r\n";
		if (MaltSugar[i][1] == 0 && MaltSugar[i][4] > 85) {
			var output = output + "*** Error: Malt yield of larger than 85% seems unusual. ***\r\n"; 
		}
	}
	}
	for (i=0; i < Hops.length; i++) {
		if (Hops[i][2] < 2.5 || Hops[i][2] > 25) {
			var output = output + "*** Error: Unusual alpha acid percentage of <2.5 or >25 in the hops field. ***\r\n";
		}
	}
	if (FermTemp < 55 && YeastType == 0 && MeasureUnit == "US") {
		var output = output + "*** Check: Fermentation temperature below 55F seems unusal for an ale. ***\r\n";
	}
	else if (FermTemp < 13 && YeastType == 0 && MeasureUnit == "METRIC") {
		var output = output + "*** Check: Fermentation below 13C seems unusual for an ale. ***\r\n"; 
	}
	else if (FermTemp < 40 && YeastType == 1 && MeasureUnit == "US") {
		var output = output + "***Check: Fermentation below 40F seems unusal for a lager. ***\r\n"; 
	}
	else if (FermTemp < 5 && YeastType == 1 && MeasureUnit == "METRIC") {
		var output = output + "***Check: Fermentation below 5C seems unusal for a lager. ***\r\n";
		}
// The following check for blank entries in the user input. Number.isNaN is a true/false test for non-numerical values. 
    if (Number.isNaN(MashEfficiency)) {
		var output = output + "*** Error: Check the mash efficiency field. ***\r\n";
    }
    if (Number.isNaN(Vol)) {
		var output = output + "*** Error: Check the volume field. ***\r\n";
    }
    if (Number.isNaN(IBU)) {
		var output = output + "*** Error: Check the IBU field. ***\r\n";
    }
    if (Number.isNaN(OG)) {
		var output = output + "*** Error: Check the original gravity field. ***\r\n";
	}
	if (YeastType == 2) {
		var output = output + "*** Error: Yeast type is uncertain. ***\r\n"; 
	}
// Checks for possible percentages that don't add to 100%. 
    var MaltCheck = 0; // Checks for malt percentages adding to 100%.  
    for (i=0; i < MaltSugar.length; i++) {
		if (MaltSugar[i][0].toUpperCase() == "NONE") {
			continue; 
		}
		var MaltCheck = MaltCheck + MaltSugar[i][2]; 
    }
	if (Math.round(MaltCheck*100)/100.00 != 100) {
		var output = output + "*** Error: Total fermentable percentages are " + MaltCheck + "%. Must add to 100%. ***\r\n";
	}
    var HopCheck = 0;
	for (i=0; i < Hops.length; i++) {
		var HopCheck = HopCheck + Hops[i][1];
    }
	if (HopCheck != 100) {
		var output = output + "*** Error: Top IBU percentages sum to " + HopCheck + "%. These must add to 100%. ***\r\n"; 
	}
// Checks for odd array lengths in MaltSugar, Hops, Spice, Dry hops
	for (i = 0; i < MaltSugar.length; i++) {
	if (MaltSugar[i][0] == "none" || MaltSugar[i][0] == "None") {
			continue; 
		}
		if (MaltSugar[i].length > 5) {
			var output = output + "*** Error: Check the malt or sugar entry for extra data. ***\r\n"; 
		}
		for (j = 1; j < 5; j++) {
			if (Number.isNaN(MaltSugar[i][j])) {
				var output = output + "*** Error: Check the data in the malt or sugar fields for errors. ***\r\n"; 
			}
		}
	}
	for (i = 0; i < Hops.length; i++) {
		if (Hops[i].length > 4) {
			var output = output + "*** Error: Check the hop fields for missing or extra data. ***\r\n"; 
		}
		for (j = 1; j < 4; j++) {
			if (Number.isNaN(Hops[i][j])) {
				var output = output + "*** Error: Check the data in the hop fields for errors. ***\r\n"; 
			}
		}
	}
	for (i = 0; i < Spice.length; i++) {
		if (Spice[i].length > 2) {
			var output = output + "*** Error: Check the spice fields for extra data. ***\r\n"; 
		}
		if (Spice[i][0] == "None" || Spice[i][0] == "none") {
			continue; // Skips the next test if the field is labeled "none". 
		}
		if (Number.isNaN(Spice[i][1])) {
				var output = output + "*** Error: Check the data in the spice fields for errors. ***\r\n"; 
		}
		}
	for (i = 0; i < DryHops.length; i++) {
		if (DryHops[i].length > 2) {
			var output = output + "*** Error: Check the dry hop fields for extra data. ***\r\n"; 
		}
		if (DryHops[i][0] == "None" || DryHops[i][0] == "none") {
			continue; // Skips the next test if the field is labeled "none". 
		}
		if (Number.isNaN(DryHops[i][1])) {
				var output = output + "*** Error: Check the data in the dry hop fields for errors. ***\r\n"; 
		}
		}
// Start the output with overall batch information
	var output = output + "# " + Title + "\r\n\r\n"; //Title with markdown heading 1 
// Use the WeightUnit variable as a measure of the true desired output units.
	var output = output + "## Overall\r\n"; // Overall header with markdown heading 2
    if (MeasureUnit == "METRIC") {
		var output = output + "- Volume: " + Math.round(Liters * 10) / 10 + " Liters\r\n"; // Liters output
    } 
	else if (MeasureUnit == "US") {
		var output = output + "- Volume: " + Math.round(Gallons * 10) / 10 + " Gallons\r\n"; // Gallons output
	}
	else if (MeasureUnit == "BARREL") {
		var output = output +  "- Volume: " + Math.round((Gallons / 31) * 10) / 10 + " US barrels\r\n"; // US barrels output
	}
	else {  
		var output = output + "- Volume: " + Math.round(Gallons * 10) / 10 + " Gallons\r\n"; // Gallons output as a default for unclear measurement units
	}
    var output = output + "- Original gravity: " + OG; // Add original gravity target
		if (OG < 1.2) {
			var output = output + " SG\r\n"; // for specific gravity
		} 
		else {
			var output = output + " &deg;P\r\n"; // for degrees Plato
		} 
	var output = output + "- IBU: " + IBU + "\r\n"; // Add the overall IBUs 
	// BU/GU source: https://brucrafter.com/gravity-to-bitterness-ratio-calculator/
	var output = output + "- BU/GU: " + Math.round((IBU / ((SG - 1) * 1000)) * 100) / 100 + "\r\n"; // Add bitterness units / gravity units ratio

// Determine the extract amounts. The formula is from the numerator of Holle, p. 4; 1 is for liters/kg; The OG in Plato is converted to SG units for this calculation via the PlatoToSG function. Plato is used as a decimal rather than percentage. 
    var ExtractWeight = Liters * 1 * SG * Plato/100;
	console.log("Extract weight needed: ",ExtractWeight," kg");
// Adjust ExtractWeight downward if there are fixed amounts of LME and/or DME for partial mash recipes.
// Yield adjustments are made for water content. LME = 76% yield, DME = .96% yield. 
	if (DME > 0 || LME > 0) {
		if (MeasureUnit == "METRIC") {
			ExtractWeight = ExtractWeight - (LME * .76) - (DME * .96);
		}
		else {
			ExtractWeight = ExtractWeight - poundsToKilograms(LME * .76) - poundsToKilograms(DME * .96); 
		}
	}
	//Error message for too much extract
	if (ExtractWeight < 0 && Vol > 0) {
		var output = output + "*** Error: There is more malt extract than this recipe requires. ***\r\n"; 
	}
// Determine the Extract contribution for each grain or sugar in the grain bill
// Malt values: name, sugar (0 = false, 1 = true), percent, lovibond, yield (coarse-grind as-is)
// 1.04621 is the extract potential of sucrose. Applies to coversion of either malt or sugar. Pierce (2006) BYO magazine
    for (i = 0; i < MaltSugar.length; i++) {
		if (MaltSugar[i][0] == "None" || MaltSugar[i][0] == "none") {
			continue; // Skip the following if the field value is none. 
		} 
		if (MaltSugar[i][4] < 1.5) {
			MaltSugar[i][4] = (((MaltSugar[i][4] * 1000) - 1000) / 46.21) * 100; // Converts SG values to percentages
		}
		if (MaltSugar[i][1] == 0) { 
			var PercentGrain = ((MaltSugar[i][4]/100) * (MashEfficiency/100)); // Figure percentage for malts, with mash efficiency
		}
        else if (MaltSugar[i][1] == 1) { 
			var PercentGrain = ((MaltSugar[i][4]/100) * 1.0); // Pecentages for sugars, 100% mash efficiency
	    }
// Formula (grain percentage * total extract weight needed) / percent of grain that can be converted to extract
	var GrainWeight = ((MaltSugar[i][2] / 100) * ExtractWeight) / PercentGrain; 
	var TotalWeight = TotalWeight + GrainWeight; // Add this present weight to the total weight

// Color calculations for malt color units 
// This calculation is done with pounds and gallons because a metric version isnt in my sources.
// MaltSugar[i][3] is the lovibond value for each grain. 
	var MCU = MCU + (kilogramsToPounds(GrainWeight) * MaltSugar[i][3]);
// Save output for each grain depending upon the desired weight unit
    if (MeasureUnit == "METRIC") { 
	var TempOutput = TempOutput + (i + 1) + ". " + MaltSugar[i][0] + " (" + MaltSugar[i][2] + "%): " +
	    Math.round(GrainWeight*1000) / 1000 + " kg\r\n"; // Metric output
    }
	else { 
	    var TempOutput = TempOutput + (i + 1) + ". " + MaltSugar[i][0] + " (" + MaltSugar[i][2] + "%): " +
		Math.round(kilogramsToPounds(GrainWeight) * 100) / 100 + " lb\r\n"; // Output for US
	}
	}
// Finish color output
    var MCU = MCU / Gallons; // MCU calculations are finished by dividing by gallons
    var SRM = 1.4922 * Math.pow(MCU,.6859); // Morey equation converts Malt color units to SRM
    var output = output + "- Color: " + Math.round(SRM) + " SRM\r\n\r\n"; 
// Finish the grain/sugar output and start the hops output
    var output = output + "## Malts and/or Sugars\r\n"; // Malt section header, markdown level 2
    var output = output + TempOutput; // Individual grain/sugar contribution    
//Start output with LME or DME weights for partial mash recipes
	if (DME > 0) {
		var i = i + 1; //Advance the previous counter by one
		if (MeasureUnit == "METRIC") {
			var output = output + i + ". Dry malt extract: " + DME + " kg\r\n";
		}
		else {
			var output = output + i + ". Dry malt extract: " + DME + " lb\r\n";
		}
	}
	if (LME > 0) {
		var i = i + 1; //Advance the previous counter by one
		if (MeasureUnit == "METRIC") {
			var output = output + i + ". Liquid malt extract: " + LME + " kg\r\n";
		}
		else {
			var output = output + i + ". Liquid malt extract: " + LME + " lb\r\n";
		}
	}
	var output = output + "\r\n"; // Line break and the end of the malts/sugars
    var output = output + "- Total grain/sugar weight: "; // Weight of total grain/sugar  
	console.log("DME before total: ",DME);
	console.log("TotalWeight before total: ",TotalWeight);
    if (MeasureUnit == "METRIC") {
	var TotalWeight = TotalWeight + DME + LME; //Add malt extract weights to the total
	var output = output + Math.round(TotalWeight*1000)/1000 + " kg\r\n"// metric output
		} 
    else {
	    var TotalWeight = TotalWeight + poundsToKilograms(DME) + poundsToKilograms(LME); //Add malt extract weights to the TotalWeight variable as kilograms
		var output = output + Math.round(kilogramsToPounds(TotalWeight)*100)/100 + " lb\r\n"; // US output
		}
	var output = output + "\r\n"; //Add line break at the end of the malt/sugar area
// IBU calculations
// This estimates boiling gravity and volume at different time points from target original gravity. 
// A 15% boil off rate is used assuming that homebrewers use small kettles and have high boil off rates.
// Source: https://learn.kegerator.com/how-to-calculate-water-loss-during-brewing/
// Hop array order: name, percent of IBUs, alpha acid percentage, boil time in minutes, estimated boil volume, utilization value (added from calculations), IBUs calculated from hop weights, etc. 
    var output = output + "## Hops and Boil Additions\r\n"; 
    for (i = 0; i < Hops.length; i++) {
		var GravityPoints = ((SG * 1000) - 1000) * Liters; // Calculate gravity points with liters
		var EstBoilVolume = Liters * (1 + ((Hops[i][3] / 4) / 100)); // Estimate volume at different boil times, 15% per 60 minutes. 60 / 4 = 15. 
		var EstBoilGravity = GravityPoints / EstBoilVolume; // Estimated boil gravity in points
		var EstBoilGravity = 1 + (EstBoilGravity / 1000); // Convert estimated gravity calculations back to SG form
		var Utilization  = (1.65 * Math.pow(0.000125, (EstBoilGravity - 1))) * ((1 - Math.exp(-.04 * Hops[i][3])) / 4.15); // Utilization; Tinseth formula
		if (Hops[i][3] < 2) {
			var Utilization = .05; // Use 5% utilization for steeping or flame out hops (not zero given by the formula).
		}
		Hops[i].push(EstBoilVolume); // Add estimated boil volume for this hop time to the hop array
		Hops[i].push(Utilization); // Adds the utilization value to the hop array
// Calculate weight needed for target IBU with a rearranged IBU formula. The AA% and utilization are expressed as decimals. 
// Simplified: Weight = (volume * IBU) / (AA% * Utilization * 1000)  Note: 1000 is for metric units - liters and grams
// The IBU*Percent (as decimal) part is for dividing up multiple hop additions. 
	var HopGrams = (Hops[i][4] * (IBU * (Hops[i][1] / 100 ))) / ((Hops[i][2] / 100) * Hops[i][5] * 1000);
// This variable is an IBU calculation check to show that inputs  = outputs. It sums IBUs for  multiple hop additions.
// Source for metric formula: https://blog.homebrewing.org/how-to-calculate-ibus/		
// Tinseth IBU = (AA% * weight * Utilization * 1000) / volume; Weight is grams and volume is liters. AA% and utilization% are in decimal form.
	var IBUtemp = ((Hops[i][2] / 100) * HopGrams * Hops[i][5] * 1000) / Hops[i][4]; // IBUs for this hop addition
	Hops[i].push(IBUtemp); // Add IBUs to the hop array
	var IBUcheck = IBUcheck + IBUtemp; // Cumulative IBUs for checking hop calculations 	
// Save output for each hop depending upon the desired weight units
        if (MeasureUnit == "METRIC") { // Output for metric
            var output = output + (i + 1) + ". " + Hops[i][0] + " (" + Hops[i][1] + "%): " + Math.round(HopGrams) + " g for " + Hops[i][3] + " minutes\r\n";
		} 
		else { // Output for US units
			var output = output + (i + 1) + ". " + Hops[i][0] + " (" + Hops[i][1] + "%): " + Math.round(gramsToOunces(HopGrams)*10)/10 + " oz. for " + Hops[i][3] + " minutes\r\n";
   		} 
    }
// Dry hop additions
    for (i = 0; i < DryHops.length; i++) {
	if (DryHops[i][0] == "None" || DryHops[i][0] == "none") {
	    continue; // Skip the following if the dry hop value is none. 
	} 
	var DryHopWeight = DryHops[i][1] * Liters; // Calculate dry hops from grams per liter 
	var output = output + "- Dry hop (" + DryHops[i][1] + " g/l): "; // Percentage
	var output = output + DryHops[i][0] + " "; // Add the hop name to the output
	if (MeasureUnit == "METRIC") {
	    var output = output + Math.round(DryHopWeight) + " g\r\n"; // Dry hops in grams
	}
	else {
	    var output = output + Math.round(gramsToOunces(DryHopWeight)*10)/10 + " oz.\r\n"; // Dry hops in ounces
	}
    }
// Spices or other late kettle additions
    for (i = 0; i < Spice.length; i++) {
	if (Spice[i][0] == "None" || Spice[i][0] == "none") {
	    continue; // Skip the following if the value is none. 
	} 
	var SpiceWeight = Spice[i][1] * Liters; // Calculate weight from grams per liter 
	var output = output + "- Late boil (" + Spice[i][1] + " g/l): "; // Percentage
	var output = output + Spice[i][0] + " "; // Add the name to the output
	if (MeasureUnit == "METRIC") {
	    var output = output + Math.round(SpiceWeight*100)/100 + " g\r\n"; // Spices in grams
	}
	else {
	    var output = output + Math.round(gramsToOunces(SpiceWeight)*100)/100 + " oz.\r\n"; // Spices in ounces
	}
    }
	var output = output + "\r\n"; //Add a line break at the end of the late boil additions

// Yeast pitching rate calculations
    var output = output + "## Yeast\r\n"; // Add yeast output header
// Base liquid yeast pitch from White and Zainasheff, Yeast, p. 122; 
    var YeastCellsToPitch = (1 * (Liters * 1000) * Plato) / 1000; // One million cells / ml / degree plato, rounded to a billion cells
// Ale calculations, with higher pitch rates for beers over 16 plato
    if (YeastType == 0) { 
	if (Plato <= 16) {
	    var output = output + "- Option 1: " + Math.round(.5 * Liters * 10) / 10 + " to " + Math.round(1.0 * Liters * 10) / 10 +
		" grams of dry ale yeast (rate: 0.5 to 1.0 g/L)\r\n";
	    var output = output + "- Option 2: " + Math.round(.75 * YeastCellsToPitch) +
		" billion cells of liquid ale yeast (rate: .75 million/mL/&deg;P)\r\n";
	}
	else {
	    var output = output + "- Option 1: " + Math.round(1.0 * Liters * 10) / 10 +
		" grams of dry ale yeast (rate: 1.0 g/L)\r\n";
	    var output = output + "- Option 2: " + Math.round(1.0 * YeastCellsToPitch) +
		" billion cells of liquid ale yeast (rate: 1.0 million/mL/&deg;P)\r\n";
	}
    }
// Lager calculations with higher pitch rates for beers over 16 plato
    if (YeastType == 1) {
       if (Plato <= 16) {
	   var output = output + "- Option 1: " + Math.round(1.0 * Liters * 10) / 10 + " to " + Math.round(2.0 * Liters * 10) / 10 + " grams of dry lager yeast\r\n";
	   var output = output + "- Option 2: " + Math.round(1.5 * YeastCellsToPitch) + " billion cells of liquid lager yeast (rate: 1.5 million/mL/&deg;P)\r\n";
	    }
	else {
            var output = output + "- Option 1: " + Math.round(2.0 * Liters * 10) / 10 + " grams of dry lager yeast\r\n"; 
            var output = output + "- Option 2: " + Math.round(2.0 * YeastCellsToPitch) + " billion cells of liquid lager yeast (rate: 2.0 million/mL/&deg;P)\r\n";
	}
    }
	var output = output + "\r\n"; // Line break at the end of the yeast section
// Calculate priming; CO2measured and needed sugar
// Documentation and explanation: https://www.topdownbrew.com/docs/CarbonationDocumentation.html
// The constant was updated from 1.969 (Kaminski, 2019) to 1.96473214286 in February, 2025. 
	var output = output + "## Priming\r\n";
	if (MeasureUnit == "METRIC") {  // Conversion of celsius to fahrenheit for CO2 measured calculation
		var FermTemp = celsiusToFahrenheit(FermTemp);
	}
	// Note: The following calculation uses fahrenheit for temperature
	var CO2measured = 3.0378 - (0.050062 * FermTemp) + (0.00026555 * FermTemp * FermTemp);
	var GlucoseMonohydrate = ((CO2desired - CO2measured) * 1.96473214286 * Liters) / .44; // .44 glucose monohydrate value from McGill (2006)
	var Sucrose = ((CO2desired - CO2measured) * 1.96473214286 * Liters) / .51; // .51 sucrose value from McGill (2006)
	var DME = ((CO2desired - CO2measured) * 1.96473214286 * Liters) / .375; // .375 DME value from Kaminski (2019)
	if (MeasureUnit == "METRIC") {
	    var output = output + Math.round(GlucoseMonohydrate) + " g dextrose or " + Math.round(Sucrose) + " g sucrose or " + Math.round(DME) + " g DME"; // priming sugar in grams
	}
	else {
	    var output = output + Math.round(gramsToOunces(GlucoseMonohydrate)*10)/10 + " oz. dextrose or " + Math.round(gramsToOunces(Sucrose)*10)/10 + " oz. sucrose or " + Math.round(gramsToOunces(DME)*10)/10 + " oz. DME"; // priming sugar in ounces
	}
// Send the output to the web page
    document.getElementById("results").innerHTML = output; 
// Outputs for debugging 
	console.log("Original gravity: ", OG);
	console.log("Mash efficiency: ", MashEfficiency);
	console.log("Volume: ", Vol);
	console.log("Measurement units: ",MeasureUnit);
	console.log("Liters: ",Liters);
	console.log("Gallons: ",Gallons);
	console.log("IBU: ", IBU);
	console.log("Plato: ", Plato);
	console.log("SG: ", SG);
    console.log("MaltSugar array: ",MaltSugar);
    console.log("Hops: ",Hops);
	console.log("Spice array: ",Spice);
    console.log("Dry Hops: ",DryHops);
	console.log("YeastType: ",YeastType);
	console.log("Measure unit: ",MeasureUnit);
    console.log("IBUcheck for inputs = outputs: ",IBUcheck);
	console.log("GrainWeight: ",GrainWeight);
	console.log("CO2desired: ",CO2desired); 
	console.log("CO2measured: ",CO2measured);
	console.log("Fermentation temperature: ",FermTemp);
    console.log("MaltCheck (should add to 100%): ",MaltCheck);
    console.log("HopCheck (should add to 100%): ",HopCheck);

// IBU estimated boiling gravity and boiling volume was improved and verified.
// To do: Add to hops array
}
