// Copyright (c) 2022 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "RSI/stats.h"

#if defined(_WIN32)

RSI_Stats::RSI_Stats()
  : m_numprocessors(0)
  , m_self(NULL)
  , m_percent(0.0)
{
  InitProcessCPUStats();
  InitMemStats();
}

RSI_Stats::~RSI_Stats()
{}

WDL_UINT64 RSI_Stats::GetTotalPhysicalMem()
{
  GlobalMemoryStatusEx(&m_meminfo);
  DWORDLONG totalphysicalmem = m_meminfo.ullTotalPhys;
  return (WDL_UINT64)totalphysicalmem;
}

WDL_UINT64 RSI_Stats::GetPhysicalMemUsed()
{
  GlobalMemoryStatusEx(&m_meminfo);
  DWORDLONG physicalmemused = m_meminfo.ullTotalPhys - m_meminfo.ullAvailPhys;
  return (WDL_UINT64)physicalmemused;
}

WDL_UINT64 RSI_Stats::GetProcessMemUsed()
{
  GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&m_pmc, sizeof(m_pmc));
  GlobalMemoryStatusEx(&m_meminfo);
  SIZE_T processmemused = m_pmc.WorkingSetSize;
  return (WDL_UINT64)processmemused;
}

double RSI_Stats::GetProcessCPUUsed()
{
  GetSystemTimeAsFileTime(&m_ftime);
  memcpy(&m_now, &m_ftime, sizeof(FILETIME));

  GetProcessTimes(m_self, &m_ftime, &m_ftime, &m_fsys, &m_fuser);
  memcpy(&m_sys, &m_fsys, sizeof(FILETIME));
  memcpy(&m_user, &m_fuser, sizeof(FILETIME));
  m_percent = (double)(m_sys.QuadPart - m_lastsyscpu.QuadPart) +
      (double)(m_user.QuadPart - m_lastusercpu.QuadPart);
  m_percent /= (m_now.QuadPart - m_lastcpu.QuadPart);
  m_percent /= m_numprocessors;
  m_lastcpu = m_now;
  m_lastusercpu = m_user;
  m_lastsyscpu = m_sys;

  return m_percent * 100;
}


void RSI_Stats::InitProcessCPUStats()
{
  SYSTEM_INFO m_sysinfo;
  FILETIME m_ftime, m_fsys, m_fuser;

  GetSystemInfo(&m_sysinfo);
  m_numprocessors = m_sysinfo.dwNumberOfProcessors;

  GetSystemTimeAsFileTime(&m_ftime);
  memcpy(&m_lastcpu, &m_ftime, sizeof(FILETIME));

  m_self = GetCurrentProcess();
  GetProcessTimes(m_self, &m_ftime, &m_ftime, &m_fsys, &m_fuser);
  memcpy(&m_lastsyscpu, &m_fsys, sizeof(FILETIME));
  memcpy(&m_lastusercpu, &m_fuser, sizeof(FILETIME));
}

void RSI_Stats::InitMemStats()
{
  // GetPhysicalMemUsed and GetTotalPhysicalMem
  m_meminfo.dwLength = sizeof(MEMORYSTATUSEX);
}

#elif defined(__linux__)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <sys/times.h>
#include <sys/vtimes.h>

RSI_Stats::RSI_Stats()
  : m_numprocessors(0)
{
  Init();
}

RSI_Stats::~RSI_Stats()
{}

WDL_UINT64 RSI_Stats::GetTotalPhysicalMem()
{
  sysinfo (&m_meminfo);
  //long long totalVirtualMem = m_meminfo.totalram;
  //totalVirtualMem += m_meminfo.totalswap;
  //totalVirtualMem *= m_meminfo.mem_unit;

  long long totalphysmem = m_meminfo.totalram;
  totalphysmem *= m_meminfo.mem_unit;
  return (WDL_UINT64)totalphysmem;
}

WDL_UINT64 RSI_Stats::GetPhysicalMemUsed()
{
  sysinfo (&m_meminfo);
  long long physmemused = m_meminfo.totalram - m_meminfo.freeram;
  physmemused *= m_meminfo.mem_unit;
  return (WDL_UINT64)physmemused;
}

WDL_UINT64 RSI_Stats::GetProcessMemUsed()
{
  FILE* file = fopen("/proc/self/status", "r");
  int result = -1;
  char line[128];

  while (fgets(line, 128, file) != NULL){
      if (strncmp(line, "VmRSS:", 6) == 0){
          result = ParseLine(line);
          break;
      }
  }
  fclose(file);
  WDL_UINT64 res = (WDL_UINT64)result;
  return result * 1024;
}

double RSI_Stats::GetProcessCPUUsed()
{
  struct tms timesample;
  clock_t now;
  double percent;

  now = times(&timesample);
  if (now <= m_lastcpu || timesample.tms_stime < m_lastsyscpu ||
    timesample.tms_utime < m_lastusercpu)
  {
    //Overflow detection. Just skip this value.
    percent = -1.0;
  }
  else
  {
    percent = (timesample.tms_stime - m_lastsyscpu) +
      (timesample.tms_utime - m_lastusercpu);
    percent /= (now - m_lastcpu);
    percent /= m_numprocessors;
    percent *= 100;
  }
  m_lastcpu = now;
  m_lastsyscpu = timesample.tms_stime;
  m_lastusercpu = timesample.tms_utime;

  return percent;
}

void RSI_Stats::Init()
{
  FILE* file;
  struct tms timesample;
  char line[128];

  m_lastcpu = times(&timesample);
  m_lastsyscpu = timesample.tms_stime;
  m_lastusercpu = timesample.tms_utime;

  file = fopen("/proc/cpuinfo", "r");
  m_numprocessors = 0;
  while(fgets(line, 128, file) != NULL){
      if (strncmp(line, "processor", 9) == 0) m_numprocessors++;
  }
  fclose(file);
}

int RSI_Stats::ParseLine(char* line)
{
  // This assumes that a digit will be found and the line ends in " Kb".
  int i = strlen(line);
  const char* p = line;
  while (*p <'0' || *p > '9') p++;
  line[i-3] = '\0';
  i = atoi(p);
  return i;
}

#elif defined(__APPLE__)

RSI_Stats::RSI_Stats()
{}

RSI_Stats::~RSI_Stats()
{}

WDL_UINT64 RSI_Stats::GetTotalPhysicalMem()
{
  return 0;
}

WDL_UINT64 RSI_Stats::GetPhysicalMemUsed()
{
  return 0;
}

WDL_UINT64 RSI_Stats::GetProcessMemUsed()
{
  return 0;
}

double RSI_Stats::GetProcessCPUUsed()
{
  return 0.0;
}

#endif
