/* Peak-oil clock

   Look for spans of class="zF19".  Text within these spans may be one of:

      PEAK_RATE_LESS_CURRENT_RATE
      OIL_PRODUCED
      PEAK_RATE
      CURRENT_RATE
      PEAK_MONTH

   Clock data are substituted for each span's contents.

   For backwards-compatibility, span with id="zF19" and string "##" treated same
   as span with class="zF19" and string "PEAK_RATE_LESS_CURRENT_RATE".

*/

if ( window.onload ) {
   var previousOnload = window.onload;

   window.onload = function() {
      previousOnload();

      // Don't start more than one even if script sourced more than once on a
      // page.

      if ( ! window.instZFPeakOil ) {
         instZFPeakOil = new zFPeakOil();
      }
   }
} else {
   window.onload = function() {
      if ( ! window.instZFPeakOil ) {
         instZFPeakOil = new zFPeakOil();
      }
   }
}

function zFPeakOil() {

   // Name of this gizmo (for usage data logging via function usage_data_log())
   // and URL of log script.

   this.gizName = "G19oil";
   this.usage_data_script = "http://zfacts.com/giz/udat/rec.php";

   // Clock data definitions.

   this.dataItems = [ "PEAK_RATE_LESS_CURRENT_RATE", 
                      "OIL_PRODUCED",
                      "PEAK_RATE",
                      "CURRENT_RATE",
                      "PEAK_MONTH"
                    ];
   this.n_dataItems = this.dataItems.length;

   // Required link alternatives (any is OK).

   this.requiredLink = [ "http://zfacts.com/p/196.html",
                         "http://zfacts.com" ];

   // Required words in link text (all must be present).

   this.requiredLinkWord = [];

   // Default units -- gallons, gallons/day.

   this.unitCvt = 42;

   // Default decimal places.

   this.dec = 2;

   // Default (if not set by user).

   if ( window.zF19cps == undefined ) {
      zF19cps = 5;
   }

   // Data.  From http://www.eia.doe.gov/emeu/ipsr/t14.xls

   // Production rate (weighted average past year).  BBL/day.

   this.mostRecentRate = 85512000;
   this.mostRecentDate = "2008-03-15";

   // Most-recent change month-to-month (of weighted average rate).  
   // BBL/day/month.

   this.currentTrend   =   154290;

   // Peak rate.  BBL/day.

   this.peakRate       = 85827000;
   this.peakRateDate   = "2008-02-15";

   // Other definitions (month strings).

   this.otherDefinitions();

   // Validate - check that specified link to zfacts provided.  Check that
   // link words include what's required.

   this.validateLink();

   // Initialize - find spans, create arrays of location and type.

   this.initSpans();

   // Initialize data - time, rate parameters.

   this.initData();

   // Record usage.

//   this.usage_data_log();

   // Begin updating.

   this.updateCounters();
}


zFPeakOil.prototype.validateLink = function() {

   // Find all <a ...> tags.

   var link = document.getElementsByTagName( "a" );
   var n_links = link.length;
   var n_alt_links = this.requiredLink.length;
   var link_ok_f = false;
   for ( var i=0; i<n_links; i++ ) {
      for ( var j=0; j<n_alt_links; j++ ) {
         var linkElement = link[i];
         if ( linkElement.href.toLowerCase() == this.requiredLink[j] ) {
            link_ok_f = true;
            break;
         }
      }
      if ( link_ok_f ) {
         break;
      }
   }
   if ( link_ok_f ) {

      // Check link words.  Need all.

      var n_linkWords = this.requiredLinkWord.length;
      var neededWords = [];
      for ( var j=0; j<n_linkWords; j++ ) {
         var linkText = linkElement.innerHTML.toLowerCase();
         if ( linkText.indexOf( this.requiredLinkWord[j] ) == -1 ) {
            neededWords.push( this.requiredLinkWord[j] );
         }
      }
      if ( neededWords.length > 0 ) {
         neededWords = neededWords.join( ", " );
         alert( "Link text must include: " + neededWords );
      }

   } else {

      // Required link not present.

      alert( "Need link like '" + this.requiredLink[0] + "'" );
   }
}


zFPeakOil.prototype.initSpans = function() {

   // Find spans.

   this.span = this.getSpans( "zF19" );

   // See which each is.

   this.n_spans = this.span.length;
   this.spanItem    = [];
   this.spanUnitCvt = [];
   this.spanDec     = [];
   for ( var i=0; i<this.n_spans; i++ ) {
      this.spanItem[i]    = -1;

      // Compare span's inner HTML with list of allowed items.

      for ( var i_item=0; i_item<this.n_dataItems; i_item++ ) {
         var text = this.span[i].innerHTML;
         if ( text.indexOf( this.dataItems[i_item] ) != -1 ) {
            this.spanItem[i] = i_item;
            break;
         }
      }

      // See if span has attributes -- units, dec.

      var units = this.span[i].getAttribute( "units" );
      if ( units ) {

         // Original calculations in BBL or BBL/day.  Default is conversion to
         // gallons or gallons/day.  Options: "bbl" or "bbl/day", "bbl/sec",
         // or "gallons/sec".
         units = units.toLowerCase();
         var unitCvt;
         switch( units ) {
            case "bbl" :
            case "bbl/day" :
               unitCvt = 1;
               break;

            case "bbl/sec" :
               unitCvt = 1 /( 3600*24 );
               break;

            case "gallons/sec" :
               unitCvt = 42 /( 3600*24 );
               break;

            default :
               unitCvt = this.unitCvt;
               break;
         }
         this.spanUnitCvt[i] = unitCvt;
      } else {

         // Default (set above).

         this.spanUnitCvt[i] = this.unitCvt;
      }
      var dec = this.span[i].getAttribute( "dec" );
      if ( dec ) {
         if ( ! isNaN( dec ) ) {
            this.spanDec[i] = parseInt( dec );
         }
      } else {

         // Default (set above).

         this.spanDec[i] = this.dec;
      }
   }

   // Also, if element with id="zF19" (backwards compatibility).
   
   var oldZF19 = document.getElementById( "zF19" );
   if ( oldZF19 ) {
      if ( oldZF19.innerHTML == "##" ) {
         this.span.push( oldZF19 );
         this.spanItem.push( 0 );

         // Defaults.

         this.spanUnitCvt.push( this.unitCvt );
         this.spanDec.push( this.dec );

         this.n_spans++;
      }
   }
}


zFPeakOil.prototype.initData = function() {

   this.clockStart = new Date() * 0.001;
   this.currentTrendPerSecond = this.currentTrend / (30*24*3600);

   /*
      Want oil produced since clock started (this gizmo) -- production rate
      varies linearly with time, so will do integral.

      r(t) = r0 + (t - t0)*r'
           = r0 - t0*r' + t*r'

      where

      r(t) = production rate, in BBL/sec
      r0   = r(t0)
      t0   = mostRecentDate in seconds
      r'(t) = change in production rate, BBL/sec^2

      Integral, from ts (clock start) to t, is:

      p   = (t - ts)*(r0 - t0*r') + 0.5*r'*( t^2 - ts^2 )
          = t^2*0.5*r' + t*(r0 - t0*r') - ts*(r0 - t0*r') - 0.5*r'*ts^2

      Let a = 0.5 * r'                        (coef. of t^2)
          b = r0 - t0*r'                      (coef. of t)
          c = ts*t0*r' - ts*r0 - 0.5*r'*ts^2  (const)

   */

   // Milliseconds to seconds.

   var yearMonthDay = this.mostRecentDate.split( "-" );
   var year  = parseInt( yearMonthDay[0] );
   var month = parseInt( yearMonthDay[1] ) - 1;
   var day   = parseInt( yearMonthDay[2] );
   var t0 = new Date( year, month, day );
   var t0 = t0 * 0.001;
   this.t0 = t0;

   // Convert from BBL/day to BBL/sec.

   var r0 = this.mostRecentRate / (24*3600);

   // Convert from BBL/day/month to BBL/sec^2

   var rPrime = this.currentTrend / (30*24*3600*24*3600);
   this.integralA = 0.5 * rPrime;

   var ts = this.clockStart;
   this.integralB = r0 - t0*rPrime;

   this.integralC = ts*t0*rPrime - ts*r0 - 0.5*rPrime*ts*ts;

   // Timeout interval - milliseconds.

   this.timeoutMillisec = 1000 / zF19cps;
}


zFPeakOil.prototype.updateCounters = function() {

   // Calculations.  Time in seconds.

   var tnow = new Date();
   var t = tnow.getTime() * 0.001;
   var currentRate = this.mostRecentRate 
                     + ( t - this.t0 )*this.currentTrendPerSecond;

   // Loop over counters.

   for ( var i=0; i<this.n_spans; i++ ) {
      switch( this.spanItem[i] ) {
         case 0:

            // Peak rate less current rate.  If negative, add note.

            var peak_rate_less_current_rate = this.peakRate - currentRate;
            if ( peak_rate_less_current_rate < 0 ) {
               var value = peak_rate_less_current_rate * this.spanUnitCvt[i];
               var formattedValue 
                          = this.formatNumber( value, this.spanDec[i], ",", ".",
                                               "", "", "-", "" );
               this.span[i].innerHTML = formattedValue 
                                        + ' <span style="font-size: 80%">'
                                        + '(neg: new peak!)</span>';
            } else {

               // Convert, format, and put in span.

               this.updateCounter( this.span[i], peak_rate_less_current_rate, 
                                   this.spanDec[i],
                                   this.spanUnitCvt[i] );
            }
            break;

         case 1:

            // Oil produced.

            var oil_produced = t*t*this.integralA + t*this.integralB 
                               + this.integralC;
            this.updateCounter( this.span[i], oil_produced, this.spanDec[i],
                                this.spanUnitCvt[i] );
            break;

         case 2:

            // Peak rate.

            this.updateCounter( this.span[i], this.peakRate, this.spanDec[i],
                                this.spanUnitCvt[i] );
            break;

         case 3:

            // Current rate.


            this.updateCounter( this.span[i], currentRate, this.spanDec[i],
                                this.spanUnitCvt[i] );
            break;

         case 4:

            // Peak month.

            var monthYear = this.toMonthYear( this.peakRateDate );
            this.span[i].innerHTML = monthYear;
            break;

      }
   }

   // Repeat at interval.

   setTimeout( "instZFPeakOil.updateCounters()", this.timeoutMillisec );
}


zFPeakOil.prototype.updateCounter 
                             = function( span, value, decimalPlaces, unitCvt ) {

   // Unit conversion.

   value *= unitCvt;

   // Format - commas, decimals.

   var formattedValue = this.formatNumber( value, decimalPlaces, ",", ".", 
                                           "", "", "-", "");
   span.innerHTML = formattedValue;
}


zFPeakOil.prototype.usage_data_log = function() {

   // src script file that records usage.  Send name of this gizmo, etc.

   var script = document.createElement( "script" );
   script.src = this.usage_data_script + "?gizName="  + this.gizName
                                       + "&domain="   + document.domain
                                       + "&location=" + document.location;
   document.body.appendChild( script );
}


zFPeakOil.prototype.toMonthYear = function( yearMonthDay ) {
   yearMonthDay = yearMonthDay.split( "-" );
   var month = parseInt( yearMonthDay[1] ) - 1;

   return this.monthString[month] + " " + yearMonthDay[0];

}


zFPeakOil.prototype.getSpans = function( className ) {
   var spans = [];
   var allSpans = document.getElementsByTagName( "span" );
   var n_spans = allSpans.length;
   for ( var i=0; i<n_spans; i++ ) {
      if ( allSpans[i].className == className ) {
         spans.push( allSpans[i] );
      }
   }

   return spans;
}


// number formatting function
// copyright Stephen Chapman 24th March 2006, 10th February 2007
// permission to use this function is granted provided
// that this copyright notice is retained intact

/* num   - number to format
 * dec   - number of decimal places
 * thou  - thousands separator
 * pnt   - decimal pt - period or comma
 * curr1 - $, etc., before number
 * curr2 - USD, etc. after number
 * n1    - before number, if negative - e.g., '-' or '('
 * n2    - after number, if negative - e.g., '' or ')'
 */

zFPeakOil.prototype.formatNumber 
                           = function (num,dec,thou,pnt,curr1,curr2,n1,n2) {
   if ( dec == 0 ) {
      pnt = '';
   }
   var x = Math.round(num * Math.pow(10,dec));
   if (x >= 0) 
      n1=n2='';
   var y = (''+Math.abs(x)).split('');
   var z = y.length - dec; 
   if (z<0) z--; 
   for(var i = z; i < 0; i++) 
      y.unshift('0');
   y.splice(z, 0, pnt); 
   if(y[0] == pnt) 
      y.unshift('0'); 
   while (z > 3) {
      z-=3; 
      y.splice(z,0,thou);
   }
   var r = curr1+n1+y.join('')+n2+curr2;
   return r;
}


zFPeakOil.prototype.otherDefinitions = function() {
   this.monthString = [ "January",
                        "February",
                        "March",
                        "April",
                        "May",
                        "June",
                        "July",
                        "August",
                        "September",
                        "October",
                        "November",
                        "December"
                      ];
}
