// Atmosphere Calculator
//
// Copyright 1996-1997 by Mark E. (melbrec@geocities.com). All Rights Reserved.
// Based on the formulas used in meteorology
// Note: Comments will be sparse to minimize loading time.

var defaultRoundSize=1;
var defaultPressure = 1013;

function createEnumObject()
{
	obj = new Object();
	var str="";
	for (var i = 0; i < arguments.length; i++)
	{
		str = "obj."+arguments[i]+"="+i;
		eval (str);
	}

	obj.length = arguments.length;
	return obj;
}

var ioEnum = createEnumObject("input", "output", "both");
var unitTypeEnum = createEnumObject("english","metric","scientific");
var degreeEnum = createEnumObject("celsius","fahr","kelvin");
var speedEnum = createEnumObject("ms", "mph", "knots", "kmh");
var forceEnum = createEnumObject("mb", "inches", "kpa");

var degreeUnitDefs = new Array(degreeEnum.fahr, degreeEnum.celsius, degreeEnum.kelvin);
var speedUnitDefs = new Array(speedEnum.mph, speedEnum.ms, speedEnum.ms);
var forceUnitDefs = new Array(forceEnum.inches, forceEnum.mb, forceEnum.kpa);
var forceUnitDefs2 = new Array(forceEnum.mb, forceEnum.mb, forceEnum.kpa);

var degreePrecisionDefs = new Array(1,1,2);
var speedPrecisionDefs = new Array(1,1,1,1);
var forcePrecisionDefs = new Array(0,2,1);
var forcePrecisionDefs2 = new Array(2,3,3);
var genericPrecisionDefs = new Array(8, 8); // second 8 is a dummy
var genericPrecisionDefs2 = new Array(1,1); // second 1 is a dummy

var mstomi=2.236936284;
var mstonm=1.969362;
var kmtomi = 0.62137119;
var kmtonm = 0.547045;
var mitonm = 0.8681069
var mbtoin = 0.029528744;

var invalidValue = Number.POSITIVE_INFINITY;

function sgn(v)
{
	if (v > 0) return 1;
	else if (v < 0) return -1;
	else return 0;
}

function roundNum(value, precision)
{
// Adapted from code in Netscape's Javascript library

	var sign = sgn(value);

	var adjust=0;
	if ((value !=0) && (Math.abs(value) < 1)) {adjust=sign; value += adjust;}

	precision = parseInt(precision);

	var whole = "" + Math.round(value * Math.pow(10, precision));
	var decPoint = whole.length - precision;
	var result = "";

	if(decPoint != 0)
	{
		if (adjust == 0) {result += whole.substring(0, decPoint);}
		else
		{
			if (whole.charAt(0) != "-") result += "0";
			else result += "-0";
		}
		if (decPoint == whole.length) return result;
		result += ".";
		result += whole.substring(decPoint, whole.length);
	}
	else
		result += whole;

	return result;
}

function fahrToCelc(fahr)
{
	return (( fahr-32 ) * 5 / 9);
}

function celcToFahr(celc)
{
	return (celc * 9 / 5 + 32);
}

function calcVaporPressure(t)
{
	return 6.112 * Math.pow(10, (7.5 * t) / (237.7 + t));
}

function calcTcFromVaporPressure ( vp )
{
	var logs=0;
	logs=Math.log(vp/6.112)/Math.LN10;
	return (237.7*logs)/(7.5-logs);
}

function calcRelHumidity(actual, satur)
{
	return actual / satur * 100.0;
}

function calcSpecHumidity(e, press )
{
	return (0.62197*e)/(press-(0.37803*e))*1000.0;
}

function calcMixingRatio(e, press )
{
	return ((0.62197*e)/(press-e))*1000.0;
}

function calcAbsHumidity(vp, t)
{
	return (vp*100)/(461.51*(273.15+t)) * 1000.0;
}

function calcWetbulb(press, t, dp)
{
// Calculates the wetbulb given the pressure (press) in mb, temperature (t) in
// celsius, and dewpoint (dp) in celsius.
// This algorithm uses a binary-search type approximation instead of a
// brute-force approach. You get a more accurate answer more quickly.
// In an interpreted (slow) language like Javascript, the more efficient the
// algorithm the better.

	var tmin=Math.min(dp,t);
	var tmax=Math.max(dp,t);
	var e=calcVaporPressure(dp);
	var vpcur;
	var peq;
	var diff;
	var tcur;

	while (true)
	{
		tcur=(tmax+tmin)/2;
		vpcur=calcVaporPressure(tcur);
		peq=0.000660*(1+0.00155*tcur)*press*(t-tcur);
		diff=peq-vpcur+e;

		if (Math.abs(diff)<0.01) break;

		if (diff<0) tmax=tcur;
		else tmin=tcur;
	}
	return tcur;
}

function calcHeatIndex(t, rh)
{
// Calculates the heat index given temperature(f), and relative humidity
// The heat index could have been calculated in one step but then it makes it
// even harder to read!

	var t2=t*t;
	var t3=t2*t;
	var rh2=rh*rh;
	var rh3=rh2*rh;

	var result =16.923 + 0.185212*t + 5.37941*rh - 0.100254*t*rh + 0.941695e-2 * t2 + 0.728898e-2 * rh2 + 0.345372e-3*t2*rh - 0.814971e-3*t*rh2 + 0.102102e-4*t2*rh2 - 0.38646e-4*t3 + 0.291583e-4*rh3 + 0.142721e-5*t3*rh + 0.197483e-6*t*rh3 - 0.218429e-7*t3*rh2 + 0.843296e-9*t2*rh3 - 0.481975e-10*t3*rh3;
	return result;
}

function calcHeatIndex2(t, rh)
{
	var t2=t*t;
	var rh2=rh*rh;

	var result =  -42.379 + 2.04901523 * t + 10.14333127 * rh - 0.22475541 * t * rh - 6.83783e-3 * t2 - 5.481717e-2*rh2 + 1.22874e-3*t2*rh + 8.5282e-4 * t * rh2 - 1.99e-6 * t2 * rh2;
	return result;
}

function calcWindChillF(t, v)
{
// Calculates the Wind Chill Index given temperature (in F), and speed (in mph)

	var wc=0.0817*(3.71*Math.sqrt(v)+5.81-0.25*v)*(t-91.4)+91.4;
	return (wc < t) ? wc : t;
}

function convertCelsius(obj, val)
{
	obj.values[degreeEnum.fahr]=celcToFahr(val);
	obj.values[degreeEnum.kelvin]=val+273.15;
}

function convertFahrenheit(obj, val)
{
	obj.values[degreeEnum.celsius]=fahrToCelc(val);
	obj.values[degreeEnum.kelvin]=obj.values[degreeEnum.celsius]+273.15;
}

function convertKelvin(obj, val)
{
	obj.values[degreeEnum.celsius]=val-273.15;
	obj.values[degreeEnum.fahr]=celcToFahr(obj.values[degreeEnum.celsius]);
}

function convertMs(obj, val)
{
	obj.values[speedEnum.kmh]=val*3.6;
	obj.values[speedEnum.mph]=val*mstomi;
	obj.values[speedEnum.knots]=val*mstonm;
}

function convertKmh(obj, val )
{
	obj.values[speedEnum.ms]=val / 3.6;
	obj.values[speedEnum.mph]=val*kmtomi;
	obj.values[speedEnum.knots]=val*kmtonm;
}

function convertMph(obj, val )
{
	obj.values[speedEnum.kmh]=val / kmtomi;
	obj.values[speedEnum.ms] = val / mstomi;
	obj.values[speedEnum.knots]=val * mitonm;
}

function convertKnots(obj, val)
{
	obj.values[speedEnum.kmh]=val / kmtonm;
	obj.values[speedEnum.ms]=val / mstonm;
	obj.values[speedEnum.mph] = val / mitonm;
}

function convertMb(obj, val )
{
	obj.values[forceEnum.inches]=val * mbtoin;
	obj.values[forceEnum.kpa]=val/10;
}

function convertInches(obj, val )
{
	obj.values[forceEnum.mb]=val / mbtoin;
	obj.values[forceEnum.kpa]=obj.values[forceEnum.mb]/10;
}

function convertKpa (obj, val)
{
	obj.values[forceEnum.mb]=val*10;
	obj.values[forceEnum.inches]=val*mbtoin*10;
}

function getValue()
{
	var u=this.unit;
	var args=getValue.arguments;
	if (args.length != 0) u=args[0];
	return this.values[u];
}

function emptyValue(val)
{
	return (val.length == 0) || (val == " ");
}

function setValue(v)
{
	var val = 1*v;
	var u=this.unit;

	if ((typeof(v)=="string") && (emptyValue(v)))
	{
		this.hasValue(false);
		return false;
	}

	if (arguments.length > 1) u=arguments[1];

	this.values[u] = val;
	if (this.conversion != null) {(this.conversion[u])(this, val);}

	return true;
}

function hasValue()
{
	if (arguments.length != 0) this.values[0] = invalidValue;

	return (this.values[0] != invalidValue);
}

function setUnit(u)
{
	this.unit=u;
	return this.unit;
}

function Temperature()
{
	var u = degreeEnum.celsius;
	var args = arguments;

	var val=0;
	if (args.length > 0) val = args[0];

	if (args.length > 1) u = args[1];

	this.values = new Array(0,1,2);
	this.conversion = new Array(convertCelsius, convertFahrenheit, convertKelvin);

	this.getValue = getValue;
	this.setValue = setValue;
	this.hasValue = hasValue;
	this.hasValue(false);

	this.setUnit = setUnit;
	this.unit = u;
	
	if (args.length > 0) this.setValue(val, u);
}

function Wind()
{
	var u=speedEnum.kmh;
	var args=Wind.arguments;
	var val=0;

	if (args.length>0) val=args[0];

	if (args.length > 1) u=args[1];

	this.values = new Array(0,1,2,3);
	this.conversion = new Array(convertMs, convertMph, convertKnots, convertKmh);

	this.unit = u;
	this.getValue = getValue;
	this.setValue = setValue;
	this.setUnit = setUnit;

	this.hasValue = hasValue;
	this.hasValue(false);

	if (args.length > 0) this.setValue(val, u);
}

function Pressure()
{
	var u = forceEnum.mb;
	var args = Pressure.arguments;

	var val = 0;

	if (args.length>0) val = args[0];

	if (args.length>1) u = args[1];

	this.values = new Array(0,1,2);
	this.conversion = new Array(convertMb, convertInches, convertKpa);

	this.unit = u;
	this.getValue = getValue;
	this.setValue = setValue;
	this.setUnit = setUnit;

	this.hasValue = hasValue;
	this.hasValue(false);

	if (args.length>0) this.setValue(val, u);
}

function genericMetVariable()
{
	var val = 0;

	if (arguments.length>0) val = arguments[0];

	this.values = new Array(1);
	this.values[0] = 0;

	this.unit = 0;

	this.getValue = getValue;
	this.setValue = setValue;

	this.hasValue = hasValue;
	this.hasValue(false);

	if (arguments.length>0) this.setValue(val);
}

function calcWithRh(ml)
{
	var mlv=ml.values;

	var rh=mlv.rh.getValue();

	var havePress=mlv.pressure.hasValue();
	
// Calculate the dewpoint and wetbulb given the ambient temperature (c), pressure (mb), and relative humidity

	var dryb = mlv.drybulb.getValue(degreeEnum.celsius);
	var es=calcVaporPressure(dryb);
	var e=es*rh/100;

// Note that Javascript uses base e logarithms so a conversion to base 10 logarithms is required
	var dp=calcTcFromVaporPressure(e);
	var wb;

	if (rh == 100) mlv.wetbulb.setValue(dp, degreeEnum.celsius);

	mlv.dewpoint.setValue(dp, degreeEnum.celsius);

	mlv.saturvp.setValue(es, forceEnum.mb);
	mlv.actualvp.setValue(e, forceEnum.mb);
	mlv.abshum.setValue(calcAbsHumidity(e, dryb));

	var press = defaultPressure;

	if (havePress)
		press=mlv.pressure.getValue(forceEnum.mb);

	if (rh != 100)
	{
		wb=calcWetbulb(press, dryb, dp);
		mlv.wetbulb.setValue(wb, degreeEnum.celsius);
	}

	mlv.mixratio.setValue(calcMixingRatio(e, press));
	mlv.spechum.setValue(calcSpecHumidity(e, press));
}

function calcWithWb(ml)
{
// Calculate the relative humidity and dewpoint given the ambient temperature (c), pressure (mb), and wetbulb

	var mlv=ml.values;

	var dryb = mlv.drybulb.getValue(degreeEnum.celsius);
	var wb = mlv.wetbulb.getValue(degreeEnum.celsius);

	var es = calcVaporPressure(dryb);
	var esw = calcVaporPressure(wb);

	mlv.saturvp.setValue(es, forceEnum.mb);

	var havePress=mlv.pressure.hasValue();

	var press = defaultPressure;
	if (havePress)
		press = mlv.pressure.getValue(forceEnum.mb);

	var e=esw-(press * (dryb - wb) * (0.000660*(1+0.00155*wb)));

	if (e < 0)
		return;

	mlv.actualvp.setValue(e, forceEnum.mb);
	mlv.abshum.setValue(calcAbsHumidity(e, dryb));

	var logs=Math.log(e/6.112)/Math.LN10;
	var dp = calcTcFromVaporPressure(e);

	mlv.dewpoint.setValue(dp, degreeEnum.celsius);
	mlv.rh.setValue(calcRelHumidity(e,es));

	mlv.mixratio.setValue(calcMixingRatio(e, press));
	mlv.spechum.setValue(calcSpecHumidity(e, press));
}

function managerObject(vElem, uElem, val, purp, uTypeSet, decimalPrec)
{
	this.valueElem = vElem;
	this.unitElem = uElem;
	this.valueObj = val;
	this.io = purp;
	this.unitTypes = uTypeSet;
	this.precision = decimalPrec;

	this.onValueChange = onValueChange;
	this.onUnitChange = onUnitChange;
	this.outputValue = outputValue;

	this.conversionEnabled = true;

	this.setValue = managerSetValue;
	this.setUnit = managerSetUnit;
	this.setType = managerSetType;

	vElem.manager = this;

	if (uElem != null)
		uElem.manager = this;
}

function outputValue()
{
	if (this.io == ioEnum.input)
		this.valueElem.value = (this.valueObj.hasValue()) ? this.valueObj.getValue() : "";
	else
	{
		var unit = this.valueObj.unit;
		var prec = this.precision[unit];
		this.valueElem.value = (this.valueObj.hasValue()) ? roundNum(this.valueObj.getValue(), prec) : "";
	}

	this.conversionEnabled = true;

}

function onUnitChange()
{
	var unit = this.unitElem.selectedIndex;

	this.valueObj.setUnit(unit);

	if (!this.valueObj.hasValue())
		return;

	if (this.conversionEnabled)
		this.outputValue();
	else
		this.onValueChange();
}

function onValueChange()
{
	var obj=this.valueElem;

	this.enableConversion = !(this.valueObj.setValue(obj.value));
}

function managerSetValue(val)
{
	this.valueElem.value = val;
	this.valueObj.setValue(val);
}

function managerSetUnit(unit)
{
	if (this.unitTypes != null) this.unitElem.selectedIndex = unit;
	this.onUnitChange();
}

function managerSetType(type)
{
	if (this.unitTypes == null) return;

	var unit = this.unitTypes[type];
	this.setUnit(unit);
}

function managerListObject()
{
	this.managers = new Array();
	this.values = new Object();
	this.resetManagers = resetManagers;
	this.addManager = addManager;
	this.onTypeChange = managerList_onTypeChange;
	this.output = managerList_output;
	this.defaultUnitType = unitTypeEnum.english;
}

function addManager(obj, str2)
{
	this.managers[this.managers.length] = obj;

	if (arguments.length == 1) return;

	var thisObj=this;
// Workaround a Netscape bug in 4.74. Use 'objval' and not 'obj.valueObj'.
	var objval=obj.valueObj;
	eval("thisObj."+arguments[1]+"=obj");
	eval("thisObj.values."+arguments[1]+"=objval");
}

function resetManagers()
{
	var doType = false;
	var type = this.defaultUnitType;

	if ((arguments.length > 0) && (arguments[0] >= 0))
	{
		doType = true;
		type = arguments[0];
		this.defaultUnitType = type;
	}	

	var io = (arguments.length > 1) ? arguments[1] : ioEnum.both;

	var empty="";

	for (var i=0; i < this.managers.length; i++)
	{
		if ((io != ioEnum.both) && (io != this.managers[i].io))
			continue;
		this.managers[i].setValue(empty);
		if (doType) this.managers[i].setType(type);
		
	}
}

function managerList_onTypeChange(type)
{
	this.defaultUnitType = type;

	for (var i=0; i < this.managers.length; i++)
		this.managers[i].setType(type);
}

function managerList_output()
{
	for (var i=0; i < this.managers.length; i++)
	{
		if (this.managers[i].io == ioEnum.output)	this.managers[i].outputValue();
		this.managers[i].conversionEnabled = true;
	}
}

