/*
* Descent 3 
* Copyright (C) 2024 Parallax Software
*
* 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 3 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, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/rtperformance/rtperformance.cpp $
 * $Revision: 9 $
 * $Date: 10/21/99 3:21p $
 * $Author: Kevin $
 *
 * Run time performance lib
 *
 * $Log: /DescentIII/Main/rtperformance/rtperformance.cpp $
 *
 * 9     10/21/99 3:21p Kevin
 * Mac merge
 *
 * 8     5/07/99 6:21p Jeff
 * hooked up fvi to rtperformance
 *
 * 7     4/16/99 4:04a Jeff
 * fixed up for non-window's builds
 *
 * 6     4/16/99 1:59a Jeff
 * ifdef out windows.h for now windows builds
 *
 * 5     10/19/98 7:51p Kevin
 * performance testing
 *
 * 4     10/19/98 2:48p Jeff
 * changed define for rtp performance...just define USE_RTP in
 * rtperformance.h in order to enable it
 *
 * 3     10/17/98 6:05p Jeff
 * it helps to write the heading to file
 *
 * 2     10/17/98 5:58p Jeff
 * put in heading
 *
 * $NoKeywords: $
 */

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#if defined(WIN32)
#include <windows.h>
#endif

#include "cfile.h"
#include "ddio.h"
#include "manage.h"
#include "mono.h"
#include "rtperformance.h"

float rtp_startlog_time;

// maximum number of samples before we stop and autoflush
#define MAX_RTP_SAMPLES 3800 // this is a little more than whats needed for 2 minutes at 30fps

//		Internal Global Vars
// ------------------------------
INT64 Runtime_performance_flags = RTI_FRAMETIME;
INT64 Runtime_performance_clockfreq = 0;
INT64 Runtime_performance_frame_counter = 0;
uint32_t Runtime_performance_counter = 0;
uint8_t Runtime_performance_enabled = 0;

tRTFrameInfo RTP_SingleFrame;
#ifdef USE_RTP
tRTFrameInfo RTP_FrameBuffer[MAX_RTP_SAMPLES];
#endif

void rtp_Close();

/*
void rtp_WriteBufferLog
        Writes the buffer of frames to file...
*/
void rtp_WriteBufferLog() {
#ifdef USE_RTP
  Runtime_performance_enabled = 1; // make sure it's enabled for the macros
  // determine how many frames to write out
  uint32_t Num_frames;
  uint32_t counter;
  char buffer[4096];

  Num_frames = std::min<unsigned>(Runtime_performance_counter, MAX_RTP_SAMPLES);

  // Open the log file for writing
  CFILE *file = cfopen(std::filesystem::path(LocalD3Dir) / "D3Performance.txt", "wt");

  if (file) {
    mprintf(0, "RTP: Recording Log\n");

    strcpy(buffer, "FrameNum,FrameTime,RenderFrameTime,MultiFrameTime,MusicFrameTime,AmbientSoundTime,WeatherFrameTime,"
                   "PlayerFrameTime,DoorwayFrameTime,LevelGoalFrameTime,MatCenFrameTime,ObjectFrameTime,AIFrameAllTime,"
                   "ProcessKeysTime,REN:NumTexturesUploaded,REN:PolysDrawn,OBJ:CT_FlyingTime,OBJ:CT_AIDoFrameTime,OBJ:"
                   "CT_WeaponFrameTime,OBJ:CT_ExplosionFrameTime,OBJ:CT_DebrisFrameTime,OBJ:CT_SplinterFrameTime,OBJ:"
                   "MT_PhsyicsFrameTime,OBJ:MT_WalkingFrame,OBJ:MT_ShockWaveTime,OBJ:DoEffectTime,OBJ:MovePlayerTime,"
                   "OBJ:D3XIntervalTime,OBJ:ObjLightTime,FRAME:NormalEventTime,AnimCycle,VisEffectMoveAll,"
                   "DoPhysLinkedFrame,ObjDoFrame,NumFVICalls,FVITime");
    cf_WriteString(file, buffer);

    // Loop through all the frames, and write out the data for each frame
    for (counter = 0; counter < Num_frames; counter++) {
      tRTFrameInfo *fi = &RTP_FrameBuffer[counter];
      double renderframe_time;
      double multiframe_time;
      double musicframe_time;
      double ambsound_frame_time;
      double weatherframe_time;
      double playerframe_time;
      double doorframe_time;
      double levelgoal_time;
      double matcenframe_time;
      double objframe_time;
      double aiframeall_time;
      double processkeys_time;
      double ct_flying_time;
      double ct_aidoframe_time;
      double ct_weaponframe_time;
      double ct_explosionframe_time;
      double ct_debrisframe_time;
      double ct_splinterframe_time;
      double mt_physicsframe_time;
      double mt_walkingframe_time;
      double mt_shockwave_time;
      double obj_doeffect_time;
      double obj_move_player_time;
      double obj_d3xint_time;
      double obj_objlight_time;
      double normalevent_time;
      double cycle_anim;
      double vis_eff_move;
      double phys_link;
      double obj_do_frm;
      double fvi_time;

      RTP_CLOCKSECONDS(fi->renderframe_time, renderframe_time);
      RTP_CLOCKSECONDS(fi->multiframe_time, multiframe_time);
      RTP_CLOCKSECONDS(fi->musicframe_time, musicframe_time);
      RTP_CLOCKSECONDS(fi->ambsound_frame_time, ambsound_frame_time);
      RTP_CLOCKSECONDS(fi->weatherframe_time, weatherframe_time);
      RTP_CLOCKSECONDS(fi->playerframe_time, playerframe_time);
      RTP_CLOCKSECONDS(fi->doorframe_time, doorframe_time);
      RTP_CLOCKSECONDS(fi->levelgoal_time, levelgoal_time);
      RTP_CLOCKSECONDS(fi->matcenframe_time, matcenframe_time);
      RTP_CLOCKSECONDS(fi->objframe_time, objframe_time);
      RTP_CLOCKSECONDS(fi->aiframeall_time, aiframeall_time);
      RTP_CLOCKSECONDS(fi->processkeys_time, processkeys_time);
      RTP_CLOCKSECONDS(fi->ct_flying_time, ct_flying_time);
      RTP_CLOCKSECONDS(fi->ct_aidoframe_time, ct_aidoframe_time);
      RTP_CLOCKSECONDS(fi->ct_weaponframe_time, ct_weaponframe_time);
      RTP_CLOCKSECONDS(fi->ct_explosionframe_time, ct_explosionframe_time);
      RTP_CLOCKSECONDS(fi->ct_debrisframe_time, ct_debrisframe_time);
      RTP_CLOCKSECONDS(fi->ct_splinterframe_time, ct_splinterframe_time);
      RTP_CLOCKSECONDS(fi->mt_physicsframe_time, mt_physicsframe_time);
      RTP_CLOCKSECONDS(fi->mt_walkingframe_time, mt_walkingframe_time);
      RTP_CLOCKSECONDS(fi->mt_shockwave_time, mt_shockwave_time);
      RTP_CLOCKSECONDS(fi->obj_doeffect_time, obj_doeffect_time);
      RTP_CLOCKSECONDS(fi->obj_move_player_time, obj_move_player_time);
      RTP_CLOCKSECONDS(fi->obj_d3xint_time, obj_d3xint_time);
      RTP_CLOCKSECONDS(fi->obj_objlight_time, obj_objlight_time);
      RTP_CLOCKSECONDS(fi->normalevent_time, normalevent_time);
      RTP_CLOCKSECONDS(fi->cycle_anim, cycle_anim);
      RTP_CLOCKSECONDS(fi->vis_eff_move, vis_eff_move);
      RTP_CLOCKSECONDS(fi->phys_link, phys_link);
      RTP_CLOCKSECONDS(fi->obj_do_frm, obj_do_frm);
      RTP_CLOCKSECONDS(fi->fvi_time, fvi_time);

      snprintf(buffer, sizeof(buffer),
               "%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d,%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f "
               "%d %lld",
               (int)fi->frame_num, fi->frame_time, renderframe_time, multiframe_time, musicframe_time,
               ambsound_frame_time, weatherframe_time, playerframe_time, doorframe_time, levelgoal_time,
               matcenframe_time, objframe_time, aiframeall_time, processkeys_time, fi->texture_uploads, fi->polys_drawn,
               ct_flying_time, ct_aidoframe_time, ct_weaponframe_time, ct_explosionframe_time, ct_debrisframe_time,
               ct_splinterframe_time, mt_physicsframe_time, mt_walkingframe_time, mt_shockwave_time, obj_doeffect_time,
               obj_move_player_time, obj_d3xint_time, obj_objlight_time, normalevent_time, cycle_anim, vis_eff_move,
               phys_link, obj_do_frm, fi->fvi_calls, fi->fvi_time);

      cf_WriteString(file, buffer);
    }

    // Close the log file
    cfclose(file);
  } else
    mprintf(0, "RTP: Unable to open log for writing\n");
#endif
}

/*
void rtp_RecordFrame
        Calling this will record the data of the frame into the internal log, and prepare for
        the next frame
*/
void rtp_RecordFrame() {
#ifdef USE_RTP
  if (Runtime_performance_enabled) {
    //	do our saving of information
    // --------------------------------

    RTP_SingleFrame.frame_num = Runtime_performance_frame_counter; // save the frame num

    // Copy everything into the buffer, and advance the buffer
    memcpy(&RTP_FrameBuffer[Runtime_performance_counter], &RTP_SingleFrame, sizeof(tRTFrameInfo));

    Runtime_performance_counter++;

    if (Runtime_performance_counter >= MAX_RTP_SAMPLES) {
      // uh oh, we hit the end of our samples, flush the buffer and stop
      rtp_StopLog();
    }

    //	reset our global struct to zero everything out
    // ------------------------------------------------
    memset(&RTP_SingleFrame, 0, sizeof(tRTFrameInfo));
  }

  // Update the frame counter to the next frame, always
  Runtime_performance_frame_counter++;
#endif
}

//		External Functions
// -----------------------------
/*
void rtp_Init
        This function initilizes the runtime performance system so it's ready to be used
*/
void rtp_Init() {
#ifdef USE_RTP
  Runtime_performance_flags = 0;
  Runtime_performance_clockfreq = 0;
  Runtime_performance_frame_counter = 0;
  Runtime_performance_counter = 0;
  Runtime_performance_enabled = 0;

  LARGE_INTEGER freq;
  if (!QueryPerformanceFrequency(&freq)) {
    // there is no hi-res clock available....ummmm
    mprintf(0, "RTP: No Hi-Resolution clock available on this system!!!!!!\n");
    // well, this isn't good...what to do, what to do?
  }

  // Setup the clock freq....we'll need this info later I guess when converting to seconds
  Runtime_performance_clockfreq = freq.QuadPart;
  rtp_EnableFlags(RTI_FRAMETIME);

  atexit(rtp_Close);
#endif
}

/*
void rtp_Close
        This function shuts down the runtime performance system when the game is done
*/
void rtp_Close() {
#ifdef USE_RTP
  if (Runtime_performance_enabled) {
    // Save the log out, since it was currently logging
    rtp_StopLog();
  }
#endif
}

/*
void rtp_EnableFlags
        This function enables the performance profiling of whatever flags you pass in
*/
void rtp_EnableFlags(INT64 flags) {
#ifdef USE_RTP
  Runtime_performance_flags |= flags;
#endif
}

/*
void rtp_DisableFlags
        This function disables the performance profiling of whatever flags you pass in
*/
void rtp_DisableFlags(INT64 flags) {
#ifdef USE_RTP
  Runtime_performance_flags &= ~flags;
#endif
}

/*
void rtp_StartLog
        Calling this function will reset the log and start a new log, recording immediatly
*/
void rtp_StartLog() {
#ifdef USE_RTP
  mprintf(0, "RTP: Starting Log\n");
  Runtime_performance_counter = 0;
  Runtime_performance_enabled = 1;
  memset(&RTP_SingleFrame, 0, sizeof(tRTFrameInfo));
  rtp_startlog_time = timer_GetTime();
#endif
}

/*
void rtp_StopLog
        Calling this function will stop the currently processing log and write it out to
        file.
*/
void rtp_StopLog() {
#ifdef USE_RTP
  mprintf(0, "Recorded performance for %f seconds\n", timer_GetTime() - rtp_startlog_time);
  mprintf(0, "RTP: Stopping Log\n");

  // Save out the log now
  rtp_WriteBufferLog();

  Runtime_performance_enabled = 0;
#endif
}

/*
void rtp_PauseLog
        Calling this function will pause the log recording until rtp_ResumeLog is called
*/
void rtp_PauseLog() {
#ifdef USE_RTP
  mprintf(0, "RTP: Pausing Log\n");
  Runtime_performance_enabled = 0;
#endif
}

/*
void rtp_ResumeLog
        Calling this will resume a paused log, starting at where it left off
*/
void rtp_ResumeLog() {
#ifdef USE_RTP
  mprintf(0, "RTP: Resuming Log\n");
  Runtime_performance_enabled = 1;
#endif
}

//			Internal Functions
// -------------------------------
/*
INT64 rtp_GetClock
        Returns the current hi-resolution clock value...no checking for overflow
*/
INT64 rtp_GetClock() {
#ifdef USE_RTP
  LARGE_INTEGER t;
  QueryPerformanceCounter(&t);
  return (INT64)t.QuadPart;
#else
  return 0;
#endif
}
