#include "config.h"

#include "statistics.h"

#ifdef STATISTICS

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <syslog.h>

#include "tick.h"
#include "text.h"
#include "spi.h"
#include "util.h"
#include "mailbox.h"
#include "mem_alloc.h"
#include "dma.h"

volatile uint64_t timeWastedPollingGPU = 0;
volatile float statsSpiBusSpeed = 0;
volatile int statsBcmCoreSpeed = 0;
volatile int statsCpuFrequency = 0;
volatile double statsCpuTemperature = 0;
double spiThreadUtilizationRate;
double spiBusDataRate;
int statsGpuPollingWasted = 0;
uint64_t statsBytesTransferred = 0;

int frameSkipTimeHistorySize = 0;
uint64_t frameSkipTimeHistory[FRAME_HISTORY_MAX_SIZE] = {};

#ifdef FRAME_COMPLETION_TIME_STATISTICS

#define FRAME_COMPLETION_HISTORY_MAX_SIZE 480
uint64_t frameCompletionTimeHistory[FRAME_COMPLETION_HISTORY_MAX_SIZE] = {};
int frameCompletionTimeHistorySize = 0;

int statsFrameIntervalsY[FRAME_COMPLETION_HISTORY_MAX_SIZE] = {};
int statsFrameIntervalsSize = 0;
int statsTargetFrameRateY = 0;
int statsAvgFrameRateIntervalY = 0;

void AddFrameCompletionTimeMarker()
{
  for(int i = frameCompletionTimeHistorySize; i >= 1; --i)
    frameCompletionTimeHistory[i] = frameCompletionTimeHistory[i-1];
  frameCompletionTimeHistory[0] = tick();
  if (frameCompletionTimeHistorySize+1 < FRAME_COMPLETION_HISTORY_MAX_SIZE)
    ++frameCompletionTimeHistorySize;
}
#else
void AddFrameCompletionTimeMarker() {}
#endif

char dmaChannelsText[32] = {};
char fpsText[32] = {};
char spiUsagePercentageText[32] = {};
char spiBusDataRateText[32] = {};
uint16_t spiUsageColor = 0, fpsColor = 0;
char statsFrameSkipText[32] = {};
char spiSpeedText[32] = {};
char spiSpeedText2[32] = {};
char cpuTemperatureText[32] = {};
uint16_t cpuTemperatureColor = 0;
char gpuPollingWastedText[32] = {};
uint16_t gpuPollingWastedColor = 0;

char cpuMemoryUsedText[32] = {};
char gpuMemoryUsedText[32] = {};

uint64_t statsLastPrint = 0;

void UpdateStatisticsNumbers()
{
  // BCM core and SPI bus speed
  int freq = (int)MailboxRet2(0x00030002/*Get Clock Rate*/, 0x4/*CORE*/);
  statsBcmCoreSpeed = freq/1000000;
  statsSpiBusSpeed = (float)freq/(1000000*spi->clk);

  // CPU temperature
  statsCpuTemperature = MailboxRet2(0x00030006/*Get Temperature*/, 0)/1000.0;

  // Raspberry pi main CPU speed
  statsCpuFrequency = (int)MailboxRet2(0x00030002/*Get Clock Rate*/, 0x3/*ARM*/) / 1000000;
}

void DrawStatisticsOverlay(uint16_t *framebuffer)
{
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, fpsText, 1, 1, fpsColor, 0);
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, statsFrameSkipText, strlen(fpsText)*6, 1, RGB565(31,0,0), 0);

#if DISPLAY_DRAWABLE_WIDTH > 130
#ifdef USE_DMA_TRANSFERS
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, dmaChannelsText, 1, 10, RGB565(31, 44, 8), 0);
#endif
#ifdef USE_SPI_THREAD
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, spiUsagePercentageText, 75, 10, spiUsageColor, 0);
#endif
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, spiBusDataRateText, 60, 1, 0xFFFF, 0);
#endif

#if DISPLAY_DRAWABLE_WIDTH > 180
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, spiSpeedText, 120, 1, RGB565(31,14,20), 0);
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, spiSpeedText2, 120, 10, RGB565(10,24,31), 0);
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, cpuTemperatureText, 190, 1, cpuTemperatureColor, 0);
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, gpuPollingWastedText, 222, 1, gpuPollingWastedColor, 0);
#endif

#if (defined(DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE) && DISPLAY_DRAWABLE_HEIGHT >= 290) || (!defined(DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE) && DISPLAY_DRAWABLE_WIDTH >= 290)
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, cpuMemoryUsedText, 250, 1, RGB565(31,50,21), 0);
  DrawText(framebuffer, gpuFrameWidth, gpuFramebufferScanlineStrideBytes, gpuFrameHeight, gpuMemoryUsedText, 250, 10, RGB565(31,50,31), 0);
#endif

#ifdef FRAME_COMPLETION_TIME_STATISTICS

#ifdef DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE
#define FRAMERATE_GRAPH_WIDTH gpuFrameHeight
#define FRAMERATE_GRAPH_MIN_Y 20
#define FRAMERATE_GRAPH_MAX_Y (gpuFrameWidth - 10)
#define AT(x,y) ((x)*(gpuFramebufferScanlineStrideBytes>>1)+(y))
#else
#define FRAMERATE_GRAPH_WIDTH gpuFrameWidth
#define FRAMERATE_GRAPH_MIN_Y 20
#define FRAMERATE_GRAPH_MAX_Y (gpuFrameHeight - 10)
#define AT(x,y) ((y)*(gpuFramebufferScanlineStrideBytes>>1)+(x))
#endif
  for(int i = 0; i < MIN(statsFrameIntervalsSize, FRAMERATE_GRAPH_WIDTH); ++i)
  {
    int x = FRAMERATE_GRAPH_WIDTH-1-i;
    int y = statsFrameIntervalsY[i];
    framebuffer[AT(x, FRAMERATE_GRAPH_MIN_Y)] = RGB565(31,0,0);
    framebuffer[AT(x, FRAMERATE_GRAPH_MIN_Y+1)] = RGB565(0,0,0);
    framebuffer[AT(x, statsTargetFrameRateY-1)] = RGB565(0,0,0);
    framebuffer[AT(x, statsTargetFrameRateY)] = RGB565(0,63,0);
    framebuffer[AT(x, statsTargetFrameRateY+1)] = RGB565(0,0,0);
    framebuffer[AT(x, statsAvgFrameRateIntervalY-1)] = RGB565(0,0,0);
    framebuffer[AT(x, statsAvgFrameRateIntervalY)] = RGB565(29,50,7);
    framebuffer[AT(x, statsAvgFrameRateIntervalY+1)] = RGB565(0,0,0);
    framebuffer[AT(x, y-3)] = RGB565(0,0,0);
    framebuffer[AT(x, y-2)] = RGB565(0,0,0);
    framebuffer[AT(x, y-1)] = RGB565(5,11,5);
    framebuffer[AT(x, y)] = RGB565(31,63,31);
    framebuffer[AT(x, y+1)] = RGB565(5,11,5);
    framebuffer[AT(x, y+2)] = RGB565(0,0,0);
    framebuffer[AT(x, y+3)] = RGB565(0,0,0);
    framebuffer[AT(x, FRAMERATE_GRAPH_MAX_Y-1)] = RGB565(0,0,0);
    framebuffer[AT(x, FRAMERATE_GRAPH_MAX_Y)] = RGB565(15,30,15);
  }
#endif
}

void RefreshStatisticsOverlayText()
{
  uint64_t now = tick();
  uint64_t elapsed = now - statsLastPrint;
  if (elapsed < STATISTICS_REFRESH_INTERVAL) return;

#ifdef FRAME_COMPLETION_TIME_STATISTICS
  if (frameCompletionTimeHistorySize > 1)
  {
    uint64_t maxInterval = 4000000 / TARGET_FRAME_RATE;
    uint64_t accumIntervals = 0;
    for(int i = 0; i < frameCompletionTimeHistorySize-1; ++i)
    {
      uint64_t interval = MIN(frameCompletionTimeHistory[i] - frameCompletionTimeHistory[i+1], maxInterval);
      accumIntervals += interval;
      statsFrameIntervalsY[i] = FRAMERATE_GRAPH_MAX_Y - (FRAMERATE_GRAPH_MAX_Y - FRAMERATE_GRAPH_MIN_Y) * interval / maxInterval;
    }
    statsTargetFrameRateY = FRAMERATE_GRAPH_MAX_Y - (FRAMERATE_GRAPH_MAX_Y - FRAMERATE_GRAPH_MIN_Y) * (1000000/TARGET_FRAME_RATE) / maxInterval;
    statsAvgFrameRateIntervalY = FRAMERATE_GRAPH_MAX_Y - (FRAMERATE_GRAPH_MAX_Y - FRAMERATE_GRAPH_MIN_Y) * (accumIntervals / (frameCompletionTimeHistorySize-1)) / maxInterval;
    statsFrameIntervalsSize = frameCompletionTimeHistorySize-1;
  }
  else
    statsFrameIntervalsSize = 0;
#endif

  UpdateStatisticsNumbers();

#ifdef USE_DMA_TRANSFERS
  sprintf(dmaChannelsText, "DMATx=%d,Rx=%d", dmaTxChannel, dmaRxChannel);
#endif
#ifdef KERNEL_MODULE_CLIENT
  spiThreadUtilizationRate = 0; // TODO
  int spiRate = 0;
  strcpy(spiUsagePercentageText, "N/A");
#else
  uint64_t spiThreadIdleFor = __atomic_load_n(&spiThreadIdleUsecs, __ATOMIC_RELAXED);
  __sync_fetch_and_sub(&spiThreadIdleUsecs, spiThreadIdleFor);
  if (__atomic_load_n(&spiThreadSleeping, __ATOMIC_RELAXED)) spiThreadIdleFor += tick() - spiThreadSleepStartTime;
  spiThreadUtilizationRate = MIN(1.0, MAX(0.0, 1.0 - spiThreadIdleFor / (double)STATISTICS_REFRESH_INTERVAL));
  int spiRate = (int)MIN(100, (spiThreadUtilizationRate*100.0));
  sprintf(spiUsagePercentageText, "%d%%", spiRate);
#endif
  spiBusDataRate = (double)8.0 * statsBytesTransferred * 1000.0 / (elapsed / 1000.0);

  if (spiRate < 90) spiUsageColor = RGB565(0,63,0);
  else if (spiRate < 100) spiUsageColor = RGB565(31,63,0);
  else spiUsageColor = RGB565(31,0, 0);

  if (spiBusDataRate > 1000000) sprintf(spiBusDataRateText, "%.2fmbps", spiBusDataRate/1000000.0);
  else if (spiBusDataRate > 1000) sprintf(spiBusDataRateText, "%.2fkbps", spiBusDataRate/1000.0);
  else sprintf(spiBusDataRateText, "%.2fbps", spiBusDataRate);

  uint64_t wastedTime = __atomic_load_n(&timeWastedPollingGPU, __ATOMIC_RELAXED);
  __atomic_fetch_sub(&timeWastedPollingGPU, wastedTime, __ATOMIC_RELAXED);
  //const double gpuPollingWastedScalingFactor = 0.369; // A crude heuristic to scale time spent in useless polling to what Linux 'top' tool shows as % usage percentages
  statsGpuPollingWasted = (int)(wastedTime /** gpuPollingWastedScalingFactor*/ * 100 / (now - statsLastPrint));

  statsBytesTransferred = 0;

  if (statsBcmCoreSpeed > 0 && statsCpuFrequency > 0) sprintf(spiSpeedText, "%d/%dMHz", statsCpuFrequency, statsBcmCoreSpeed);
  else spiSpeedText[0] = '\0';

  if (statsSpiBusSpeed > 0) sprintf(spiSpeedText2, "SPI:%.3fMHz (/%d)", statsSpiBusSpeed, spi->clk);
  else spiSpeedText2[0] = '\0';

  if (statsCpuTemperature > 0)
  {
    sprintf(cpuTemperatureText, "%.1fc", statsCpuTemperature);
    if (statsCpuTemperature >= 80) cpuTemperatureColor = RGB565(31, 0, 0);
    else if (statsCpuTemperature >= 65) cpuTemperatureColor = RGB565(31, 63, 0);
    else cpuTemperatureColor = RGB565(0, 63, 0);
  }

  if (statsGpuPollingWasted > 0)
  {
    gpuPollingWastedColor = (statsGpuPollingWasted > 5) ? RGB565(31, 0, 0) : RGB565(31, 63, 0);
    sprintf(gpuPollingWastedText, "+%d%%", statsGpuPollingWasted);
  }
  else gpuPollingWastedText[0] = '\0';

  statsLastPrint = now;

  if (frameTimeHistorySize >= 3)
  {
    int numInterlacedFramesInHistory = 0;
    int numProgressiveFramesInHistory = 0;
    for(int i = 0; i < frameTimeHistorySize; ++i)
      if (frameTimeHistory[i].interlaced)
        ++numInterlacedFramesInHistory;
      else
        ++numProgressiveFramesInHistory;

    int frames = frameTimeHistorySize;
    if (numInterlacedFramesInHistory)
      frames += numProgressiveFramesInHistory; // Progressive frames count twice as interlaced
    int fps = (0.5 + (frames - 1) * 1000000.0 / (frameTimeHistory[frameTimeHistorySize-1].time - frameTimeHistory[0].time));
#ifdef NO_INTERLACING
    sprintf(fpsText, "%d", fps);
    fpsColor = 0xFFFF;
#else
    if (numInterlacedFramesInHistory > 0)
    {
      if (numProgressiveFramesInHistory > 0) sprintf(fpsText, "%di/%d", fps, numProgressiveFramesInHistory);
      else sprintf(fpsText, "%di", fps);
      fpsColor = RGB565(31, 30, 11);
    }
    else
    {
      sprintf(fpsText, "%dp", fps);
      fpsColor = 0xFFFF;
    }
#endif
    if (frameSkipTimeHistorySize > 0) sprintf(statsFrameSkipText, "-%d", frameSkipTimeHistorySize);
    else statsFrameSkipText[0] = '\0';
  }
  else
  {
    strcpy(fpsText, "-");
    statsFrameSkipText[0] = '\0';
    fpsColor = 0xFFFF;
  }

#if (defined(DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE) && DISPLAY_DRAWABLE_HEIGHT > 302) || (!defined(DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE) && DISPLAY_DRAWABLE_WIDTH > 302)
#define HINTSUFFIX "MB"
#else
#define HINTSUFFIX ""
#endif

  sprintf(cpuMemoryUsedText, "CPU:%.2f" HINTSUFFIX, totalCpuMemoryAllocated/1024.0/1024.0);

#ifdef USE_DMA_TRANSFERS
  if (totalGpuMemoryUsed > 0)
    sprintf(gpuMemoryUsedText, "GPU:%.2f" HINTSUFFIX, totalGpuMemoryUsed/1024.0/1024.0);
#endif
}
#else
void RefreshStatisticsOverlayText() {}
void DrawStatisticsOverlay(uint16_t *) {}
#endif // ~STATISTICS
