#include <stdio.h>
#include <inttypes.h>

#define MAX_FRAMES   12000 // 10:00.00
#define TIME_STR_LEN 9

typedef struct {
  int32_t x, y, z;
} Vec32;

typedef struct {
  int16_t x, y, z;
} Vec16;

typedef struct {
  Vec32 pos1;
  Vec32 pos2;
  Vec16 rot;
  int16_t grav;
  int16_t steer;
  uint16_t curRpm;
  uint16_t lastRpm;
  uint16_t idleRpm;
  uint16_t speed;
  uint16_t speed2;
  uint16_t lastSpeed;
  uint16_t gearRatio;
  uint8_t data[0x9E];
} CarState;

typedef struct {
  uint8_t data1[0x120];
  Vec16 v1;
  Vec16 v2;
  Vec16 v3;
  Vec16 v4;
  uint16_t fis;
  uint16_t fps;
  uint32_t dist;
  uint16_t frame;
  uint16_t totalFinishTime;
  uint16_t unk;
  uint16_t playerFinish;
  uint16_t oppFinish;
  uint16_t penalty;
  uint16_t impactSpeed;
  uint16_t topSpeed;
  uint16_t jumps;
  CarState player;
  CarState opponent;
  uint8_t data2[0x16A];
} GameState;

void formatTime(uint16_t ticks, char* str)
{
    snprintf(str, TIME_STR_LEN,
            "%d:%02d.%02d",
            ticks / 20 / 60,
            ticks / 20 % 60,
            ticks % 20 * 5);
}

double dst2mi(uint32_t distance)
{
    return distance / 256.0;
}

double dst2km(uint32_t distance)
{
    return dst2mi(distance) * 1.609344;
}

int main(int argc, char* argv[])
{
  int status = 0;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s DUMP.BIN\n", argv[0]);
    status = 1;
    goto done;
  }

  FILE* fin = fopen(argv[1], "r");

  if (fin == NULL) {
      fprintf(stderr, "Error: Couldn't open file \"%s\".\n", argv[1]);
      status = 2;
      goto done;
  }

  uint16_t frames;
  if (fread(&frames, sizeof(uint16_t), 1, fin) != 1) {
      fprintf(stderr, "Error: Couldn't read frame count.\n");
      status = 3;
      goto close;
  }

  if (!frames) {
      fprintf(stderr, "Error: No frames in dump.\n");
      status = 4;
      goto close;
  }

  if (frames > 12000) {
      fprintf(stderr, "Error: Invalid dump, length exceeds 10:00.00 buffer.\n");
      status = 4;
      goto close;
  }

  if (fseek(fin, (frames - 1) * sizeof(GameState), SEEK_CUR)) {
      fprintf(stderr, "Error: Unexpected end of file while seeking to frame %d.\n", frames);
      status = 3;
      goto close;
  }

  GameState state;
  if (fread(&state, sizeof(GameState), 1, fin) != 1) {
      fprintf(stderr, "Error: Couldn't read final frame.\n");
      status = 3;
      goto close;
  }

  char finishStr[TIME_STR_LEN], penaltyStr[TIME_STR_LEN], totalTimeStr[TIME_STR_LEN];
  formatTime(state.playerFinish ? state.playerFinish : frames, finishStr); // Use last last frame if player aborted while driving.
  formatTime(state.penalty, penaltyStr);
  formatTime(state.totalFinishTime, totalTimeStr);

  printf("Driving time : %s\n", finishStr);
  printf("Penalty time : %s\n", penaltyStr);
  printf("Total time   : %s\n",
          (state.totalFinishTime ? totalTimeStr : (state.playerFinish ? "DNF (crashed)" : "DNF (aborted)")));

  printf("Avg speed    : %6.2f mph, %6.2f km/h\n",
          dst2mi((double)state.dist / (state.playerFinish ? state.playerFinish : state.frame)),
          dst2km((double)state.dist / (state.playerFinish ? state.playerFinish : state.frame)));

  printf("Top speed    : %6.2f mph, %6.2f km/h\n",
          dst2mi(state.topSpeed),
          dst2km(state.topSpeed));

  if (state.impactSpeed) {
    printf("Impact speed : %6.2f mph, %6.2f km/h\n",
            dst2mi(state.impactSpeed),
            dst2km(state.impactSpeed));
  }

  printf("Jumps        : %d\n", state.jumps);

close:
  fclose(fin);

done:
  return status;
}
