#pragma once

static int order_timestamp(const void *a, const void *b);

typedef struct {
  double    latitude;
  double    longitude;
  double    altitude;
  double    latitude_meter;
  double    longitude_meter;
  double    speed; // Meters per Second.
  double    bearing;
  double    accuracy;
  bool      stoped;
  long long stoped_time;
  long long time;
  long long time_scaled;
  bool      always_keep;
} Location;

void to_json(nlohmann::json& j, const Location& l) {
  j = nlohmann::json{
    { "latitude"    , l.latitude     },
    { "longitude"   , l.longitude    },
    { "accuracy"    , l.accuracy     },
    { "speed"       , l.speed        },
    { "stoped"      , l.stoped       },
    { "stoped_time" , l.stoped_time  },
    { "time"        , l.time         },
  };
}

// Convert stb Location array to JSON array
nlohmann::json locations_to_json(Location* locations) {
  nlohmann::json j = nlohmann::json::array();
  for (int i = 0; i < arrlen(locations); i++) {
    j.push_back(locations[i]);  // Uses to_json
  }
  return j;
}

typedef struct {
  double    latitude;
  double    longitude;
  long long datetime;
  long long time_stoped;
  char      datetime_str[20];
  char      time_str[9];
} Location_Stop;

inline Location_Stop Location_Stop_Init(Location l) {
  Location_Stop ls = {l.latitude, l.longitude, l.time, l.stoped_time, "", ""};
  Time_ConvertSecondsToHMS(l.stoped_time, ls.time_str  );
  Time_UnixToHMS          (l.time/1000, ls.datetime_str);
  return ls;
}

void to_json(nlohmann::json& j, const Location_Stop& s) {
  j = nlohmann::json{
    { "latitude"    , s.latitude     },
    { "longitude"   , s.longitude    },
    { "duration"    , s.time_stoped  },
    { "duration_str", s.time_str     },
    { "datetime"    , s.datetime     },
    { "datetime_str", s.datetime_str },
  };
}

typedef struct {
  double    latitude_start;
  double    longitude_start;
  double    latitude_end;
  double    longitude_end;
  long long datetime_start;
  long long datetime_end;
  long long duration;
  char      datetime_start_str[20];
  char      datetime_end_str  [20];
  char      duration_str      [9];
  double    distance_traveled;
  double    speed_max;
} Location_Trajectory;

inline Location_Trajectory Location_Trajectory_Init(double lat_start, double lon_start, double lat_end, double lon_end, long long datetime_start, long long datetime_end, double distance_traveled, double speed_max) {

  Location_Trajectory lt = {
    lat_start,
    lon_start,
    lat_end,
    lon_end,
    datetime_start/ 1000,
    datetime_end  / 1000,
    (datetime_end - datetime_start) / 1000,
    "",
    "",
    "",
    distance_traveled,
    speed_max
  };


  Time_UnixToHMS(lt.datetime_start, lt.datetime_start_str);
  Time_UnixToHMS(lt.datetime_end  , lt.datetime_end_str  );
  Time_ConvertSecondsToHMS(lt.duration, lt.duration_str  );

  return lt;
}

void to_json(nlohmann::json& j, const Location_Trajectory& l) {
  j = nlohmann::json{
    { "latitude_start" , l.latitude_start  },
    { "longitude_start", l.longitude_start },

    { "latitude_end"   , l.latitude_end    },
    { "longitude_end"  , l.longitude_end   },

    { "datetime_start" , l.datetime_start  },
    { "datetime_end"   , l.datetime_end    },

    { "duration"       , l.duration        },

    { "datetime_start_str", l.datetime_start_str },
    { "datetime_end_str"  , l.datetime_end_str   },
    { "duration_str"      , l.duration_str       },

    { "distance_traveled", l.distance_traveled },

    { "speed_max", l.speed_max }
  };
}

typedef struct {
  double accuracy_median;
  double accuracy_p75;
  double accuracy_p90;
} Location_GeneralData;


static int order_double(const void *a, const void *b);

Location_GeneralData location_generalData = {0};
//
 // @Todo: Make it inline function... ~ H.Hamza Zakaria - 2026-01-28.
void Location_Print(Location l) {
  fprintf(
    stderr,
    "Location.{%lf, %lf, %lf, %lf, %lf, %lf, %lf, %lld, %lld, %d, %lld}\n", 
    l.latitude,
    l.longitude,
    l.latitude_meter,
    l.longitude_meter,
    l.speed,
    l.bearing,
    l.accuracy,
    l.time, 
    l.time_scaled,
    l.stoped,
    l.stoped_time
  );
}
void Location_Prints(Location *locations) {
  for (int i = 0; i < arrlen(locations); ++i) {
    Location l = locations[i];
    Location_Print(l);
  }
}

#define deg2rad(deg) (deg * (3.14159265359/180))
size_t Location_ReadStandardInput(Location **locations) {
  char buffer[256];

  bool   is_init = false;
  double lat0, lon0, lat0_rad;
  long   time0;

  double *accuracies = NULL;

  // Read from stdinput, data format is `latitude,longitude,time`
  // latitude and longitude ar ein degrees, time in unix timestamp.
  int i = 0;
  while (fgets(buffer, sizeof(buffer), stdin)) {
    double lat, lon, alt, acc, speed, bearing;
    long long ts;
    if (sscanf(buffer, "%lf,%lf,%lf,%lf,%lf,%lf,%lld", &lat, &lon, &alt, &acc, &speed, &bearing, &ts) == 7) {
      // We will need these values to convert to meters, and to scale time properlly.
      if (!is_init) {
        lat0     = lat;
        lon0     = lon;
        time0    = ts;
        lat0_rad = deg2rad(lat0);
        is_init  = true;
      }

      // Convert longitude and latitude to meters.
      double    lon_m = (lon - lon0) * 111320 * cos(lat0_rad);
      double    lat_m = (lat - lat0) * 111320;
      long long t     = (ts  - time0) / 1000; // Convert Unix Time to Relative seconds.

      Location location = {lat, lon, alt, lat_m, lon_m, speed, bearing, acc, false, 0, ts, t};
      arrput(*locations, location);
      arrput(accuracies, acc);
    } else {
      fprintf(stderr, "Bad line: %s", buffer);
    }
  }

  qsort(accuracies, arrlen(accuracies), sizeof(accuracies), order_double);
  location_generalData.accuracy_median = accuracies[(int)(arrlen(accuracies) / 2)];
  location_generalData.accuracy_p75 = accuracies[(int)(arrlen(accuracies)*0.75)];
  location_generalData.accuracy_p90 = accuracies[(int)(arrlen(accuracies)*0.90)];

  return arrlen(*locations);
}

void Location_OrderByTimestamp(Location *locations) {
  qsort(locations, arrlen(locations), sizeof(Location), order_timestamp);
}

void Location_RemoveOutOfOrder(Location *locations) {
  size_t len = arrlen(locations);
  if (len == 0) return;
  for (size_t i = len - 1; i > 0; --i) {
    if (locations[i].time_scaled < locations[i - 1].time_scaled) {
      Location_Print(locations[i]);
      arrdel(locations, i);
    }
  }
}

static int order_timestamp(const void *a, const void *b) {
  const Location *la = (const Location*) a;
  const Location *lb = (const Location*) b;
  if (la->time_scaled < lb->time_scaled) return -1;
  if (la->time_scaled > lb->time_scaled) return 1;
  return 0;
}

static int order_double(const void *a, const void *b) {
  const double da = *(const double*) a;
  const double db = *(const double*) b;
  if (da < db) return -1;
  if (da > db) return 1;
  return 0;
}

