/**
 * @file
 *
 * @date Created  on Oct 13, 2025
 * @author Attila Kovacs
 */

#include <iostream>
#include <string.h>

#include "TestUtil.hpp"




int main() {
  TestUtil test = TestUtil("Observer");

  int n = 0;

  EOP eop(37, 0.15, 0.2 * Unit::arcsec, -0.3 * Unit::arcsec);
  Site site(Angle(-2.0), Angle(1.0), Coordinate(75.0));
  Apparent app = Apparent::from_tod(1.234 * Unit::hour_angle, -23.45 * Unit::deg, Observer::at_geocenter().reduced_accuracy_frame_at(Time::b1950()));

  Observer x = Observer::undefined();
  const Observer *copy;

  if(!test.check("undefined()", !x.is_valid())) n++;
  if(!test.equals("undefined().type", (int) x.type(), -1)) n++;
  if(!test.check("undefined().to_interferometric", !x.to_interferometric(app).is_valid())) n++;

  copy = x.copy();
  if(!test.check("undefined().copy()", !copy->is_valid())) n++;
  delete copy;

  if(!test.check("invalid Site", !Observer::on_earth(Site::undefined(), eop).is_valid())) n++;
  if(!test.check("invalid Site (moving)", !Observer::moving_on_earth(Site::undefined(), Velocity::stationary(), eop).is_valid())) n++;
  if(!test.check("invalid Site (moving ENU)", !Observer::moving_on_earth(Site::undefined(), eop, ScalarVelocity(1.0), Angle(0.0)).is_valid())) n++;
  if(!test.check("invalid Velocity", !Observer::moving_on_earth(site, Velocity::undefined(), eop).is_valid())) n++;
  if(!test.check("invalid speed", !Observer::moving_on_earth(site, eop, ScalarVelocity(NAN), Angle(0.0)).is_valid())) n++;
  if(!test.check("invalid direction", !Observer::moving_on_earth(site, eop, ScalarVelocity(1.0), Angle(NAN)).is_valid())) n++;
  if(!test.check("invalid vertial speed", !Observer::moving_on_earth(site, eop, ScalarVelocity(1.0), Angle(0.0), ScalarVelocity(NAN)).is_valid())) n++;
  if(!test.check("invalid EOP", !Observer::on_earth(site, EOP::undefined()).is_valid())) n++;
  if(!test.check("invalid EOP (moving)", !Observer::moving_on_earth(site, Velocity::stationary(), EOP::undefined()).is_valid())) n++;

  if(!test.check("invalid orbit Position", !Observer::in_earth_orbit(Position::undefined(), Velocity::stationary()).is_valid())) n++;
  if(!test.check("invalid orbit Velocity", !Observer::in_earth_orbit(Position::origin(), Velocity::undefined()).is_valid())) n++;

  if(!test.check("invalid ssb Position", !Observer::in_solar_system(Position::undefined(), Velocity::stationary()).is_valid())) n++;
  if(!test.check("invalid ssb Velocity", !Observer::in_solar_system(Position::origin(), Velocity::undefined()).is_valid())) n++;

  if(!test.equals("to_string(base)", Observer::undefined().to_string(),
          "Observer type -1")) n++;


  test = TestUtil("GeodeticObserver");

  GeodeticObserver gdx =  Observer::moving_on_earth(Site::undefined(), Velocity::undefined(), EOP::undefined());
  if(!test.check("is_valid(invalid)", !gdx.is_valid())) n++;
  if(!test.check("site(invalid)", !gdx.site().is_valid())) n++;
  if(!test.check("itrs_velocity(invalid)", !gdx.itrs_velocity().is_valid())) n++;
  if(!test.check("enu_velocity(invalid)", !gdx.enu_velocity().is_valid())) n++;


  GeodeticObserver g1 = Observer::on_earth(site, eop);
  if(!test.check("is_valid(on_earth)", g1.is_valid())) n++;
  if(!test.equals("type(on_earth)", g1.type(), NOVAS_OBSERVER_ON_EARTH)) n++;
  if(!test.check("is_geodetic(on_earth)", g1.is_geodetic())) n++;
  if(!test.check("is_geocentric(on_earth)", !g1.is_geocentric())) n++;
  if(!test.check("site()", g1.site() == site)) n++;
  if(!test.check("velocity()", g1.itrs_velocity() == Velocity::stationary())) n++;
  if(!test.check("site()", g1.mean_eop() == eop)) n++;
  if(!test.check("frame_at(reduced)", g1.frame_at(Time::j2000(), NOVAS_REDUCED_ACCURACY).is_valid())) n++;
  if(!test.check("frame_at(full)", !g1.frame_at(Time::j2000(), NOVAS_FULL_ACCURACY).is_valid())) n++;
  if(!test.equals("to_string(on_earth)", g1.to_string(),
          "GeodeticObserver at Site (W 114d 35m 29.612s, N  57d 17m 44.806s, altitude 75 m)")) n++;

  copy = g1.copy();
  if(!test.check("copy(on_earth)", memcmp(copy->_novas_observer(), g1._novas_observer(), sizeof(observer)) == 0)) n++;
  delete copy;

  EOP e = g1.eop_at(app.frame().time());

  double uvw[3] = {0.0};
  novas_site_uvw(app.frame().time()._novas_timespec(), g1.site()._on_surface(), app.xyz().scaled(1.0 / Unit::AU)._array(),
          e.xp().arcsec(), e.yp().arcsec(), NOVAS_REDUCED_ACCURACY, uvw);

  Interferometric u = g1.to_interferometric(app);
  if(!test.check("to_interferometric()", u.is_valid())) n++;
  if(!test.equals("to_interferometric() u", u[0], uvw[0], 1e-8)) n++;
  if(!test.equals("to_interferometric() v", u[1], uvw[1], 1e-8)) n++;
  if(!test.equals("to_interferometric() w", u[2], uvw[2], 1e-8)) n++;

  if(!test.check("to_interferometric(invalid)", !gdx.to_interferometric(app).is_valid())) n++;
  if(!test.check("to_interferometric(phase_center invalid)", !g1.to_interferometric(Apparent::undefined()).is_valid())) n++;
  if(!test.check("to_interferometric(system invalid)", !g1.to_interferometric(app, (enum novas_reference_system) -1).is_valid())) n++;

  Apparent xapp = app;
  ((novas_frame *) xapp.frame()._novas_frame())->accuracy = (enum novas_accuracy) -1;
  if(!test.check("to_interferometric(accuracy invalid)", !gdx.to_interferometric(xapp).is_valid())) n++;

  const observer *o = g1._novas_observer();
  if(!test.check("_novas_observer(on_earth)", o != NULL && o->where == NOVAS_OBSERVER_ON_EARTH)) n++;

  double gcp[3] = {0.0}, gcv[3] = {0.0};
  novas_site_gcrs_posvel(app.frame().time()._novas_timespec(), g1.site()._on_surface(), NULL, e.xp().arcsec(), e.yp().arcsec(), NOVAS_FULL_ACCURACY, gcp, gcv);
  GeocentricObserver gco = g1.to_geocentric_at(app.frame().time(), NOVAS_FULL_ACCURACY);
  if(!test.check("to_geocentric_at()", gco.is_valid())) n++;
  if(!test.check("to_geocentric_at(invalid)", !gdx.to_geocentric_at(app.frame().time()).is_valid())) n++;
  if(!test.check("to_geocentric_at() pos", gco.gcrs_position() == Position(gcp, Unit::AU))) n++;
  if(!test.check("to_geocentric_at() vel", gco.gcrs_velocity() == Velocity(gcv, Unit::AU_per_day))) n++;

  Velocity v1 = Velocity(1.0, -2.0, 3.0);
  GeodeticObserver g2 = Observer::moving_on_earth(site, v1, eop);
  if(!test.equals("type(moving)", g2.type(), NOVAS_AIRBORNE_OBSERVER)) n++;
  if(!test.check("itrs_velocity(moving)", g2.itrs_velocity() == v1)) n++;
  if(!test.equals("to_string(moving)", g2.to_string(),
          "GeodeticObserver at Site (W 114d 35m 29.612s, N  57d 17m 44.806s, altitude 75 m) moving at ENU Velocity (0.002 km/s, 0.000 km/s, 0.003 km/s)")) n++;

  copy = g2.copy();
  if(!test.check("copy(moving)", memcmp(copy->_novas_observer(), g2._novas_observer(), sizeof(observer)) == 0)) n++;
  delete copy;


  double v_enu[3] = {1.0, -2.0, 3.0}, v_itrs[3] = {0.0};
  novas_enu_to_itrs(v_enu, site.longitude().deg(), site.latitude().deg(), v_itrs);
  GeodeticObserver g3 = Observer::moving_on_earth(site, eop, ScalarVelocity(hypot(v_enu[0], v_enu[1]) * Unit::km_per_s), Angle(atan2(v_enu[0], v_enu[1])), ScalarVelocity(3.0 * Unit::km_per_s));
  if(!test.equals("type(moving ENU)", g3.type(), NOVAS_AIRBORNE_OBSERVER)) n++;
  if(!test.check("enu_velocity(moving ENU)", g3.enu_velocity() == Velocity(v_enu, Unit::km_per_s))) n++;
  if(!test.check("itrs_velocity(moving ENU)", g3.itrs_velocity() == Velocity(v_itrs, Unit::km_per_s))) n++;
  if(!test.equals("to_string(moving ENU)", g3.to_string(),
          "GeodeticObserver at Site (W 114d 35m 29.612s, N  57d 17m 44.806s, altitude 75 m) moving at ENU Velocity (1.000 km/s, -2.000 km/s, 3.000 km/s)")) n++;

  GeocentricObserver gc = Observer::at_geocenter();
  if(!test.check("is_valid(gc)", gc.is_valid())) n++;
  if(!test.equals("type(gc)", gc.type(), NOVAS_OBSERVER_AT_GEOCENTER)) n++;
  if(!test.check("is_geocentric(gc)", gc.is_geocentric())) n++;
  if(!test.check("is_geodetic(gc)", !gc.is_geodetic())) n++;
  if(!test.check("geocentric_position(gc)", gc.gcrs_position() == Position::origin())) n++;
  if(!test.check("geocentric_velocity(gc)", gc.gcrs_velocity() == Velocity::stationary())) n++;
  if(!test.check("frame_at(reduced)", gc.frame_at(Time::j2000(), NOVAS_REDUCED_ACCURACY).is_valid())) n++;
  if(!test.check("frame_at(full)", !gc.frame_at(Time::j2000(), NOVAS_FULL_ACCURACY).is_valid())) n++;
  if(!test.equals("to_string(gc)", gc.to_string(), "Geocentric Observer")) n++;


  copy = gc.copy();
  if(!test.check("copy(gc)", memcmp(copy->_novas_observer(), gc._novas_observer(), sizeof(observer)) == 0)) n++;
  delete copy;

  o = gc._novas_observer();
  if(!test.check("_novas_observer(gc)", o != NULL && o->where == NOVAS_OBSERVER_AT_GEOCENTER)) n++;

  test = TestUtil("GeocentricObserver");

  Position p1(10000.0 * Unit::km, 0.0, 0.0);

  GeocentricObserver ogx = Observer::in_earth_orbit(Position::undefined(), Velocity::undefined());
  if(!test.check("is_valid(orbit invalid)", !ogx.is_valid())) n++;
  if(!test.check("geocentric_position(orbit invalid)", !ogx.gcrs_position().is_valid())) n++;
  if(!test.check("geocentric_velocity(orbit invalid)", !ogx.gcrs_velocity().is_valid())) n++;

  GeocentricObserver o1 = Observer::in_earth_orbit(p1, v1);
  if(!test.check("is_valid(orbit)", o1.is_valid())) n++;
  if(!test.equals("type(orbit)", o1.type(), NOVAS_OBSERVER_IN_EARTH_ORBIT)) n++;
  if(!test.check("is_geocentric(orbit)", o1.is_geocentric())) n++;
  if(!test.check("is_geodetic(orbit)", !o1.is_geodetic())) n++;
  if(!test.check("geocentric_position(orbit)", o1.gcrs_position() == p1)) n++;
  if(!test.check("geocentric_velocity(orbit)", o1.gcrs_velocity() == v1)) n++;
  if(!test.equals("to_string(orbit)", o1.to_string(), "Geocentric Observer at Position (10000.000 km, 0.000 m, 0.000 m) moving at Velocity (0.001 km/s, -0.002 km/s, 0.003 km/s)")) n++;



  o = o1._novas_observer();
  if(!test.check("_novas_observer(orbit)", o != NULL && o->where == NOVAS_OBSERVER_IN_EARTH_ORBIT)) n++;

  copy = o1.copy();
  if(!test.check("copy(orbit)", memcmp(copy->_novas_observer(), o1._novas_observer(), sizeof(observer)) == 0)) n++;
  delete copy;

  test = TestUtil("SolarSystemObserver");

  SolarSystemObserver ssb = Observer::at_ssb();
  if(!test.check("is_valid(ssb)", ssb.is_valid())) n++;
  if(!test.equals("type(ssb)", ssb.type(), NOVAS_SOLAR_SYSTEM_OBSERVER)) n++;
  if(!test.check("is_geocentric(ssb)", !ssb.is_geocentric())) n++;
  if(!test.check("is_geodetic(ssb)", !ssb.is_geodetic())) n++;
  if(!test.check("frame_at(reduced)", ssb.frame_at(Time::j2000(), NOVAS_REDUCED_ACCURACY).is_valid())) n++;
  if(!test.check("frame_at(full)", !ssb.frame_at(Time::j2000(), NOVAS_FULL_ACCURACY).is_valid())) n++;
  if(!test.equals("to_string(ssb)", ssb.to_string(), "SolarSystemObserver at SSB")) n++;

  o = ssb._novas_observer();
  if(!test.check("_novas_observer(ssb)", o != NULL && o->where == NOVAS_SOLAR_SYSTEM_OBSERVER)) n++;

  Position p2(-1.1 * Unit::au, 2.2 * Unit::au, -3.3 * Unit::au);
  Velocity v2(1.0 * Unit::AU_per_day, -2.0 * Unit::AU_per_day, 3.0 * Unit::AU_per_day);

  SolarSystemObserver sx = Observer::in_solar_system(Position::undefined(), Velocity::undefined());
  if(!test.check("is_valid(invalid)", !sx.is_valid())) n++;
  if(!test.check("ssb_position(invalid)", !sx.ssb_position().is_valid())) n++;
  if(!test.check("ssb_velocity(invalid)", !sx.ssb_velocity().is_valid())) n++;

  SolarSystemObserver s1 = Observer::in_solar_system(p2, v2);
  if(!test.check("is_valid(ss)", s1.is_valid())) n++;
  if(!test.equals("type(ss)", s1.type(), NOVAS_SOLAR_SYSTEM_OBSERVER)) n++;
  if(!test.check("is_geocentric(ss)", !s1.is_geocentric())) n++;
  if(!test.check("is_geodetic(ss)", !s1.is_geodetic())) n++;
  if(!test.check("ssb_position(ss)", s1.ssb_position() == p2)) n++;
  if(!test.check("ssb_velocity(ss)", s1.ssb_velocity() == v2)) n++;
  if(!test.equals("to_string(ss)", s1.to_string(),
          "SolarSystemObserver at Position (-1.100 AU, 2.200 AU, -3.300 AU) moving at SSB Velocity (1731.457 km/s, -3462.914 km/s, 5194.371 km/s)")) n++;

  o = s1._novas_observer();
  if(!test.check("_novas_observer(ss)", o != NULL && o->where == NOVAS_SOLAR_SYSTEM_OBSERVER)) n++;

  copy = s1.copy();
  if(!test.check("copy(ss)", memcmp(copy->_novas_observer(), s1._novas_observer(), sizeof(observer)) == 0)) n++;
  delete copy;

  std::cout << "Observer.cpp: " << (n > 0 ? "FAILED" : "OK") << "\n";
  return n;
}
