#include <calculations.h>

/* TODO
 * Azimuth and Elevation are wrong in the calculation of the position of the Milky Way */


/* Convert degree to radiant */
double calc_deg_rad (double deg) {
  return M_PI/180*deg;
}
/* Convert radiant to degree */
double calc_rad_deg (double rad) {
  return 180/M_PI*rad;
}

int times_to_zone (int day_utc, int hour_utc, int day_local, int hour_local){
  int time_zone;
  if (day_utc == day_local){
    time_zone = hour_local - hour_utc;
  }
  else{
    if (abs(day_utc-day_local)>1){
      if (day_utc < day_local){
        day_local = day_utc-1;
      }
      else{
        day_local = day_utc+1;
      }
    }
    time_zone = 24*(day_local-day_utc)+(hour_local-hour_utc);
  }
  return time_zone;
}

void utc_zone_to_time (int *time_utc, int *time_local){
  int sign = *(time_utc+5)/abs(*(time_utc+5));

  *time_local=*time_utc;
  *(time_local+1)=*(time_utc+1);
  *(time_local+2)=*(time_utc+2);
  *(time_local+3)=*(time_utc+3);
  *(time_local+4)=*(time_utc+4);
  *(time_local+5)=*(time_utc+5);

  *(time_local+3) = *(time_utc+3)+*(time_utc+5);
  if (*(time_local+3)>24 || *(time_local+3) < 0){ /* Test if hours are still inside the same day */
    *(time_local+3) = (*(time_local+3)+24)%24;
    *(time_local+2) = *(time_utc+2)+sign;

    if (*(time_local+2) > max_day_in_month (*(time_utc+1), *(time_utc)) || *(time_local+2) < 1){ /* Test if days are still inside the same month */
      *(time_local+2) = (*(time_local+2)-1)%max_day_in_month (*(time_utc+1)+sign, *(time_utc))+1; /* -1 and +1 necessary due to mod calculations */
      *(time_local+1) = *(time_utc+1)+sign;

      if (*(time_local+1) > 12 || *(time_local+1) < 1){ /* Test if months are still inside the same year */
        *(time_local+1) = (*(time_local+1)-1)%12+1;
        *(time_local) = *(time_utc)+sign;
      }
    }
  }
}

void time_to_utc (int *time_local, int *time_utc){
  int sign = *(time_utc+5)/abs(*(time_utc+5));

  *time_utc=*time_local;
  *(time_utc+1)=*(time_local+1);
  *(time_utc+2)=*(time_local+2);
  *(time_utc+3)=*(time_local+3);
  *(time_utc+4)=*(time_local+4);
  *(time_local+5)=*(time_utc+5);

  *(time_utc+3) = *(time_local+3)-*(time_utc+5);
  if (*(time_utc+3) > 24 || *(time_utc+3) < 0){
    *(time_utc+3) = (*(time_utc+3)+24)%24;
    *(time_utc+2) = *(time_utc+2)+sign;

    if (*(time_utc+2) > max_day_in_month (*(time_local+1), *(time_local)) || *(time_utc+2) < 1){
      *(time_utc+2) = (*(time_utc+2)-1)%max_day_in_month (*(time_local+1)+sign, *(time_local))+1;
      *(time_utc+1) = *(time_local+1)+sign;

      if (*(time_utc+1) > 12 || *(time_utc+1) < 1){
        *(time_utc+1) = (*(time_local+1)-1)%12+1;
        *(time_utc) = *(time_local)+sign;
      }
    }
  }
}

int max_day_in_month (int month, int year){
  int max_days = 30;
  /* check which month is set */
  switch (month) {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
      max_days = 31;
      break;
    case 2:
      /* definition of a leap year: Every fourth year is a leap year expect if year%100 is 0 e.g. 1900
       * but every year where year%400 is 0 is a leap year again e.g. 2000 */
      if ((year%4==0 && year%100!=0) || year%400==0) {
        max_days = 29;
      }
      else {
        max_days = 28;
      }
      break;
  }
  return max_days;
}


/* Get current time and date and return as a pointer to an array */
void calc_time_utc (int *time_utc)
{
  time_t t;
  struct tm tmp_local;
  struct tm *tmp;

  t = time(&t);

  tmp = localtime(&t);
  memcpy(&tmp_local, tmp, sizeof(struct tm));
  tmp = gmtime(&t);

  int time_zone = times_to_zone (tmp->tm_mday, tmp->tm_hour, tmp_local.tm_mday, tmp_local.tm_hour);

  g_print ("UTC: %02d:%02d - %d.%d.%d, Time zone: %d\n", tmp->tm_hour, tmp->tm_min, tmp->tm_mday, tmp->tm_mon+1, tmp->tm_year+1900, time_zone);

  *(time_utc) = tmp->tm_year+1900;
  *(time_utc+1) = tmp->tm_mon+1;
  *(time_utc+2) = tmp->tm_mday;
  *(time_utc+3) = tmp->tm_hour;
  *(time_utc+4) = tmp->tm_min;
  *(time_utc+5) = time_zone;
}


/* Calculate the Julian Day */
void calc_jd (int *time_utc, double *time_jd)
{
  float year = *time_utc;
  float month = *(time_utc+1);
  double day = *(time_utc+2);
  float hour = *(time_utc+3);
  float min = *(time_utc+4);

  /* g_print ("Minute: %f, Hour: %f, Day: %f, Month: %f, Year: %f\n",min,hour,day,month,year); */
  /* calculate a float type day number which is direct proportional to the time went by */
  day += (hour + min/60.)/24.;
  /*g_print("Day JD: %f\n", day);*/

  if (month<=2){
    year --;
    month += 12;
  }

  *time_jd = (int)(365.25*(year+4716.)) + (int)(30.6001*(month+1.)) - (int)(year/100.) + (int)(year/400.) + day + 2. - 1524.5;
  /* g_print ("Julian Day: %f, Day JD: %f, hour JD %f\n", *time_jd, day, hour); */

  /*Explanation:
   * There are 5 main parts in this calculation.
   * 1. The first question is if our month is in January or February.
   *    This is necessary because if we have a leap year we should first add the extra day in March not allready in January or February.
   *    We than simply see January and February as the 13th or 14th month of the last year.
   *
   * 2. The secont main part is the expression "(int)(365.25*(year+4716))" and partly combined with the " - 1524.5".
   *    This mainly results from the definition of the Julian Day which is defined to be zero at the 1. January of -4712 at 12 UT
   *    Due to step 1 this would lead to a negativ year. To avoid this we define the 0 point 4 years earlier and substrate these 1461 days.
   *    To get then the right amount of days on the 1st of March 4712 at 00:00 we have to add these "lost" days which ar 59.5.
   *    It is 59.5 and not 58.5 because the year -4712 was also a leap year.
   *    The (int) definition is necessary because we only want to add a day every 4 years. The int gets rid of the .25 .50 or .75.
   *
   * 3. (int)(30.6001*(month+1)): This part is not as obvios as the rest of the calculation. This formular is an "estimation" function of the days passed until the given month.
   *    Due to step 1 we don't need to look at January and February so let's start with March. How many days have passed in March since the "beginning" of the year.
   *    Where the beginning is March it selfe. It is 0. How many days have passed on 1st of April? 31. The following months are
   *    61, 92, 122, 153, 184, 214, 245, 275, 306 and 337 on 1. February. The first of March is then again 0.
   *    We can receive these values also whith the equation (int)(30.6*(month+1)-122). Due to "errors" in floating point arithmetic 30.6 can not be represented in binary
   *    and can lead for example in april to 152.999998 which is in (int) 152 and not 153. Therefore we do not multiply by 30.6 and use 30.6001 instead.
   *
   * 4. The adding of the days (+ day) should be obvious. BUT we have to calculate (+ day - 1) because if we have the 3rd of Oktober for example in principle it is
   *    the 2nd of Oktober + a few hous.
   *
   * 5. 2 - (int)(year/100) + (int)(year/400): In the gregorian calendar we have every 4 years a leap year (step 2) but every 100th year we do NOT have a leap year
   *    but every 400th year it is still a leap year. This is to compensate the rotation around the Sun more precise. This is calculated by "- (int)(year/100) + (int)(year/400)"
   *    The 2 is an offset because at JD 0 the Gregorian calendar did not exist at this time. It was defined that the 5. Oktober 1582 is the 15. Oktober 1582. If we calculate
   *    the JD of this date with the Julian calendar we see a offset of two days to the same JD calculated with the Gregorian calendar. This is where the 2 comes from.
   *    Just keep in mind that the Julian Day is not the same as the Julian Date!
   *
   * --> combining all of this leads to the above formula.
   */
}


/* Calculate the Sidereal time in degree (German: Sternzeit) */
void calc_sidereal_time(float longitude, double *time_jd, double *sidereal_time) {
  float hours_passed;
  double jd_sidereal;

  /* Julian date at 0 UT at the current date */
  if (*time_jd - (int)*time_jd >= 0.5){
    jd_sidereal = (int)(*time_jd) + 0.5;
  }
  else{
    jd_sidereal = (int)(*time_jd) - 0.5;
  }

  hours_passed = 24*(*time_jd-jd_sidereal);
  /*g_print("hours_passed %f\n", hours_passed);*/
  double T = (jd_sidereal - 2451545)/36525;

  /* This formular calculatest the amount of 100 years since the fundamental epoche J2000 (1.1.2000)
   * This formular is NOT allowed to get julian days which end on .5 (00:00), because the formular below only considers the rotation around the sun NOT its' own prcession.
   * So we only want to know in which "direktion" we look every day at 00:00 in Greenwich in comparison to the "fixed" stars. */

  *sidereal_time = 100.46061837 + T*(36000.770053608 + T*(0.000387933 - T/38710000.0)) + longitude + hours_passed*1.00273790935*15;
  *sidereal_time = remainder(*sidereal_time, 360);

  /* g_print("time_jd: %f, sidereal_time %f, hours_passed: %f\n", *time_jd, *sidereal_time, hours_passed); */


  /* This formula for the sidereal time is not that easy to explain and strongly based on numerical calculations which do not allow analytical arguments.
   * For further details I recommend reading the reports of the International Atronomical Union (IAU) and the publications from P. K. Seidelmann from 1981.
   *
   * Short: The first part: "100.46061837 + T*(36000.770053608 + T*(0.000387933 - T/38710000))" calculates the startime at 00:00 in Greenwich.
   *        The first offset at 100.46061837 degree is due to its definition to the 1.1.2000 which was the offset at this spesific date (fundamental epoch J2000).
   *
   *        The first linear part T*36000.770053608 is also quite easily to anderstand. At 00:00 every day the earth had to spin about 361 degree to be at the same
   *        position towards the sun due to the rotation around the sun. This means every day at 00:00 we look about 1 degree differently to a fixed star then the day
   *        bevore at the same time. In one year this is 360 degree and in 100 years this is 36000 degree.
   *        The quadratic and cubic parts are due to different effects like the precission and nutation of the ecliptic.
   *        --> more detailed explanations where these terms come from may follow. If someone knows the answer PLEASE tell me!
   *
   *        The adding of the longitude is to get the correct sidereal time at the location of interest. This is necessary because the sidereal time
   *        is not the same around the globe.
   *
   *        The adding of the time multiplied by "1.00273790935" is necessary because a classical day is more than a 360 degree rotation of the earth.
   *        This can be explained because we can see the same star at the skye one time more often than the sun due to the earth rotating around the sun.
   *        The precise time can not be took into account in the calculation of T as mentioned above.
   *        It has to be multiplied by 15 to receive an angle (360/24=15).*/
}

void calc_coordinates_sun(double time_jd, float *coordinates_sun){
  float right_ascension;
  float declination;
  double T;
  double mean_longitude;
  double mean_anomaly;
  double equation_of_center;
  double ecliptic_longitude;
  double ecliptic;

  T = (time_jd-2451545.0)/36525;
  mean_longitude = 280.46645 + (36000.76983 + 0.0003032*T)*T;
  mean_anomaly = 357.52910 + (35999.05030 - (0.0001559 - 0.00000048*T)*T)*T;
  equation_of_center = (1.914600 - (0.004817 - 0.000014*T)*T)*sin(calc_deg_rad (mean_anomaly))
                     + (0.019993 - 0.000101*T)*sin(2*calc_deg_rad (mean_anomaly))
                     + (0.00029)*sin(3*calc_deg_rad (mean_anomaly));
  ecliptic_longitude = mean_longitude + equation_of_center;
  ecliptic = 23.43928 + 0.01301*T;

  ecliptic_longitude = calc_deg_rad (ecliptic_longitude);
  ecliptic = calc_deg_rad (ecliptic);

  right_ascension = atan2 (cos(ecliptic)*sin(ecliptic_longitude), cos(ecliptic_longitude));
  declination = asin (sin(ecliptic)*sin(ecliptic_longitude));

  *coordinates_sun = calc_rad_deg (right_ascension);
  *(coordinates_sun+1) = calc_rad_deg (declination);
}

void calc_coordinates_moon(double time_jd, float *coordinates_moon){
  float right_ascension;
  float declination;
  double T;
  double mean_anomaly;
  double mean_longitude; /* ecliptic longitude? */
  double mean_distance;
  double longitude_moon;
  double latitude_moon;
  double ecliptic;

  T = (time_jd-2451545.0)/36525;

  mean_longitude = 218.316 + (13.176396*36525) * T;
  mean_anomaly = 134.963 + (13.064993*36525) * T;
  mean_distance = 93.272 + (13.229350*36525) * T;

  longitude_moon = mean_longitude + 6.289 * sin(calc_deg_rad (mean_anomaly));
  latitude_moon = 5.128 * sin(calc_deg_rad(mean_distance));
  /*double distance_moon;
  distance_moon = 385001. - 20905. * cos(calc_deg_rad(mean_anomaly)); Useful for super moon calculation?*/

  ecliptic = calc_deg_rad(23.43928 + 0.01301*T);
  longitude_moon = calc_deg_rad (longitude_moon);
  latitude_moon = calc_deg_rad (latitude_moon);

  right_ascension = atan2(cos(ecliptic)*sin(longitude_moon)-sin(ecliptic)*tan(latitude_moon), cos(longitude_moon));
  declination = asin(cos(ecliptic)*sin(latitude_moon)+sin(ecliptic)*cos(latitude_moon)*sin(longitude_moon));

  /* g_print("time_jd: %f, ecliptic: %f, longitude_moon: %f, latitude_moon: %f, right_ascension %f\n", time_jd, ecliptic, longitude_moon, latitude_moon, right_ascension); */

  *coordinates_moon = calc_rad_deg (right_ascension);
  *(coordinates_moon+1) = calc_rad_deg (declination);
}

/* Convert between the rotation coordinate system and the horizontal coordinate system */
void calc_convert_rotation_horizontal(float right_ascension, float declination, float latitude, double time_sidereal, float *coordinates)
{
  latitude = calc_deg_rad (latitude);
  right_ascension = calc_deg_rad (right_ascension);
  declination = calc_deg_rad(declination);
  time_sidereal = calc_deg_rad (time_sidereal);

  float azimuth, elevation;
  float x = -cos(latitude)*sin(declination)+sin(latitude)*cos(declination)*cos(time_sidereal-right_ascension);
  float y = cos(declination)*sin(time_sidereal-right_ascension);

  if (x < 0 && y <= 0){
    azimuth = atan(y/x);
  }
  else if (x < 0 && y > 0){
    azimuth = atan(y/x)+2*M_PI;
  }
  else if (x > 0){
    azimuth = atan(y/x)+M_PI;
  }
  else if (x == 0 && y < 0){
    azimuth = M_PI/2;
  }
  else if (x == 0 && y > 0){
    azimuth = -3*M_PI/2;
  }
  else{
    azimuth = 0;
    g_print("Error calculating azimuth!");
  }

  elevation = asin( sin(latitude)*sin(declination) + cos(latitude)*cos(declination)*cos(time_sidereal-right_ascension) );

  /* TODO
   * explanation missing!!
   * */
  /* g_print("Latitude %f, RA %f, dec %f, time_sidereal %f, azimuth %f, elevation %f\n", latitude, right_ascension, declination, time_sidereal, azimuth, elevation); */

  azimuth = calc_rad_deg (azimuth);
  elevation = calc_rad_deg (elevation);

  *coordinates = azimuth;
  *(coordinates+1) = elevation;

  /*g_print("Azimuth: %f, Elevation: %f\n", azimuth, elevation);*/
}
