/* The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Mozilla Communicator client code, released March
 * 31, 1998.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation. Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *
 */
/*
 * JavaScript shared functions file for running the tests in either
 * stand-alone JavaScript engine.  To run a test, first load this file,
 * then load the test script.
 */

var completed = false;
var testcases;
var tc = 0;

SECTION = "";
VERSION = "";
BUGNUMBER = "";

/*
 * constant strings
 */
var GLOBAL = "[object global]";
var PASSED = " PASSED!"
var FAILED = " FAILED! expected: ";
var DEBUG = false;


/*
* Wrapper for test case constructor that doesn't require the SECTION argument.
 */
function AddTestCase( description, expect, actual ) 
{
  testcases[tc++] = new TestCase( SECTION, description, expect, actual );
}


/*
 * TestCase constructor
*/
function TestCase( n, d, e, a ) 
{
  this.name = n;
  this.description = d;
  this.expect = e;
  this.actual = a;
  this.passed = true;
  this.reason = "";
  this.bugnumber = BUGNUMBER;
  this.passed = getTestCaseResult( this.expect, this.actual );
  if ( DEBUG ) {writeLineToLog("added " + this.description);}
}


/*
 * Set up test environment.
*/
function startTest() 
{
  if ( version ) 
  {
    // JavaScript 1.3 is supposed to be compliant ECMA version 1.0
    if (VERSION == "ECMA_1" ) {version ("130");}
    if (VERSION == "JS_1.3" ) {version ( "130");}
    if (VERSION == "JS_1.2" ) {version ( "120");}
    if (VERSION == "JS_1.1" ) {version( "110");}

   // for ECMA version 2.0, we will leave the JavaScript version 
   // to the default ( for now ).
 }

  // print out bugnumber
  if ( BUGNUMBER ) 
  {
    writeLineToLog ("BUGNUMBER: " + BUGNUMBER );
  }

  testcases = new Array();
  tc = 0;
}


function test() 
{
  for ( tc=0; tc < testcases.length; tc++ ) 
  {
    testcases[tc].passed = writeTestCaseResult(
                            testcases[tc].expect,
                            testcases[tc].actual,
                            testcases[tc].description +" = "+ testcases[tc].actual );

    testcases[tc].reason += ( testcases[tc].passed ) ? "" : "wrong value ";
    }

  stopTest();
  return ( testcases );
}


/*
 * Compare expected result to the actual result and figure out whether
 * the test case passed.
 */
function getTestCaseResult(expect, actual ) 
{
  //because ( NaN == NaN ) always returns false, need to do
  //a special compare to see if we got the right result.
  if ( actual != actual ) 
  {
    if ( typeof actual == "object" ) {actual = "NaN object";} 
    else {actual = "NaN number";}
  }

  if ( expect != expect ) 
  {
    if ( typeof expect == "object" ) {expect = "NaN object";} 
    else {expect = "NaN number";}
  }

  var passed = ( expect == actual ) ?  true : false;

  // if both objects are numbers, need to replace w/ IEEE standard for rounding
  if ( !passed  &&  typeof(actual) == "number"  &&  typeof(expect) == "number" ) 
  {
    if ( Math.abs(actual-expect) < 0.0000001 ) {passed = true;}
  }

  //verify type is the same
  if ( typeof(expect) != typeof(actual) ) {passed = false;}

  return passed;
}


/*
 * Begin printing functions.  These functions use the shell's print function. 
*  When running tests in the browser, override these functions with functions 
*  that use document.write.
 */
function writeTestCaseResult( expect, actual, string ) 
{
  var passed = getTestCaseResult(expect, actual );
  writeFormattedResult( expect, actual, string, passed );
  return passed;
}


function writeFormattedResult( expect, actual, string, passed ) 
{
  var s = string ;
  s += ( passed ) ? PASSED : FAILED + expect;
  writeLineToLog( s);
  return passed;
}


function writeLineToLog( string ) 
{
  print( string );
}


function writeHeaderToLog( string ) 
{
  print( string );
}
/* End of printing functions */


/*
 * When running in the shell, run the garbage collector after the test has completed.
 */
function stopTest() 
{
  var gc;
  if ( gc != undefined ) 
  {
    gc();
  }
}


/*
 * Convenience function for displaying failed test cases.  
 * Useful when running tests manually.
*/
function getFailedCases() 
{
  for (var i = 0; i < testcases.length; i++ ) 
  {
    if ( !testcases[i].passed ) 
    {
      print( testcases[i].description  +  " = " +  testcases[i].actual  +  " expected: "  +  testcases[i].expect );
    }
  }
}


 /*
  * Date constants and functions used by tests in Date suite
*/
var msPerDay = 86400000;
var HoursPerDay = 24;
var MinutesPerHour = 60;
var SecondsPerMinute = 60;
var msPerSecond = 1000;
var msPerMinute = 60000;   // msPerSecond * SecondsPerMinute
var msPerHour = 3600000;   // msPerMinute * MinutesPerHour
var TZ_DIFF = getTimeZoneDiff();
var TZ_ADJUST = TZ_DIFF * msPerHour;
var TIME_1970 = 0;
var TIME_2000 = 946684800000;
var TIME_1900 = -2208988800000;
var UTC_29_FEB_2000 = TIME_2000 + 31*msPerDay + 28*msPerDay;
var UTC_1_JAN_2005 = TIME_2000 + TimeInYear(2000) + TimeInYear(2001) +
       TimeInYear(2002) + TimeInYear(2003) + TimeInYear(2004);
var now = new Date();
var TIME_NOW = now.valueOf();  //valueOf() is to accurate to the millisecond
                                                              //Date.parse() is accurate only to the second



/*
 * Originally, the test suite used a hard-coded value TZ_DIFF = -8. 
 * But that was only valid for testers in the Pacific Standard Time Zone! 
 * We calculate the proper number dynamically for any tester. We just
 * have to be careful to use a date not subject to Daylight Savings Time...
*/
function getTimeZoneDiff()
{
  return -((new Date(2000, 1, 1)).getTimezoneOffset())/60;
}


function Day( t) 
{
  return ( Math.floor( t/msPerDay ) );
}


function DaysInYear( y ) 
{
  if ( y % 4 != 0 ) {return 365;}

  if ( (y%4 == 0) && (y%100 != 0) ) {return 366;}

  if ( (y%100 == 0) && (y%400 != 0) ) {return 365;}

  if ( (y%400 == 0)){return 366;} 
  else {return "ERROR: DaysInYear("  +  y  +  ") case not covered";}
}


function TimeInYear( y ) 
{
  return ( DaysInYear(y) * msPerDay );
}


function DayNumber( t ) 
{
  return ( Math.floor( t / msPerDay ) );
}


function TimeWithinDay( t ) 
{
  if ( t < 0 ) {return ( (t%msPerDay) + msPerDay );} 
  else {return ( t % msPerDay );}
}


function YearNumber( t ) 
{
}


function TimeFromYear( y ) 
{
  return ( msPerDay * DayFromYear(y) );
}


function DayFromYear( y ) 
{
  return ( 365*(y-1970)  +  Math.floor((y-1969)/4)  -  Math.floor((y-1901)/100) 
                + Math.floor((y-1601)/400) );
}


function InLeapYear( t ) 
{
  if ( DaysInYear(YearFromTime(t)) == 365 ) {return 0;}

  if ( DaysInYear(YearFromTime(t)) == 366 ) {return 1;} 
  else {return "ERROR: InLeapYear("  +  t  +  ") case not covered";}
}


function YearFromTime( t ) 
{
  t =Number( t );
  var sign = ( t < 0 ) ? -1 : 1;
  var year = ( sign < 0 ) ? 1969 : 1970;

  for (var timeToTimeZero = t; ;  ) 
  {
    // subtract the current year's time from the time that's left.
    timeToTimeZero -= sign * TimeInYear(year)

    // if there's less than the current year's worth of time left, then break.
    if ( sign < 0 ) 
    {
      if ( sign * timeToTimeZero <= 0 ) {break;} 
      else {year += sign;}
    } 
    else 
    {
      if ( sign * timeToTimeZero < 0 ) {break;} 
      else {year += sign;}
    }
  }

  return ( year );
}


function MonthFromTime( t ) 
{
  var day = DayWithinYear( t );
  var leap = InLeapYear(t);

  // I know I could use switch but I'd rather not until it's part of ECMA
  if ( (0 <= day) && (day < 31) ) {return 0;}
  if ( (31 <= day) && (day < (59+leap) )) {return 1;}
  if ( ((59+leap) <= day) && (day < (90+leap) )) {return 2;}
  if ( ((90+leap) <= day) && (day < (120+leap) )) {return 3;}
  if ( ((120+leap) <= day) && (day < (151+leap) )) {return 4;}
  if ( ((151+leap) <= day) && (day < (181+leap) )) {return 5;}
  if ( ((181+leap) <= day) && (day < (212+leap) )) {return 6;}
  if ( ((212+leap) <= day) && (day < (243+leap)) ) {return 7;}
  if ( ((243+leap) <= day) && (day < (273+leap) )) {return 8;}
  if ( ((273+leap) <= day) && (day < (304+leap)) ) {return 9;}
  if ( ((304+leap) <= day) && (day < (334+leap)) ) {return 10;}
  if ( ((334+leap) <= day) && (day < (365+leap)) ) {return 11;} 
  else {return "ERROR: MonthFromTime("  +  t  +  ") not known";}
}


function DayWithinYear( t ) 
{
  return(Day(t) - DayFromYear(YearFromTime(t)) );
}


function DateFromTime( t ) 
{
  var day = DayWithinYear(t);
  var month = MonthFromTime(t);

  if ( month == 0) {return ( day + 1 );}
  if ( month == 1) {return ( day - 30 );}
  if ( month == 2) {return ( day - 58 - InLeapYear(t) );}
  if ( month == 3) {return ( day - 89 - InLeapYear(t));}
  if ( month == 4) {return ( day - 119 - InLeapYear(t));}
  if ( month == 5) {return ( day - 150 - InLeapYear(t));}
  if ( month == 6) {return ( day - 180 - InLeapYear(t));}
  if ( month == 7) {return ( day - 211 - InLeapYear(t));}
  if ( month == 8) {return ( day - 242 - InLeapYear(t));}
  if ( month == 9) {return ( day - 272 - InLeapYear(t));}
  if ( month == 10) {return ( day - 303 - InLeapYear(t));}
  if ( month == 11) {return ( day - 333 - InLeapYear(t));}
  return ("ERROR: DateFromTime("+t+") not known" );
}


function WeekDay( t ) 
{
  var weekday = (Day(t)+4)%7;
  return( weekday < 0 ?  7+weekday : weekday );
}


// missing daylight savings time adjustment


function HourFromTime( t ) 
{
  var h = Math.floor( t / msPerHour )%HoursPerDay;
  return ( (h<0) ? HoursPerDay + h : h  );
}


function MinFromTime( t ) 
{
  var min = Math.floor( t / msPerMinute )%MinutesPerHour;
  return( (min < 0 ) ?  MinutesPerHour + min : min );
}


function SecFromTime( t ) 
{
  var sec = Math.floor( t / msPerSecond )%SecondsPerMinute;
  return ( (sec < 0 ) ?  SecondsPerMinute + sec : sec );
}


function msFromTime( t ) 
{
  var ms = t%msPerSecond;
  return ( (ms < 0 ) ? msPerSecond + ms : ms );
}


function LocalTZA() 
{
  return ( TZ_DIFF * msPerHour );
}


function UTC( t ) 
{
  return ( t - LocalTZA() - DaylightSavingTA(t  - LocalTZA()) );
}


function DaylightSavingTA( t ) 
{
  t = t - LocalTZA();

  var dst_start = GetFirstSundayInApril(t) +  2*msPerHour;
  var dst_end = GetLastSundayInOctober(t) +  2*msPerHour;

  if ( t >= dst_start  &&  t < dst_end ) {return msPerHour;} 
  else {return 0;}

  // Daylight Savings Time starts on the first Sunday in April at 2:00AM in PST.  
  // Other time zones will need to override this function.

print( new Date( UTC(dst_start + LocalTZA())) );
return UTC(dst_start + LocalTZA());
}


function GetFirstSundayInApril( t ) 
{
  var year = YearFromTime(t);
  var leap = InLeapYear(t);

  var april = TimeFromYear(year) + TimeInMonth(0, leap) + TimeInMonth(1,leap) + TimeInMonth(2,leap);

  for ( var first_sunday = april;  WeekDay(first_sunday) > 0;  first_sunday += msPerDay )
  { 
    ;
  }

  return first_sunday;
}


function GetLastSundayInOctober( t ) 
{
  var year = YearFromTime(t);
  var leap = InLeapYear(t);

  for ( var oct = TimeFromYear(year), m =0;   m < 9;  m++ ) 
  {
    oct += TimeInMonth(m, leap);
  }

  for ( var last_sunday = oct +  30*msPerDay;  WeekDay(last_sunday) > 0;  last_sunday -= msPerDay )
  {
    ;
  }

  return last_sunday;
}


function LocalTime( t ) 
{
  return ( t + LocalTZA() + DaylightSavingTA(t) );
}


function MakeTime( hour, min, sec, ms ) 
{
  if ( isNaN(hour) || isNaN(min) || isNaN(sec) || isNaN(ms) ){return Number.NaN;}

  hour = ToInteger(hour);
  min  = ToInteger( min);
  sec  = ToInteger( sec);
  ms = ToInteger( ms );

  return( (hour*msPerHour) + (min*msPerMinute) + (sec*msPerSecond) + ms );
}


function MakeDay( year, month, date ) 
{
  if ( isNaN(year) || isNaN(month) || isNaN(date)) {return Number.NaN;}

  year = ToInteger(year);
  month = ToInteger(month);
  date = ToInteger(date );

  var sign = ( year < 1970 ) ?  -1 : 1;
  var t = ( year < 1970 ) ? 1 :  0;
  var y = ( year < 1970 ) ? 1969 : 1970;

  var result5 = year + Math.floor( month/12 );
  var result6= month%12;

  if ( year < 1970 ) 
  {
    for ( y = 1969; y >= year;  y += sign ) 
    {
      t += sign * TimeInYear(y);
    }
  } 
  else 
  {
    for ( y = 1970 ; y < year; y += sign ) 
    {
      t += sign * TimeInYear(y);
    }
  }

  var leap = InLeapYear( t );

  for ( var m = 0; m < month; m++) 
  {
    t += TimeInMonth( m, leap );
  }

  if ( YearFromTime(t) != result5 ) {return Number.NaN;} 
  if ( MonthFromTime(t) != result6 ) {return Number.NaN;}
  if ( DateFromTime(t) != 1 ){return Number.NaN;}

  return ( (Day(t)) + date - 1 );
}


function TimeInMonth( month, leap ) 
{  
  // Jan 0  Feb 1  Mar 2  Apr 3   May 4  June 5  Jul 6 Aug 7  Sep 8 Oct 9  Nov 10  Dec11

  // April  June  September November
  if ( month == 3 || month == 5 || month == 8 || month == 10 ) {return ( 30*msPerDay );}

  // all the rest
  if ( month == 0 || month == 2 || month == 4  || month == 6 ||
       month == 7  || month == 9 || month == 11 ) {return ( 31*msPerDay );}

  // save February
  return ( (leap == 0) ?  28*msPerDay : 29*msPerDay );
}


function MakeDate( day, time ) 
{
  if (day == Number.POSITIVE_INFINITY ||
       day == Number.NEGATIVE_INFINITY ||
       day == Number.NaN ) 
  {
    return Number.NaN;
  }

  if ( time == Number.POSITIVE_INFINITY ||
        time == Number.POSITIVE_INFINITY ||
        day == Number.NaN) 
  {
    return Number.NaN;
  }

  return ( day * msPerDay ) + time;
}


function TimeClip( t ) 
{
  if ( isNaN( t )) {return ( Number.NaN);}
  if ( Math.abs( t ) > 8.64e15 ) {return ( Number.NaN);}

  return ( ToInteger( t ) );
}


function ToInteger( t ) 
{
  t = Number( t );

  if ( isNaN( t )) {return ( Number.NaN);}

  if ( t == 0 || t == -0 || 
        t == Number.POSITIVE_INFINITY || 
        t == Number.NEGATIVE_INFINITY) 
  {
    return 0;
  }

  var sign = ( t < 0 ) ?  -1 : 1;

  return ( sign * Math.floor( Math.abs( t ) ) );
}


function Enumerate( o ) 
{
  var p;
  for ( p in o ) {print( p +  ": "  +  o[p] );}
}


/* these functions are useful for running tests manually in Rhino */

function GetContext() 
{
  return Packages.com.netscape.javascript.Context.getCurrentContext();
}


function OptLevel( i ) 
{
  i = Number(i);
  var cx = GetContext();
  cx.setOptimizationLevel(i);
}

/* end of Rhino functions */

