/**
 * Unit utilities.
 *
 * The Units constructor serves as a place to hold utility functions
 * shared between the various unit functions.
 */
function Units() {
   // Nothing -- this just is a place to hold methods.
}

Units.prototype.getNumUnits = function() {
   return this.units.length;
}

Units.prototype.getName = function(unit) {
   var unitConfig = this.units[unit];
   if (unitConfig) {
      name = unitConfig.name;
   } else {
      name = "unknown unit";
   }
   return name;
}

Units.prototype.getAbbrev = function(unit) {
   var unitConfig = this.units[unit];
   if (unitConfig) {
      abbrev = unitConfig.abbrev;
   } else {
      abbrev = "unknown unit";
   }
   return abbrev;
}

Units.prototype.getConversionRatio = function(unit) {
   var unitConfig = this.units[unit];
   if (unitConfig) {
      ratio = unitConfig.convratio;
   } else {
      ratio = "unknown unit";
   }
   return ratio;
}

Units.prototype.convert = function(value, fromUnit, toUnit) {

   // Find the unit configuration object for the "from" unit
   fromUnitConfig = this.units[fromUnit];
   if (!fromUnitConfig) {
      return 0;
   }

   // Find the unit configuration object for the "to" unit
   toUnitConfig = this.units[toUnit];
   if (!toUnitConfig) {
      return 0;
   }

   // Perform the actual conversion
   result = value * fromUnitConfig.convratio / toUnitConfig.convratio;

   return result;
}

Units.prototype.writeHtmlOptions = function() {
   for (var i = 0; i < this.getNumUnits(); i++) {
       document.write("<option value=\"" + i + "\">" + this.getName(i) + "</option>");
   }
}


/**
 * Represent a unit of area.
 *
 * Each AreaUnit object represents one unit for the measurement of area.
 * Note that this represents the *unit* of measurement, not any particular
 * value.
 */

function AreaUnit(name, abbrev, convratio) {
   this.name = name;
   this.abbrev = abbrev;
   this.convratio = convratio;
}



/**
 * Represent the set of area measurements supported by this program.
 *
 * For configuration, each unit of area is represented by an
 * AreaUnit object.
 */
function AreaUnitConfig() {

   // Declare each of the possible units
   this.units = [
      new AreaUnit("square meter", "m<sup>2</sup>", 1),
      new AreaUnit("hectare", "ha", 10000),
      new AreaUnit("acre", "acre", 43560 * (12*12*2.54*2.54) / 10000)
   ];

   // Declare constants to represent each unit.  It is important that
   // the numeric value of each of these constants match the subscript
   // for the corresponding unit in the UNITS array.
   this.M2     = 0;
   this.HA     = 1;
   this.ACRE   = 2;
}

AreaUnitConfig.prototype = new Units();


/**
 * The Area object represents a particular value of an area.
 * Conceptually, it represents both a numeric value and a unit.
 */
function Area(val, unit) {
//alert("New area: " + val + ", " + unit);
   this.val = val;
   this.unit = 0;
   if (arguments.length >= 2) {
      this.unit = unit;
   }
   this.config = new AreaUnitConfig();
}


Area.prototype.getArea = function(unit) {
   var desiredunit = 0;
   if (arguments.length > 0) {
      desiredunit = unit;
   }

//alert("getting area: " + this.val + ", " + this.unit + ", " + desiredunit);
//alert(this.config.convert(this.val, this.unit, desiredunit));
   return this.config.convert(this.val, this.unit, desiredunit);
}


Area.prototype.getValue = function() {
   return this.val;
}


Area.prototype.getUnit = function() {
   return this.unit;
}



/**
 * Represent a unit of carbon.
 *
 * For configuration, each unit of value is represented by an
 * CarbonUnit object.
 */

function CarbonUnit(name, abbrev, convratio) {
   this.name = name;
   this.abbrev = abbrev;
   this.convratio = convratio;
}

function CarbonUnitConfig() {

   // Declare each of the possible units

   // Conversion between mass of C and CO2 is based on the atomic weights.
   var CperCO2 = 12.0107 / (12.0107 + 2*15.9994);
   this.units = [
      new CarbonUnit("grams of carbon",            "g C",  1),
      new CarbonUnit("kilograms of carbon",        "Kg C", 1e3),
      new CarbonUnit("metric tons of carbon",      "MT C", 1e6),
      new CarbonUnit("gigagrams of carbon",        "Gg C", 1e9),
      new CarbonUnit("teragrams of carbon",        "Tg C", 1e12),
      new CarbonUnit("petagrams of carbon",        "Pg C", 1e15),
      new CarbonUnit("pounds of carbon",           "lbs. C", 1000.0/2.205),
      new CarbonUnit("tons of carbon",             "tons C", 1000.0/2.205*2000),

      new CarbonUnit("grams of carbon dioxide",          "g CO<sub>2</sub>",  CperCO2 * 1),
      new CarbonUnit("kilograms of carbon dioxide",      "Kg CO<sub>2</sub>", CperCO2 * 1e3),
      new CarbonUnit("metric tons of carbon dioxide",    "MT CO<sub>2</sub>", CperCO2 * 1e6),
      new CarbonUnit("gigagrams of carbon dioxide",      "Gg CO<sub>2</sub>", CperCO2 * 1e9),
      new CarbonUnit("teragrams of carbon dioxide",      "Tg CO<sub>2</sub>", CperCO2 * 1e12),
      new CarbonUnit("petagrams of carbon dioxide",      "Pg CO<sub>2</sub>", CperCO2 * 1e15),
      new CarbonUnit("pounds of carbon dioxide",         "lbs. CO<sub>2</sub>", CperCO2 * 1000.0/2.205),
      new CarbonUnit("tons of carbon dioxide",           "tons CO<sub>2</sub>", CperCO2 * 1000.0/2.205*2000)
   ];

   // Declare constants to represent each unit.  It is important that
   // the numeric value of each of these constants match the subscript
   // for the corresponding unit in the UNITS array.
   this.G_C       = 0;
   this.KG_C      = 1;
   this.MG_C      = 2;
   this.GG_C      = 3;
   this.TG_C      = 4;
   this.PG_C      = 5;
   this.LBS_C     = 6;
   this.TONS_C    = 7;
   this.G_CO2     = 8;
   this.KG_CO2    = 9;
   this.MG_CO2    =10;
   this.GG_CO2    =11;
   this.TG_CO2    =12;
   this.PG_CO2    =13;
   this.LBS_CO2   =14;
   this.TONS_CO2  =15;
}

// Inherit the get/set functions
CarbonUnitConfig.prototype = new Units();


/**
 * The Carbon object represents a particular amount of carbon.
 * Conceptually, it represents both a numeric value and a unit.
 */
function Carbon(val, unit) {
   this.val = val;
   this.unit = 0;
   if (arguments.length >= 2) {
      this.unit = unit;
   }
//alert("new carbon created: " + this.val + ", " + this.unit);
   this.config = new CarbonUnitConfig();
}


Carbon.prototype.getCarbon = function(unit) {
   var desiredunit = 0;
   if (arguments.length > 0) {
      desiredunit = unit;
   }

   return this.config.convert(this.val, this.unit, desiredunit);
}


Carbon.prototype.getValue = function() {
   return this.val;
}


Carbon.prototype.getUnit = function() {
   return this.unit;
}


/**
 * Represent a unit of monetary value.
 *
 * For configuration, each unit of value is represented by an
 * MonetaryUnit object.
 */

function MonetaryUnit(name, abbrev, convratio) {
   this.name = name;
   this.abbrev = abbrev;
   this.convratio = convratio;
}

function MonetaryUnitConfig() {

   // Declare each of the possible units
   this.units = [
      new MonetaryUnit("dollars", "$", 1),
      new MonetaryUnit("euro", "euros", 1.1924),
      new MonetaryUnit("yen", "yen", 0.008836)
   ];

   // Declare constants to represent each unit.  It is important that
   // the numeric value of each of these constants match the subscript
   // for the corresponding unit in the UNITS array.
   this.USD     = 0;
   this.EURO    = 1;
   this.YEN     = 2;
}

MonetaryUnitConfig.prototype = new Units();


/**
 * The Money object represents a particular amount of money.
 * Conceptually, it represents both a numeric value and a unit.
 */
function Money(val, unit) {
   this.val = val;
   this.unit = 0;
   if (arguments.length >= 2) {
      this.unit = unit;
   }
   this.config = new MonetaryUnitConfig();
}


Money.prototype.getMoney = function(unit) {
   var desiredunit = 0;
   if (arguments.length > 0) {
      desiredunit = unit;
   }

   return this.config.convert(this.val, this.unit, desiredunit);
}


Money.prototype.getValue = function() {
   return this.val;
}


Money.prototype.getUnit = function() {
   return this.unit;
}


au = new AreaUnitConfig();
cu = new CarbonUnitConfig();
mu = new MonetaryUnitConfig();


function CarbonCalculator() {
   this.area   = null;              // Always in square meters
   this.carbon = null;              // Always in kilograms of carbon
   this.value  = null;              // Always in U. S. dollars

   // There are three conversion ratios:
   // carbon/area, value/carbon, and value/area.  Only two can be
   // independently supplied -- the other is dependent on the other two.
   this.CarbonPerArea_Carbon = new Carbon(0.50, cu.KG_C);
   this.CarbonPerArea_Area   = new Area(1, au.HA);

   this.ValuePerCarbon_Value = new Money(8, mu.USD);
   this.ValuePerCarbon_Carbon = new Carbon(1, cu.MG_C);

   // Using the above values, value/area is calculated.
   this.calcThirdRatio();
}

CarbonCalculator.prototype = new Object();

CarbonCalculator.prototype.calcThirdRatio = function() {
   var ratio   = (this.ValuePerCarbon_Value.getMoney() / this.ValuePerCarbon_Carbon.getCarbon())
                 *
                 (this.CarbonPerArea_Carbon.getCarbon() / this.CarbonPerArea_Area.getArea());

   this.ValuePerArea_Value = new Money(ratio);
   this.ValuePerArea_Area  = new Area(1);
}

/**
 * Set a value for the land area.
 *
 */
CarbonCalculator.prototype.setArea = function(area) {
   this.area   = area;
   this.carbon = null;
   this.value  = null;
}


CarbonCalculator.prototype.getArea = function(units) {

   var result = null;

   if (this.area) {
      result = this.area;
   }

   if (this.carbon) {
      val    = this.carbon.getCarbon() / this.CarbonPerArea_Carbon.getCarbon() * this.CarbonPerArea_Area.getArea();
      result = new Area(val)
   }

   if (this.value) {
      val    = this.value.getMoney() / this.ValuePerArea_Value.getMoney() * this.ValuePerArea_Area.getArea();
      result = new Area(val);
   }

   // Unless perhaps all three are null, result will now be non-null
   if (result == null) {
      return null;
   }

   // Convert to the requested units
   return result.getArea(units);

}

CarbonCalculator.prototype.isAreaSet = function() {

   return this.area != null;

}


/**
 * Set a value for the Carbon
 *
 */
CarbonCalculator.prototype.setCarbon = function(carbon) {
   this.area   = null;
   this.carbon = carbon;
   this.value  = null;
}


CarbonCalculator.prototype.getCarbon = function(units) {

//alert("Calculating carbon");
   var result = null;

   if (this.area) {
      val    = this.area.getArea() * this.CarbonPerArea_Carbon.getCarbon() / this.CarbonPerArea_Area.getArea();
//alert("Carbon val = " + val);
      result = new Carbon(val);
   }

   if (this.carbon) {
      result = this.carbon;
   }

   if (this.value) {
      val    = this.value.getMoney() * this.ValuePerCarbon_Carbon.getCarbon() / this.ValuePerCarbon_Value.getMoney();
      result = new Carbon(val);
   }

   // Unless perhaps all three are null, result will now be non-null
   if (result == null) {
      return null;
   }

   // Convert to the requested units
   return result.getCarbon(units);

}

CarbonCalculator.prototype.isCarbonSet = function() {

   return this.carbon != null;

}


/**
 * Set a value for the Value
 *
 */
CarbonCalculator.prototype.setMoney = function(money) {
   this.area   = null;
   this.carbon = null;
   this.value  = money;
}


CarbonCalculator.prototype.getMoney = function(units) {

   var result = null;

   if (this.area) {
//alert("here" + this.area.getArea());
      val    = this.area.getArea() * this.ValuePerArea_Value.getMoney() / this.ValuePerArea_Area.getArea();
      result = new Money(val);
   }

   if (this.carbon) {
      val    = this.carbon.getCarbon() * this.ValuePerCarbon_Value.getMoney() / this.ValuePerCarbon_Carbon.getCarbon();
      result = new Money(val);
   }

   if (this.value) {
      result = this.value;
   }

   // Unless perhaps all three are null, result will now be non-null
   if (result == null) {
      return null;
   }

   // Convert to the requested units
   return result.getMoney(units);

}


CarbonCalculator.prototype.isMoneySet = function() {

   return this.value != null;

}


function DisplayFormat(v) {


   if (v.toPrecision) {
      v = v.toPrecision(3);
////alert(v)
      v = Number(v);
      v = v.toFixed(10);
////alert(v);
      v = v.replace(/0*$/, "");
////alert(v);
   }

   return v;
}


cc = new CarbonCalculator();

/*====================================================================*/
/* Functions for event handlers on the HTML page.                     */
/*====================================================================*/

function onChangeArea(field) {
   var f = document.forms["calc"];
   if (field.name=="landarea"  ||  cc.isAreaSet()) {
      cc.setArea(new Area(f["landarea"].value, f["areaunits"].value));
   }
   calc();
}

function onChangeCarbon(field) {
   var f = document.forms["calc"];
   if (field.name=="carbon"  ||  cc.isCarbonSet()) {
      cc.setCarbon(new Carbon(f["carbon"].value, f["carbonunits"].value));
   }
   calc();
}

function onChangeMoney(field) {
   var f = document.forms["calc"];
   if (field.name=="money"  ||  cc.isMoneySet()) {
      cc.setMoney(new Money(f["money"].value, f["moneyunits"].value));
   }
   calc();
}

function calcobsolete() {
   f = document.forms["calc"];

   // Assuming at least one of the three values has been set, then
   // label which one is specified and which two are calculated.
   if (cc.isAreaSet()  ||  cc.isCarbonSet()  ||  cc.isMoneySet()) {
      var nbsp = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
      var e = document.getElementById("calcarea");
      if (cc.isAreaSet()) {
         e.innerHTML = "starting with...";
         e.className = "calclabel startwith";
      } else {
         e.innerHTML = nbsp + "...calculate";
         e.className = "calclabel calculated";
      }

      var e = document.getElementById("calccarbon");
      if (cc.isCarbonSet()) {
         e.innerHTML = "starting with...";
         e.className = "calclabel startwith";
      } else {
         e.innerHTML = nbsp + "...calculate";
         e.className = "calclabel calculated";
      }

      var e = document.getElementById("calcmoney");
      if (cc.isMoneySet()) {
         e.innerHTML = "starting with...";
         e.className = "calclabel startwith";
      } else {
         e.innerHTML = nbsp + "...calculate";
         e.className = "calclabel calculated";
      }
   }

   if (!cc.isAreaSet()) {
      val = cc.getArea(f["areaunits"].value);
      f["landarea"].value = DisplayFormat(val);
   }
   if (!cc.isCarbonSet()) {
      val = cc.getCarbon(f["carbonunits"].value);
//alert("setting carbon field to " + val);
      f["carbon"].value = DisplayFormat(val);
   }
   if (!cc.isMoneySet()) {
      val = cc.getMoney(f["moneyunits"].value);
      f["money"].value = DisplayFormat(val);
   }

}
