// Some useful functions
////////////////////////////


// Convert radian angle to degrees

	function radToDeg(angleRad) 
	{
		return (180.0 * angleRad / Math.PI);
	}


// Convert degree angle to radians

	function degToRad(angleDeg) 
	{
		return (Math.PI * angleDeg / 180.0);
	}

// cos and sin with degree arguments
  function cos_d(angle)
  {
    return (Math.cos(degToRad(angle)));
  }
  function sin_d(angle)
  {
    return (Math.sin(degToRad(angle)));
  }
  function tan_d(angle)
  {
    return (Math.tan(degToRad(angle)));
  }
  
// isLeapYear returns 1 if the 4-digit yr is a leap year, 0 if it is not
	function isLeapYear(yr) 
	{
		return ((yr % 4 == 0 && yr % 100 != 0) || yr % 400 == 0);
	}
// calculate the day of the year (1st January = 1)
	function calcDayOfYear(year, month, day)     // month 1-12, day 1-31, year yyyy
	{
		var k = (isLeapYear(year) ? 1 : 2);
		var doy = Math.floor((275 * month)/9) - k * Math.floor((month + 9)/12) + day -30;
		return doy;
	}
  
  
  function calcJD(year, month, day, localtime, timezone)
  {
    // localtime = time of day in hours
    // timezone = time difference from UTC in hours
    //Conversion of Gregorian calendar date to Julian date for years AD 1801-2099
    //can be carried out with the following formula:
    // JD = 367K - <(7(K+<(M+9)/12>))/4> + <(275M)/9> + I + 1721013.5 + UT/24
    //	    - 0.5sign(100K+M-190002.5) + 0.5  
    //where K is the year (1801 <= K <= 2099), M is the month (1 <= M <= 12),
    // I is the day of the month (1 <= I <= 31),
    // and UT is the universal time in hours.
    // The last two terms in the formula add up to zero for all dates
    // after 1900 February 28, so these two terms can be omitted for subsequent dates.
    var jd;
    with (Math) {
      jd = 367*year - floor( (7*(year + floor((month+9)/12)))/4 )
	         + floor((275*month)/9) + day + 1721013.5;
      jd += (localtime - timezone)/24;
      if (100*year + month < 190002.5)	{
	      jd += 1;
      }
    }
    return jd;
  }

// reduce angle to range [0, 360) 
  function reduce360(angle)
  {
    while(angle >= 360.0)
		{
			angle -= 360.0;
		}
		while(angle < 0.0)
		{
			angle += 360.0;
		}
    return angle;
  }
// calculate angular difference
  function diffAngle(angle1, angle2)
  {
    var diff = angle1 - angle2;
    if (diff > 180)
      diff -= 360;
    else if (diff < -180)
      diff += 360;
    return diff;
  } 

///////////////////////////////////////////////
// solar calculator functions
////////////////////////////////
 
  function SolarCalculator()
  {
    this.rightAscension = 0;
    this.declination =  0;
    this.eqTime = 0;
    this.timeDiff = 0;
		this.latitude = -30.515;
		this.longitude = 151.663;
		this.timeZone = 10;
    this.day = 1;
    this.month = 1;
    this.year = 2001;
    this.localTime = 0;    // local standard time in hours
    this.solarTime = 0;    // solar mean time in hours
    this.hourAngle = 0;
    this.azimuth = 0;
    this.zenith = 0;
    this.elevation = 0;
    this.noon = 0;         // solar noon in local standard time
    this.sunRise = 0;      // sunrise local standard time
    this.sunSet = 0;       // sunset local standard time
    this.ComputeAzimuth = _computeAzimuth;
    this.ComputeZenith = _computeZenith;
    this.Initialise = _init;
    this.Update = _updateTime;
    this.UpdateDay = _updateDay;
    this.UpdateTime = _updateTime;
    this.SetLocation = _setLocation;
    this.SetDay = _setDay;
    
    // initialise
    this.UpdateDay();
    this.UpdateTime(12);      // 12 noon local time
  }
 
  function _computeZenith()
  {
    // requires latitude, longitude, declination, hourAngle
    var csz;
    with (Math) {
      csz = sin_d(this.latitude) * sin_d(this.declination) + 
				    cos_d(this.latitude) * cos_d(this.declination) * cos_d(this.hourAngle);
			if (csz > 1.0) {
				csz = 1.0;
			} else if (csz < -1.0) { 
				csz = -1.0; 
			}
			this.zenith = radToDeg(acos(csz));
    }
  } 
 
  function _computeAzimuth()
  {
  // requires latitude, and declination, and hourAngle
    var azDenom, azRad;
    with (Math) {
      azDenom = cos_d(this.latitude) * sin_d(this.zenith);
			if (abs(azDenom) > 0.001) {
				azRad = (  sin_d(this.latitude) * cos_d(this.zenith)  - 
						 sin_d(this.declination)  ) / azDenom;
				if (abs(azRad) > 1.0) {
					if (azRad < 0) {
						azRad = -1.0;
					} else {
						azRad = 1.0;
					}
				}
				this.azimuth = 180.0 - radToDeg(acos(azRad));
				if (this.hourAngle > 0.0) {
					this.azimuth = -this.azimuth;
				}
		 } else {
				if (this.latitude > 0.0) {
					this.azimuth = 180.0;
				} else { 
					this.azimuth = 0.0;
				}
		 }
		 if (this.azimuth < 0.0) {
				this.azimuth += 360.0;
		 }
   }
  }
  
  
function _init(latitude, longitude, timeZone, year, month, day)
{
  this.latitude = latitude;
  this.longitude = longitude;
  this.timeZone = timeZone;
  this.year = year;
  this.month = month;
  this.day = day;
  this.UpdateDay();
  this.UpdateTime(12);  // use 12 noon as default local time
}
function _setLocation(latitude, longitude, timeZone)
{
  this.latitude = latitude;
  this.longitude = longitude;
  this.timeZone = timeZone;
  this.UpdateDay();
  this.UpdateTime(12);  // use 12 noon as default local time
}
function _setDay(year, month, day)
{
  this.year = year;
  this.month = month;
  this.day = day;
  this.UpdateDay();
  this.UpdateTime(12);  // use 12 noon as default local time
}
  
function _updateDay()
{
  var jday, d, g, q, L, R, e, RA, SD;
  // first calculate sun's orbital position
  jday = calcJD(this.year, this.month, this.day, 12.0, this.timeZone);  // julian day at 12noon local time
  with (Math) {
    d = jday - 2451545.0;      // days since J2000.0
    g = 357.529 + 0.98560028*d;
    g = reduce360(g);
    q = 280.459 + 0.98564736*d;
    q = reduce360(q);
    L = q + 1.915*sin_d(g) + 0.020*sin_d(2*g);	 // Sun's apparent ecliptic longitude
    L = reduce360(L);
    R = 1.00014 - 0.01671*cos_d(g) - 0.00014*cos_d(2*g); // distance to Sun
    e = 23.439 - 0.00000036*d;	 // mean obliquity of the ecliptic
    RA = radToDeg(atan2(cos_d(e)*sin_d(L), cos_d(L)));	// Sun's right ascension
    if (RA < 0)
      RA += 360.0;
    this.rightAscension = RA;
    this.declination = radToDeg(asin(sin_d(e)*sin_d(L)));		    // Sun's declination
    this.eqTime = diffAngle(q, RA) / 15;	      // equation of time (hours)
    //SD = 0.2666 / R;			     // Sun's semi-diameter (degrees)

    // noon, sunrise, and sunset times
    this.timeDiff = this.eqTime + (this.longitude/15 - this.timeZone);   // solar time - local time (hours)
	  this.noon = 12.0 - this.timeDiff;   // local time
    var arg = cos_d(90.833)/(cos_d(this.latitude)*cos_d(this.declination)) 
	      - tan_d(this.latitude) * tan_d(this.declination);
    if (arg < -1)
      arg = -1.0;
    else if (arg > 1)
      arg = 1.0;
	  var ts = radToDeg(acos(arg)) /15;   // time of sunrise/set before/after noon
    this.sunRise = this.noon - ts;   // in local time
    this.sunSet = this.noon + ts;
  }
}
  
function _updateTime(localTime)
{
// update the sun's postion at the given time (localTime in hours)
    this.localTime = localTime;
      // solar time and hour angle
	  this.solarTime = localTime + this.eqTime + (this.longitude/15 - this.timeZone);
		this.hourAngle = (this.solarTime -12) * 15;  // degrees from 12 noon
      // zenith, elevation
    this.ComputeZenith();
    this.elevation = 90.0 - this.zenith;
    // azimuth - requires zenith calculation first
    this.ComputeAzimuth();
}

function formatTime(hours)
	//  returns a zero-padded string (HH:MM) given time in hours
	{
    if (hours < 0)
      hours += 24.0;
    if (hours > 24)
      hours -= 24.0;
    var mins = Math.round(hours*60);    // time to nearest minute
		var hh = Math.floor(mins/60);
		var mm = mins % 60;            // 60 * (hours - Math.floor(hours));
		//var mm = Math.round(mins);

		var timeStr = "";
		if (hh < 10)	//	i.e. only one digit
			timeStr += "0" + hh + ":";
		else
			timeStr += hh + ":";

		if (mm < 10)	//	i.e. only one digit
			timeStr += "0" + mm;
		else
			timeStr += mm;

		return timeStr;
	}
  
  function formatTimeDiff(dt)
  {
    // format dt in hours as minutes
    var str = "";
		var t = Math.round(dt * 600) / 10;
		str += t;
    return str;
  }
  
// variables

  var theSun = new SolarCalculator();

  
  

    
