/*
 *  RPGeneration - A role playing utility generate interesting things
 *  Copyright (C) 2002 Devon D Jones
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  The author of this program grants you the ability to use this code
 *  in conjunction with code that is covered under the Open Gaming License
 *
 * Suncalc.java
 *
 * Created on October 22, 2002, 2:40 PM
 */

package org.evilsoft.gaming.RPGeneration;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
/**
 *
 * @author  devon
 */
public class SunCalc {
	static final double ZOFFICIAL = 90.8333;
	static final double ZCIVIL = 96.0;
	static final double ZNAUTICAL = 102.0;
	static final double ZASTRONOMICAL = 108.0;
	Calendar calendar;
	double latitude;
	double longitude;

    public SunCalc(Calendar calendar, double latitude, double longitude) {
		this.calendar = calendar;
		this.latitude = latitude;
		this.longitude = longitude;
    }

	public Date getSunSetOfficial() {
		return calculateSunSet(ZOFFICIAL);
	}

	public Date getSunSetCivil() {
		return calculateSunSet(ZCIVIL);
	}

	public Date getSunSetNautical() {
		return calculateSunSet(ZNAUTICAL);
	}

	public Date getSunSetAstronomical() {
		return calculateSunSet(ZASTRONOMICAL);
	}

	public Date getSunRiseOfficial() {
		return calculateSunRise(ZOFFICIAL);
	}

	public Date getSunRiseCivil() {
		return calculateSunRise(ZCIVIL);
	}

	public Date getSunRiseNautical() {
		return calculateSunRise(ZNAUTICAL);
	}

	public Date getSunRiseAstronomical() {
		return calculateSunRise(ZASTRONOMICAL);
	}

	public boolean isLeapYear(int year) {
		return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
	}

	public boolean isPosInteger(int inputVal) {
		if(inputVal >= 0) {
			return true;
		}
		else {
			return false;
		}
	}

	public int calcDayOfYear(int month, int day, boolean leapyear) 	{
		int k = (leapyear ? 1 : 2);
		Double doy = new Double(Math.floor((275 * month) / 9) - k * Math.floor((month + 9) / 12));
		return doy.intValue();
	}

	public int calcJD(int year, int month, int day)
	{
		if (month <= 2) {
			year -= 1;
			month += 12;
		}
		double A = Math.floor(year/100);
		double B = 2 - A + Math.floor(A/4);

		Double JD = new Double(Math.floor(365.25*(year + 4716)) + Math.floor(30.6001*(month+1)) + day + B - 1524.5);
		return JD.intValue();
	}

	public Date calculateSunRise(double zenith) {
System.out.println("Rise:");
		/*
		1. first calculate the day of the year

		N1 = floor(275 * month / 9)
		N2 = floor((month + 9) / 12)
		N3 = (1 + floor((year - 4 * floor(year / 4) + 2) / 3))
		N = N1 - (N2 * N3) + day - 30
		*/
		int day = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println("Day: " + day);

		/*
		2. convert the longitude to hour value and calculate an approximate time

		lngHour = longitude / 15

		if rising time is desired:
		t = N + ((6 - lngHour) / 24)
		if setting time is desired:
		t = N + ((18 - lngHour) / 24)
		*/
		double lngHour = longitude / 15;
		double roughHour = day + ((6 - lngHour) / 24);
System.out.println("Longitude Hour:" + lngHour);
System.out.println("Rough Hour:" + roughHour);

		/*
		3. calculate the Sun's mean anomaly

		M = (0.9856 * t) - 3.289
		*/

		double meanAnomaly = (0.9856 * roughHour) - 3.289;
System.out.println("Mean Anomaly:" + meanAnomaly);

		/*
		4. calculate the Sun's true longitude

		L = M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634
		NOTE: L potentially needs to be adjusted into the range [0,360) by adding/subtracting 360
		*/
		double sunLongitude = meanAnomaly + (1.916 * Math.sin(meanAnomaly)) + (0.020 * Math.sin(2* meanAnomaly)) + 282.643;
		if(sunLongitude < 0) {
			sunLongitude = sunLongitude + 360;
		}
		else if(sunLongitude > 360) {
			sunLongitude = sunLongitude - 360;
		}
System.out.println("Sun True Longitude:" + sunLongitude);

		/*
		5a. calculate the Sun's right ascension

		RA = atan(0.91764 * tan(L))
		NOTE: RA potentially needs to be adjusted into the range [0,360) by adding/subtracting 360
		*/
		double sunRA = Math.atan(0.91764 * Math.tan(sunLongitude));
		if(sunRA < 0) {
			sunRA = sunRA + 360;
		}
		else if(sunLongitude > 360) {
			sunRA = sunRA - 360;
		}
System.out.println("Sun Right Ascension:" + sunRA);

		/*
		5b. right ascension value needs to be in the same quadrant as L

		Lquadrant  = (floor( L/90)) * 90
		RAquadrant = (floor(RA/90)) * 90
		RA = RA + (Lquadrant - RAquadrant)
		*/
		double lQuadrant = (Math.floor(sunLongitude / 90)) * 90;
		double raQuadrant = (Math.floor(sunLongitude / 90)) * 90;
		sunRA = sunRA + (lQuadrant - raQuadrant);
System.out.println("Sun Right Ascension (Adjusted for quadrant):" + sunRA);
		/*
		5c. right ascension value needs to be converted into hours

		RA = RA / 15
		*/
		sunRA = sunRA / 15;
System.out.println("Sun Right Ascension (Hours):" + sunRA);
		/*
		6. calculate the Sun's declination

		sinDec = 0.39782 * sin(L)
		cosDec = cos(asin(sinDec))
		*/
		double sinDec = 0.39782 * Math.sin(sunLongitude);
		double cosDec = Math.cos(Math.asin(sinDec));
System.out.println("Sun Declination (sin):" + sinDec);
System.out.println("Sun Declination (cos):" + cosDec);
		/*
		7a. calculate the Sun's local hour angle

		cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))

		if (cosH >  1)
			the sun never rises on this location (on the specified date)
		if (cosH < -1)
			the sun never sets on this location (on the specified date)
		*/
		double cosH = (Math.cos(zenith) - (sinDec * Math.sin(latitude))) / (cosDec * Math.cos(latitude));
System.out.println("Sun Local Hour Angle:" + cosH);

		/*
		7b. finish calculating H and convert into hours

		if if rising time is desired:
		H = 360 - acos(cosH)
		if setting time is desired:
		H = acos(cosH)

		H = H / 15
		*/
		double hour = 360 - Math.acos(cosH);
		hour = hour / 15;
System.out.println("Hours:" + hour);

		/*
		8. calculate local mean time of rising/setting

		T = H + RA - (0.06571 * t) - 6.622
		*/

		double time = hour + sunRA - (0.06571 * roughHour) - 6.622;
System.out.println("Time: " + time);

		/*
		9. adjust back to UTC

		UT = T - lngHour
		NOTE: UT potentially needs to be adjusted into the range [0,24) by adding/subtracting 24
		*/
		double ut = time - lngHour;
//System.out.println(ut);
		/*
		10. convert UT value to local time zone of latitude/longitude

		localT = UT + localOffset
		*/
		return new Date();
	}

	public Date calculateSunSet(double zenith) {
System.out.println("\nSet:");
		/*
		1. first calculate the day of the year

		N1 = floor(275 * month / 9)
		N2 = floor((month + 9) / 12)
		N3 = (1 + floor((year - 4 * floor(year / 4) + 2) / 3))
		N = N1 - (N2 * N3) + day - 30
		*/
            double N1 = Math.floor(275 * calendar.get(Calendar.MONTH) / 9);
            double N2 = Math.floor((calendar.get(Calendar.MONTH) + 9) / 12);
            double N3 = (1 + Math.floor((calendar.get(Calendar.YEAR) - 4 * Math.floor(calendar.get(Calendar.YEAR) / 4) + 2) / 3));
            double N = N1 - (N2 * N3) + calendar.get(Calendar.DAY_OF_MONTH) - 30;
System.out.println("Day: " + N);
		int day = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println("Day: " + day);

		/*
		2. convert the longitude to hour value and calculate an approximate time

		lngHour = longitude / 15

		if rising time is desired:
		t = N + ((6 - lngHour) / 24)
		if setting time is desired:
		t = N + ((18 - lngHour) / 24)
		*/
		double lngHour = longitude / 15;
		double roughHour = day + ((18 - lngHour) / 24);
System.out.println("Longitude Hour:" + lngHour);
System.out.println("Rough Hour:" + roughHour);

		/*
		3. calculate the Sun's mean anomaly

		M = (0.9856 * t) - 3.289
		*/

		double meanAnomaly = (0.9856 * roughHour) - 3.289;
System.out.println("Mean Anomaly:" + meanAnomaly);

		/*
		4. calculate the Sun's true longitude

		L = M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634
		NOTE: L potentially needs to be adjusted into the range [0,360) by adding/subtracting 360
		*/
		double sunLongitude = meanAnomaly + (1.916 * Math.sin(meanAnomaly)) + (0.020 * Math.sin(2* meanAnomaly)) + 282.643;
		if(sunLongitude < 0) {
			sunLongitude = sunLongitude + 360;
		}
		else if(sunLongitude > 360) {
			sunLongitude = sunLongitude - 360;
		}
System.out.println("Sun True Longitude:" + sunLongitude);

		/*
		5a. calculate the Sun's right ascension

		RA = atan(0.91764 * tan(L))
		NOTE: RA potentially needs to be adjusted into the range [0,360) by adding/subtracting 360
		*/
		double sunRA = Math.atan(0.91764 * Math.tan(sunLongitude));
		if(sunRA < 0) {
			sunRA = sunRA + 360;
		}
		else if(sunLongitude > 360) {
			sunRA = sunRA - 360;
		}
System.out.println("Sun Right Ascension:" + sunRA);

		/*
		5b. right ascension value needs to be in the same quadrant as L

		Lquadrant  = (floor( L/90)) * 90
		RAquadrant = (floor(RA/90)) * 90
		RA = RA + (Lquadrant - RAquadrant)
		*/
		double lQuadrant = (Math.floor(sunLongitude / 90)) * 90;
		double raQuadrant = (Math.floor(sunLongitude / 90)) * 90;
		sunRA = sunRA + (lQuadrant - raQuadrant);
System.out.println("Sun Right Ascension (Adjusted for quadrant):" + sunRA);
		/*
		5c. right ascension value needs to be converted into hours

		RA = RA / 15
		*/
		sunRA = sunRA / 15;
System.out.println("Sun Right Ascension (Hours):" + sunRA);
		/*
		6. calculate the Sun's declination

		sinDec = 0.39782 * sin(L)
		cosDec = cos(asin(sinDec))
		*/
		double sinDec = 0.39782 * Math.sin(sunLongitude);
		double cosDec = Math.cos(Math.asin(sinDec));
System.out.println("Sun Declination (sin):" + sinDec);
System.out.println("Sun Declination (cos):" + cosDec);
		/*
		7a. calculate the Sun's local hour angle

		cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))

		if (cosH >  1)
			the sun never rises on this location (on the specified date)
		if (cosH < -1)
			the sun never sets on this location (on the specified date)
		*/
		double cosH = (Math.cos(zenith) - (sinDec * Math.sin(latitude))) / (cosDec * Math.cos(latitude));
System.out.println("Sun Local Hour Angle:" + cosH);

		/*
		7b. finish calculating H and convert into hours

		if if rising time is desired:
		H = 360 - acos(cosH)
		if setting time is desired:
		H = acos(cosH)

		H = H / 15
		*/
		double hour = Math.acos(cosH);
		hour = hour / 15;
System.out.println("Hours:" + hour);

		/*
		8. calculate local mean time of rising/setting

		T = H + RA - (0.06571 * t) - 6.622
		*/

		double time = hour + sunRA - (0.06571 * roughHour) - 6.622;
System.out.println("Time: " + time);

		/*
		9. adjust back to UTC

		UT = T - lngHour
		NOTE: UT potentially needs to be adjusted into the range [0,24) by adding/subtracting 24
		*/
		double ut = time - lngHour;
//System.out.println(ut);
		/*
		10. convert UT value to local time zone of latitude/longitude

		localT = UT + localOffset
		*/
		return new Date();
	}
}


/*
Sunrise/Sunset Algorithm

Source:
	Almanac for Computers, 1990
	published by Nautical Almanac Office
	United States Naval Observatory
	Washington, DC 20392

Inputs:
	day, month, year:      date of sunrise/sunset
	latitude, longitude:   location for sunrise/sunset
	zenith:                Sun's zenith for sunrise/sunset
	  offical      = 90 degrees 50'
	  civil        = 96 degrees
	  nautical     = 102 degrees
	  astronomical = 108 degrees

	NOTE: longitude is positive for East and negative for West


1. first calculate the day of the year

	N1 = floor(275 * month / 9)
	N2 = floor((month + 9) / 12)
	N3 = (1 + floor((year - 4 * floor(year / 4) + 2) / 3))
	N = N1 - (N2 * N3) + day - 30

2. convert the longitude to hour value and calculate an approximate time

	lngHour = longitude / 15

	if rising time is desired:
	  t = N + ((6 - lngHour) / 24)
	if setting time is desired:
	  t = N + ((18 - lngHour) / 24)

3. calculate the Sun's mean anomaly

	M = (0.9856 * t) - 3.289

4. calculate the Sun's true longitude

	L = M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634
	NOTE: L potentially needs to be adjusted into the range [0,360) by adding/subtracting 360

5a. calculate the Sun's right ascension

	RA = atan(0.91764 * tan(L))
	NOTE: RA potentially needs to be adjusted into the range [0,360) by adding/subtracting 360

5b. right ascension value needs to be in the same quadrant as L

	Lquadrant  = (floor( L/90)) * 90
	RAquadrant = (floor(RA/90)) * 90
	RA = RA + (Lquadrant - RAquadrant)

5c. right ascension value needs to be converted into hours

	RA = RA / 15

6. calculate the Sun's declination

	sinDec = 0.39782 * sin(L)
	cosDec = cos(asin(sinDec))

7a. calculate the Sun's local hour angle

	cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))

	if (cosH >  1)
	  the sun never rises on this location (on the specified date)
	if (cosH < -1)
	  the sun never sets on this location (on the specified date)

7b. finish calculating H and convert into hours

	if if rising time is desired:
	  H = 360 - acos(cosH)
	if setting time is desired:
	  H = acos(cosH)

	H = H / 15

8. calculate local mean time of rising/setting

	T = H + RA - (0.06571 * t) - 6.622

9. adjust back to UTC

	UT = T - lngHour
	NOTE: UT potentially needs to be adjusted into the range [0,24) by adding/subtracting 24

10. convert UT value to local time zone of latitude/longitude

	localT = UT + localOffset
*/


