// Armscye calculation

var FP_PRECISION = 2;

function toNum(x) {
  if (x && (!isNaN(Number(x)))) {
	return parseFloat(x);
  }
  return 0;
}
function toInt(x) {
  return toNum(x).toFixed(0);
}

function makeNumArray(numIndices, defaultValue) {
  defaultValue = toNum(defaultValue);
  var returnArray = new Array();
  for (var i = 0; i < numIndices; i++) {
	returnArray[i] = defaultValue;
  }
  return $(returnArray);
}

function jsStupidFactor(x) {
  return toNum(Math.round(toNum(x) * Math.pow(10, FP_PRECISION)));
}

function jsStupidUnfactor(x) {
  return toNum(toNum(x) / Math.pow(10, FP_PRECISION)).toFixed(FP_PRECISION);
}

function jsStupidFloat(x) {
  return jsStupidUnfactor(jsStupidFactor(x));
}

function addFloat(x, y) {
  return jsStupidUnfactor(jsStupidFactor(x) + jsStupidFactor(y));
}

function subtractFloat(x, y) {
  return jsStupidUnfactor(jsStupidFactor(x) - jsStupidFactor(y));
}

function multiplyFloat(x, y) {
  return jsStupidUnfactor(jsStupidUnfactor(jsStupidFactor(x) * jsStupidFactor(y)));
}

function divideFloat(x, y) {
  return jsStupidUnfactor(jsStupidFactor(jsStupidFactor(x) / jsStupidFactor(y)));
}

function addFloatArray(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (x.length == y.length) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = addFloat(x[i], y[i]);
	}
  }
  return resultArray;
}

function subtractFloatArray(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (x.length == y.length) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = subtractFloat(x[i], y[i]);
	}
  }
  return resultArray;
}

function multiplyFloatArray(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (x.length == y.length) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = multiplyFloat(x[i], y[i]);
	}
  }
  return resultArray;
}

function multiplyFloatArrayScalar(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (Object.isArray(x) &&  Object.isNumber(y)) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = multiplyFloat(x[i], y);
	}
  }
  return resultArray;
}

function divideFloatArray(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (x.length == y.length) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = divideFloat(x[i], y[i]);
	}
  }
  return resultArray;
}

function divideFloatArrayScalar(x, y) {
  var resultArray = new Array();
  if (Object.isArray(x)) {
	resultArray = makeNumArray(x.length, 0);
  }
  if (Object.isArray(x) &&  Object.isNumber(y)) {
	for (var i = 0; i < x.length; i++) {
	  resultArray[i] = divideFloat(x[i], y);
	}
  }
  return resultArray;
}

function roundFloat(num, places) {
//  var round_factor = Math.pow(10, places);
//  var x = (Math.round(num * round_factor)) / round_factor;
  return toNum(num).toFixed(places);
}

function in_to_cm (inches) {
  if (inches) {
	return multiplyFloat(toNum(inches), 2.54);
  }
  return 0;
}
function cm_to_in (cm) {
  if (cm) {
	return divideFloat(toNum(cm), 2.54);
  }
  return 0;
}

function hypotenuseArray (a, b) {
  var resultArray = new Array();
  if (a.length == b.length) {
	for (var i = 0; i < a.length; i++) {
	  resultArray[i] = jsStupidFloat(Math.sqrt(addFloat(jsStupidFloat(Math.pow(toNum(a[i]), 2)), jsStupidFloat(Math.pow(toNum(b[i]), 2)))));
	}
  }
  return resultArray;
}

function getWidthArray (b, c) {
  var resultArray = new Array();
  if (b.length == c.length) {
	for (var i = 0; i < a.length; i++) {
	  resultArray[i] = jsStupidFloat(Math.sqrt(subtractFloat(jsStupidFloat(Math.pow(toNum(c[i]), 2)), jsStupidFloat(Math.pow(toNum(b[i]), 2)))));
	}
  }
  return resultArray;
}

function getHeightArray (a, c) {
  var resultArray = new Array();
  if (a.length == c.length) {
	for (var i = 0; i < a.length; i++) {
	  resultArray[i] = jsStupidFloat(Math.sqrt(subtractFloat(jsStupidFloat(Math.pow(toNum(c[i]), 2)), jsStupidFloat(Math.pow(toNum(a[i]), 2)))));
	}
  }
  return resultArray;
}

function updateUnits(unitName) {
  mySettings.units = unitName;
  
  var unitsAry = $$('span.unit');
  unitsAry.each(function (u) {
	u.innerHTML = unitName;
  });
  // convert inches to centimeters
  if (unitName == 'cm') {
	var unitInputs = $$('input.unit_in');
	unitInputs.each(function (i) {
	  i.removeClassName('unit_in');
	  i.addClassName('unit_cm');
	  var valAry = i.value.split(",");
	  valAry.each(function (v,idx) {
		valAry[idx] = in_to_cm(toNum(v));
	  });
	  i.value = valAry.join(",");
	});
	
	unitInputs = $$('input.unit_in_inverse');
	unitInputs.each(function (i) {
	  i.removeClassName('unit_in_inverse');
	  i.addClassName('unit_cm_inverse');
	  var valAry = i.value.split(",");
	  valAry.each(function (v,idx) {
		valAry[idx] = cm_to_in(toNum(v));
	  });
	  i.value = valAry.join(",");
	});
  }
  // convert centimeters to inches
  if (unitName == 'in') {
	var unitInputs = $$('input.unit_cm');
	unitInputs.each(function (i) {
	  i.removeClassName('unit_cm');
	  i.addClassName('unit_in');
	  var valAry = i.value.split(",");
	  valAry.each(function (v,idx) {
		valAry[idx] = cm_to_in(toNum(v));
	  });
	  i.value = valAry.join(",");
	});
	
	unitInputs = $$('input.unit_cm_inverse');
	unitInputs.each(function (i) {
	  i.removeClassName('unit_cm_inverse');
	  i.addClassName('unit_in_inverse');
	  var valAry = i.value.split(",");
	  valAry.each(function (v,idx) {
		valAry[idx] = in_to_cm(toNum(v));
	  });
	  i.value = valAry.join(",");
	});
  }
  calculate();
}

function updateSizeCount() {
  var count = toInt($F('size_count')) || 1;
  mySettings.size_count = count;
  $('size_count').value = count;
}

function forceInteger(elem) {
  var x = toInt(elem.value);
  if (x > 0) {
	elem.value = x;
  }
  else {
	elem.value = 1;
  }
}

function toCalcArray(dataName) {
  var expectedCount = mySettings.size_count || 1;
  var val = $F(dataName);
  
  if (Object.isString(val) && val == "") {
	return makeNumArray(expectedCount, 0);
  }
  
  if (Object.isArray(val)) {
	return val;
  }
  
  if (val) {
	var newArray = val.split(",", expectedCount);
	if (newArray.length == expectedCount) {
	  newArray.each(function (v) {
		v.strip();
	  });
	}
	else {
	  $R(newArray.length + 1, expectedCount).each(function (i) {
		newArray.push(0);
	  });
	  $(dataName).value = newArray.join(",");
	}
	return $(newArray);
  }
}

function displayArray(a) {
//  return a.each(function (i) {i = '<span class="output_value">i<\/span>';}).join(" &nbsp; ");
  return '<span class="output_value">' + a.join('<\/span> <span class="output_value">') + '<\/span>';
}

function setMultivalueDisplay() {
  var count = toInt(mySettings.size_count);
  if (count == 1) {
	$$('tr.multivalue_instructions').each(function (i) { i.hide(); });
  }
  else {
	var sample_ary = $([6, 9, 12, 15, 18]);
	var sample_list_string = '&nbsp; ' + sample_ary[0];
	for (var i = 1; i < count; i++) {
	  sample_list_string = sample_list_string + ', ' + sample_ary[i];
	}
	$$('span.sample_list').each(function (i) { i.innerHTML = sample_list_string; });
	$$('tr.multivalue_instructions').each(function (i) { i.show(); });
  }
}

var Settings = Class.create({
  initialize: function() {
	this.units = "in",
	this.stitch_gauge = 0;
	this.row_gauge = 0;
	this.size_count = 1;
  }
});

var Body = Class.create({
  initialize: function() {
	this.armhole_depth = new Array(),
	this.initial_bo_body = new Array(),
	this.addl_bo = new Array(),
	this.num_decreases_body = new Array(),
	this.num_decrease_rows_body = new Array(),
	this.half_armscye = new Array(),
	this.work_straight = new Array()
  }
});

var Sleeve = Class.create({
  initialize: function() {
	this.final_bo = new Array(),
	this.num_bo = new Array(),
	this.num_bo_rows = new Array(),
	this.initial_stitches = new Array(),
	this.initial_bo_sleeve = new Array(),
	this.num_decreases_rem = new Array(),
	this.rows_rem = new Array(),
	this.cap_height = new Array(),
	this.cap_width = new Array()
  }
});

function calculate() {
  
  // Show or hide multi-value instructions
  setMultivalueDisplay();
  
  // ----------------------------------------------------------------
  // Settings
  mySettings.units = $F('units');
  mySettings.stitch_gauge = toNum($F("stitch_gauge"));
  mySettings.row_gauge = toNum($F("row_gauge"));
  var size_count = toInt($F('size_count')) || 1;
  mySettings.size_count = size_count;
  
  // ----------------------------------------------------------------
  // Body
  myBody.armhole_depth = toCalcArray("armhole_depth");
  myBody.initial_bo_body = toCalcArray("initial_bo_body");
  myBody.addl_bo = toCalcArray("addl_bo");
  myBody.num_decreases_body = toCalcArray("num_decreases_body");
  myBody.num_decrease_rows_body = toCalcArray("num_decrease_rows_body");
  
  var decrease_length_body = divideFloatArrayScalar(myBody.num_decreases_body, mySettings.stitch_gauge);
  var decrease_height_body = divideFloatArrayScalar(myBody.num_decrease_rows_body, mySettings.row_gauge);
  var decrease_edge_length_body = hypotenuseArray(decrease_length_body, decrease_height_body);
  myBody.work_straight = subtractFloatArray(myBody.armhole_depth, decrease_height_body);
  myBody.half_armscye = addFloatArray(addFloatArray(decrease_edge_length_body, myBody.work_straight), addFloatArray(divideFloatArrayScalar(myBody.initial_bo_body, mySettings.stitch_gauge), divideFloatArrayScalar(myBody.addl_bo, mySettings.stitch_gauge)));
  var armscye_perimeter = multiplyFloatArrayScalar(myBody.half_armscye, 2);
  
  // ----------------------------------------------------------------
  // Sleeve
  mySleeve.final_bo = toCalcArray("final_bo");
  mySleeve.num_bo = toCalcArray("num_bo");
  mySleeve.num_bo_rows = toCalcArray("num_bo_rows");
  mySleeve.initial_stitches = toCalcArray("initial_stitches");
  mySleeve.initial_bo_sleeve = toCalcArray("initial_bo_sleeve");
  
  var half_final_bo_length = divideFloatArrayScalar(divideFloatArrayScalar(mySleeve.final_bo, 2), mySettings.stitch_gauge);
  var decrease_length_sleeve = divideFloatArrayScalar(mySleeve.num_bo, mySettings.stitch_gauge);
  var decrease_height_sleeve = divideFloatArrayScalar(mySleeve.num_bo_rows, mySettings.row_gauge);
  var decrease_edge_length_sleeve = hypotenuseArray(decrease_length_sleeve, decrease_height_sleeve);
  var half_initial_stitches = divideFloatArrayScalar(mySleeve.initial_stitches, 2);
  var bo_inches_sleeve = divideFloatArrayScalar(addFloatArray(mySleeve.initial_bo_sleeve, mySleeve.num_bo), mySettings.stitch_gauge);
  
  var perimeter_rem = subtractFloatArray(subtractFloatArray(subtractFloatArray(myBody.half_armscye, half_final_bo_length), decrease_edge_length_sleeve), divideFloatArrayScalar(mySleeve.initial_bo_sleeve, mySettings.stitch_gauge));
  mySleeve.num_decreases_rem = subtractFloatArray(subtractFloatArray(subtractFloatArray(half_initial_stitches, mySleeve.initial_bo_sleeve), mySleeve.num_bo), divideFloatArrayScalar(mySleeve.final_bo, 2));
  var width_rem = divideFloatArrayScalar(mySleeve.num_decreases_rem, mySettings.stitch_gauge);
  var height_rem = getHeightArray(width_rem, perimeter_rem);
  mySleeve.rows_rem = multiplyFloatArrayScalar(height_rem, mySettings.row_gauge);
  mySleeve.cap_height = divideFloatArrayScalar(addFloatArray(mySleeve.num_bo_rows, mySleeve.rows_rem), mySettings.row_gauge);
  mySleeve.cap_width = divideFloatArrayScalar(mySleeve.initial_stitches, mySettings.stitch_gauge);
  
  $('bodycalcoutput').update('');
  $('bodycalcoutput').insert(makeBodyTable(mySettings, myBody));
  $('sleevecalcoutput').update('');
  $('sleevecalcoutput').insert(makeSleeveTable(mySettings, mySleeve));
  
  // reset global "has already seen the error message" variable
  SHOWN_ERROR = false;
}

function makeBodyTable (mySettings, myBody) {
  var tbl = new Element('table');
  var tbody = new Element('tbody');
  
  tbody.insert(makeSizeHeadRow());
  tbody.insert(makeRow("After decreases, work straight", myBody.work_straight, mySettings.units));
  tbody.insert(makeRow("Half armscye", myBody.half_armscye, mySettings.units));
  tbody.insert(makeEmptyRow());
  
  tbl.insert(tbody);
  tbl.addClassName('calcoutput');
  return tbl;
}

function makeSleeveTable (mySettings, mySleeve) {
  var tbl = new Element('table');
  var tbody = new Element('tbody');
  
  tbody.insert(makeSizeHeadRow());
  tbody.insert(makeRow("Sleeve cap max width", mySleeve.cap_width, mySettings.units));
  tbody.insert(makeRow("Sleeve cap height", mySleeve.cap_height, mySettings.units));
  tbody.insert(makeRow("Decreases remaining", mySleeve.num_decreases_rem, ''));
  tbody.insert(makeRow("Rows remaining", mySleeve.rows_rem, ''));
  tbody.insert(makeEmptyRow());
  
  tbl.insert(tbody);
  tbl.addClassName('calcoutput');
  return tbl;
}

function makeSizeHeadRow() {
  var tr = new Element('tr');
  var tdlabel = new Element('td');
  tdlabel.innerHTML = "Size";
  tdlabel.addClassName('labelhead');
  tr.insert(tdlabel);
  $R(1, toInt($F("size_count"))).each(function (i) {
	var tdval = new Element('td').update(i);
	tdval.addClassName('datahead');
	tr.insert(tdval);
  });
  tr.insert(new Element('td').update(''));
  return tr;
}

function makeRow(label, values, unit) {
  var tr = new Element('tr');
  var tdlabel = new Element('td').update(label);
  tdlabel.addClassName('label');
  tr.insert(tdlabel);
  values.each(function (i) {
	var tdval = new Element('td').update(i);
	tdval.addClassName('data');
	if (i < 0) {
	  tdval.addClassName('dataerror');
	}
	tr.insert(tdval);
  });
  tr.insert(new Element('td').update(unit).addClassName('unit'));
  return tr;
}

function makeEmptyRow() {
  var tr = new Element('tr');
  var tdlabel = new Element('td').update('&nbsp;');
  tdlabel.addClassName('labelbottom');
  tr.insert(tdlabel);
  $R(1, toInt($F("size_count"))).each(function (i) {
	var tdval = new Element('td').update('&nbsp;');
	tdval.addClassName('databottom');
	tr.insert(tdval);
  });
  tr.insert(new Element('td').update('&nbsp;').addClassName('unitbottom'));
  return tr;
}



// ----------------------------------------------------------------------------
// Printable output

function printme() {
  calculate();
//  var settingsTable = makeBigSettingsTable(mySettings);
  var bodyTable = makeBigBodyTable(mySettings, myBody);
  var sleeveTable = makeBigSleeveTable(mySettings, mySleeve);
  
  var html = '';
  html = html + '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\
				 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">\
				 <head>\
				   <title> Armscye : knitting calculator </title>\
				   <link href="css/armcalc_style.dev.css" rel="stylesheet" type="text/css" />\
				 </head>\
				 <body><div id="mainwhite">';
  
  html = html + '<h2>Armscye Calculation</h2> \
				 <h3>General</h3> \
				 <table class="printoutput"> \
				  <tr><td class="labelhead">&nbsp;</td><td class="datahead">&nbsp;</td><td></td></tr> \
				  <tr><td class="label">Stitch Gauge</td><td class="data">' + mySettings.stitch_gauge + '</td><td class="unit">sts/' + mySettings.units + '</td></tr> \
				  <tr><td class="label">Row Gauge</td><td class="data">' + mySettings.row_gauge + '</td><td class="unit">rows/' + mySettings.units + '</td></tr> \
				  <tr><td class="labelbottom">&nbsp;</td><td class="databottom">&nbsp;</td><td class="unitbottom">&nbsp;</td></tr> \
				 </table>';
  
  html = html + '<h3>Body</h3><table class="printoutput">' + bodyTable.innerHTML + '</table><h3>Sleeve</h3><table class="printoutput">' + sleeveTable.innerHTML;
  html = html + '</div></body></html>';
  
  var win = window.open();
  var doc = win.document;
  doc.write(html);
  doc.close();
}

function makeBigBodyTable (mySettings, myBody) {
  var tbl = new Element('table');
  var tbody = new Element('tbody');
  
  tbody.insert(makeSizeHeadRow());
  tbody.insert(makeRow("Armhole depth", myBody.armhole_depth, mySettings.units));
  tbody.insert(makeRow("Initial armhole bind-off stitches (each side)", myBody.initial_bo_body, ''));
  tbody.insert(makeRow("Additional armhole bind-off stitches (each side)", myBody.addl_bo, ''));
  tbody.insert(makeRow("Number of armhole decreases...", myBody.num_decreases_body, ''));
  tbody.insert(makeRow("...over how many rows?", myBody.num_decrease_rows_body, ''));
  tbody.insert(makeRow("After decreases, work straight", myBody.work_straight, mySettings.units).addClassName('results'));
  tbody.insert(makeRow("Half armscye", myBody.half_armscye, mySettings.units));
  tbody.insert(makeEmptyRow());
  
  tbl.insert(tbody);
  tbl.addClassName('calcoutput');
  return tbl;
}

function makeBigSleeveTable (mySettings, mySleeve) {
  var tbl = new Element('table');
  var tbody = new Element('tbody');
  
  tbody.insert(makeSizeHeadRow());
  tbody.insert(makeRow("Initial stitch count at armhole (max width before bind-off)", mySleeve.initial_stitches, ''));
  tbody.insert(makeRow("Initial armhole bind-off (each side)", mySleeve.initial_bo_sleeve, ''));
  tbody.insert(makeRow("Final bind-off at top of sleeve cap", mySleeve.final_bo, ''));
  tbody.insert(makeRow("Number bind-off stitches before final...", mySleeve.num_bo, ''));
  tbody.insert(makeRow("...over how many rows?", mySleeve.num_bo_rows, ''));
  tbody.insert(makeRow("Sleeve cap max width", mySleeve.cap_width, mySettings.units).addClassName('results'));
  tbody.insert(makeRow("Sleeve cap height", mySleeve.cap_height, mySettings.units));
  tbody.insert(makeRow("Decreases remaining", mySleeve.num_decreases_rem, ''));
  tbody.insert(makeRow("Rows remaining", mySleeve.rows_rem, ''));
  tbody.insert(makeEmptyRow());
  
  tbl.insert(tbody);
  tbl.addClassName('calcoutput');
  return tbl;
}

