/*
htop - Row.c
(C) 2004-2015 Hisham H. Muhammad
(C) 2020-2023 Red Hat, Inc.  All Rights Reserved.
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "config.h" // IWYU pragma: keep

#include "Row.h"

#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "CRT.h"
#include "DynamicColumn.h"
#include "Hashtable.h"
#include "Machine.h"
#include "Macros.h"
#include "Process.h"
#include "RichString.h"
#include "Settings.h"
#include "XUtils.h"


int Row_pidDigits = ROW_MIN_PID_DIGITS;
int Row_uidDigits = ROW_MIN_UID_DIGITS;

void Row_init(Row* this, const Machine* host) {
   this->host = host;
   this->tag = false;
   this->showChildren = true;
   this->show = true;
   this->wasShown = false;
   this->updated = false;
}

void Row_done(Row* this) {
   assert(this != NULL);
   (void) this;
}

static inline bool Row_isNew(const Row* this) {
   const Machine* host = this->host;
   if (host->monotonicMs < this->seenStampMs)
      return false;

   const Settings* settings = host->settings;
   return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs;
}

static inline bool Row_isTomb(const Row* this) {
   return this->tombStampMs > 0;
}

void Row_display(const Object* cast, RichString* out) {
   const Row* this = (const Row*) cast;
   const Settings* settings = this->host->settings;
   const RowField* fields = settings->ss->fields;

   for (int i = 0; fields[i]; i++)
      As_Row(this)->writeField(this, out, fields[i]);

   if (Row_isHighlighted(this))
      RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);

   if (this->tag == true)
      RichString_setAttr(out, CRT_colors[PROCESS_TAG]);

   if (settings->highlightChanges) {
      if (Row_isTomb(this))
         out->highlightAttr = CRT_colors[PROCESS_TOMB];
      else if (Row_isNew(this))
         out->highlightAttr = CRT_colors[PROCESS_NEW];
   }

   assert(RichString_size(out) > 0);
}

void Row_setPidColumnWidth(pid_t maxPid) {
   if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) {
      Row_pidDigits = ROW_MIN_PID_DIGITS;
      return;
   }

   Row_pidDigits = (int)countDigits((size_t)maxPid, 10);
   assert(Row_pidDigits <= ROW_MAX_PID_DIGITS);
}

void Row_setUidColumnWidth(uid_t maxUid) {
   if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) {
      Row_uidDigits = ROW_MIN_UID_DIGITS;
      return;
   }

   Row_uidDigits = (int)countDigits((size_t)maxUid, 10);
   assert(Row_uidDigits <= ROW_MAX_UID_DIGITS);
}

uint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 };

void Row_resetFieldWidths(void) {
   for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
      if (!Process_fields[i].autoWidth)
         continue;

      size_t len = strlen(Process_fields[i].title);
      assert(len <= UINT8_MAX);
      Row_fieldWidths[i] = (uint8_t)len;
   }
}

void Row_updateFieldWidth(RowField key, size_t width) {
   if (width > UINT8_MAX)
      Row_fieldWidths[key] = UINT8_MAX;
   else if (width > Row_fieldWidths[key])
      Row_fieldWidths[key] = (uint8_t)width;
}

// helper function to fill an aligned title string for a dynamic column
static const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) {
   const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);
   if (column == NULL)
      return "- ";

   int width = column->width;
   if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)
      width = DYNAMIC_DEFAULT_COLUMN_WIDTH;

   xSnprintf(titleBuffer, titleBufferSize, "%*s ", width, column->heading);
   return titleBuffer;
}

// helper function to fill an aligned title string for a process field
static const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) {
   const char* title = Process_fields[field].title;
   if (!title)
      return "- ";

   if (Process_fields[field].pidColumn) {
      xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_pidDigits, title);
      return titleBuffer;
   }

   if (field == ST_UID) {
      xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_uidDigits, title);
      return titleBuffer;
   }

   if (Process_fields[field].autoWidth) {
      if (Process_fields[field].autoTitleRightAlign)
         xSnprintf(titleBuffer, titleBufferSize, "%*s ", Row_fieldWidths[field], title);
      else
         xSnprintf(titleBuffer, titleBufferSize, "%-*.*s ", Row_fieldWidths[field], Row_fieldWidths[field], title);
      return titleBuffer;
   }

   return title;
}

// helper function to create an aligned title string for a given field
const char* RowField_alignedTitle(const Settings* settings, RowField field) {
   static char titleBuffer[UINT8_MAX + sizeof(" ")];
   assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(" "));
   assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(" "));
   assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(" "));

   if (field < LAST_PROCESSFIELD)
      return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer));
   return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer));
}

RowField RowField_keyAt(const Settings* settings, int at) {
   const RowField* fields = settings->ss->fields;
   RowField field;
   int rem = at;
   for (int i = 0; (field = fields[i]); i++) {
      int len = rem > 0 ? (int)strnlen(RowField_alignedTitle(settings, field), rem) : 0;
      if (rem <= len) {
         return field;
      }
      rem -= len;
   }
   return COMM;
}

void Row_printKBytes(RichString* str, unsigned long long number, bool coloring) {
   char buffer[16];
   int len;

   int color = CRT_colors[PROCESS];
   int nextUnitColor = CRT_colors[PROCESS];

   const int colors[4] = {
      [0] = CRT_colors[PROCESS],
      [1] = CRT_colors[PROCESS_MEGABYTES],
      [2] = CRT_colors[PROCESS_GIGABYTES],
      [3] = CRT_colors[LARGE_NUMBER]
   };

   if (number == ULLONG_MAX)
      goto invalidNumber;

   if (coloring) {
      color = colors[0];
      nextUnitColor = colors[1];
   }

   if (number < 1000) {
      // Plain number, no markings
      len = xSnprintf(buffer, sizeof(buffer), "%5u ", (unsigned int)number);
      RichString_appendnAscii(str, color, buffer, len);
      return;
   }

   if (number < 100000) {
      // 2 digits for M, 3 digits for K
      len = xSnprintf(buffer, sizeof(buffer), "%2u", (unsigned int)(number / 1000));
      RichString_appendnAscii(str, nextUnitColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%03u ", (unsigned int)(number % 1000));
      RichString_appendnAscii(str, color, buffer, len);
      return;
   }

   // 100000 KiB (97.6 MiB) or greater. A unit prefix would be added.
   const size_t maxUnitIndex = (sizeof(number) * CHAR_BIT - 1) / 10 + 1;
   const bool canOverflow = maxUnitIndex >= ARRAYSIZE(unitPrefixes);

   size_t i = 1;
   int prevUnitColor;
   // Convert KiB to (1/100) of MiB
   unsigned long long hundredths = (number / 256) * 25 + (number % 256) * 25 / 256;
   while (true) {
      if (canOverflow && i >= ARRAYSIZE(unitPrefixes))
         goto invalidNumber;

      prevUnitColor = color;
      color = nextUnitColor;

      if (coloring && i + 1 < ARRAYSIZE(colors))
         nextUnitColor = colors[i + 1];

      if (hundredths < 1000000)
         break;

      hundredths /= ONE_K;
      i++;
   }

   number = hundredths / 100;
   hundredths %= 100;
   if (number < 100) {
      len = xSnprintf(buffer, sizeof(buffer), "%u", (unsigned int)number);
      RichString_appendnAscii(str, color, buffer, len);
      if (number < 10) {
         // 1 digit + decimal point + 2 digits
         // "9.76G", "9.99G", "9.76T", "9.99T", etc.
         len = xSnprintf(buffer, sizeof(buffer), ".%02u", (unsigned int)hundredths);
      } else {
         // 2 digits + decimal point + 1 digit
         // "97.6M", "99.9M", "10.0G", "99.9G", etc.
         len = xSnprintf(buffer, sizeof(buffer), ".%u", (unsigned int)hundredths / 10);
      }
      RichString_appendnAscii(str, prevUnitColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%c ", unitPrefixes[i]);
   } else if (number < 1000) {
      // 3 digits
      // "100M", "999M", "100G", "999G", etc.
      len = xSnprintf(buffer, sizeof(buffer), "%4u%c ", (unsigned int)number, unitPrefixes[i]);
   } else {
      // 1 digit + 3 digits
      // "1000M", "9999M", "1000G", "9999G", etc.
      assert(number < 10000);

      len = xSnprintf(buffer, sizeof(buffer), "%u", (unsigned int)number / 1000);
      RichString_appendnAscii(str, nextUnitColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%03u%c ", (unsigned int)number % 1000, unitPrefixes[i]);
   }
   RichString_appendnAscii(str, color, buffer, len);
   return;

invalidNumber:
   if (coloring)
      color = CRT_colors[PROCESS_SHADOW];

   RichString_appendAscii(str, color, "  N/A ");
}

void Row_printBytes(RichString* str, unsigned long long number, bool coloring) {
   if (number == ULLONG_MAX)
      Row_printKBytes(str, ULLONG_MAX, coloring);
   else
      Row_printKBytes(str, number / ONE_K, coloring);
}

void Row_printCount(RichString* str, unsigned long long number, bool coloring) {
   char buffer[13];

   int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
   int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
   int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
   int baseColor = CRT_colors[PROCESS];

   if (number == ULLONG_MAX) {
      RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], "        N/A ");
   } else if (number >= 100000LL * ONE_DECIMAL_T) {
      xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G);
      RichString_appendnAscii(str, largeNumberColor, buffer, 12);
   } else if (number >= 100LL * ONE_DECIMAL_T) {
      xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M);
      RichString_appendnAscii(str, largeNumberColor, buffer, 8);
      RichString_appendnAscii(str, megabytesColor, buffer + 8, 4);
   } else if (number >= 10LL * ONE_DECIMAL_G) {
      xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K);
      RichString_appendnAscii(str, largeNumberColor, buffer, 5);
      RichString_appendnAscii(str, megabytesColor, buffer + 5, 3);
      RichString_appendnAscii(str, baseColor, buffer + 8, 4);
   } else {
      xSnprintf(buffer, sizeof(buffer), "%11llu ", number);
      RichString_appendnAscii(str, largeNumberColor, buffer, 2);
      RichString_appendnAscii(str, megabytesColor, buffer + 2, 3);
      RichString_appendnAscii(str, baseColor, buffer + 5, 3);
      RichString_appendnAscii(str, shadowColor, buffer + 8, 4);
   }
}

void Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
   char buffer[10];
   int len;

   if (totalHundredths == 0) {
      int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];

      RichString_appendAscii(str, shadowColor, " 0:00.00 ");
      return;
   }

   int yearColor = coloring ? CRT_colors[LARGE_NUMBER]      : CRT_colors[PROCESS];
   int dayColor  = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
   int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
   int baseColor = CRT_colors[PROCESS];

   unsigned long long totalSeconds = totalHundredths / 100;
   unsigned long long totalMinutes = totalSeconds / 60;
   unsigned long long totalHours = totalMinutes / 60;
   unsigned int seconds = totalSeconds % 60;
   unsigned int minutes = totalMinutes % 60;

   if (totalMinutes < 60) {
      unsigned int hundredths = totalHundredths % 100;
      len = xSnprintf(buffer, sizeof(buffer), "%2u:%02u.%02u ", (unsigned int)totalMinutes, seconds, hundredths);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }
   if (totalHours < 24) {
      len = xSnprintf(buffer, sizeof(buffer), "%2uh", (unsigned int)totalHours);
      RichString_appendnAscii(str, hourColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%02u:%02u ", minutes, seconds);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }

   unsigned long long totalDays = totalHours / 24;
   unsigned int hours = totalHours % 24;
   if (totalDays < 10) {
      len = xSnprintf(buffer, sizeof(buffer), "%ud", (unsigned int)totalDays);
      RichString_appendnAscii(str, dayColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%02uh", hours);
      RichString_appendnAscii(str, hourColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%02um ", minutes);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }
   if (totalDays < /* Ignore leap years */365) {
      len = xSnprintf(buffer, sizeof(buffer), "%4ud", (unsigned int)totalDays);
      RichString_appendnAscii(str, dayColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%02uh ", hours);
      RichString_appendnAscii(str, hourColor, buffer, len);
      return;
   }

   unsigned long long years = totalDays / 365;
   unsigned int days = totalDays % 365;
   if (years < 1000) {
      len = xSnprintf(buffer, sizeof(buffer), "%3uy", (unsigned int)years);
      RichString_appendnAscii(str, yearColor, buffer, len);
      len = xSnprintf(buffer, sizeof(buffer), "%03ud ", days);
      RichString_appendnAscii(str, dayColor, buffer, len);
   } else if (years < 10000000) {
      len = xSnprintf(buffer, sizeof(buffer), "%7luy ", (unsigned long)years);
      RichString_appendnAscii(str, yearColor, buffer, len);
   } else {
      RichString_appendAscii(str, yearColor, "eternity ");
   }
}

void Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring) {
   if (totalNanoseconds == 0) {
      int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];

      RichString_appendAscii(str, shadowColor, "     0ns ");
      return;
   }

   char buffer[10];
   int len;
   int baseColor = CRT_colors[PROCESS];

   if (totalNanoseconds < 1000000) {
      len = xSnprintf(buffer, sizeof(buffer), "%6luns ", (unsigned long)totalNanoseconds);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }

   if (totalNanoseconds < 10000000) {
      // The precision is 0.1 microseconds here.
      // We print the unit in "ms" rather than microseconds in order
      // to save the code of choosing the Greek "mu" or Latin "u".
      uint32_t fraction = (uint32_t)totalNanoseconds / 100;
      unsigned int milliseconds = (unsigned int)(fraction / 10000);
      fraction %= 10000;
      len = xSnprintf(buffer, sizeof(buffer), "%u.%04ums ", milliseconds, (unsigned int)fraction);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }

   unsigned long long totalMicroseconds = totalNanoseconds / 1000;
   unsigned long long totalSeconds = totalMicroseconds / 1000000;
   uint32_t microseconds = totalMicroseconds % 1000000;
   if (totalSeconds < 60) {
      int width = 6;
      uint32_t fraction = microseconds;
      for (unsigned long long limit = 1; totalSeconds >= limit; limit *= 10) {
         width--;
         fraction /= 10;
      }
      // "%.u" prints no digits if (totalSeconds == 0).
      len = xSnprintf(buffer, sizeof(buffer), "%.u.%0*lus ", (unsigned int)totalSeconds, width, (unsigned long)fraction);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }

   if (totalSeconds < 600) {
      unsigned int minutes = (unsigned int)totalSeconds / 60;
      unsigned int seconds = (unsigned int)totalSeconds % 60;
      unsigned int milliseconds = (unsigned int)(microseconds / 1000);
      len = xSnprintf(buffer, sizeof(buffer), "%u:%02u.%03u ", minutes, seconds, milliseconds);
      RichString_appendnAscii(str, baseColor, buffer, len);
      return;
   }

   unsigned long long totalHundredths = totalMicroseconds / 1000 / 10;
   Row_printTime(str, totalHundredths, coloring);
}

void Row_printRate(RichString* str, double rate, bool coloring) {
   char buffer[16];

   int largeNumberColor = CRT_colors[LARGE_NUMBER];
   int megabytesColor = CRT_colors[PROCESS_MEGABYTES];
   int shadowColor = CRT_colors[PROCESS_SHADOW];
   int baseColor = CRT_colors[PROCESS];

   if (!coloring) {
      largeNumberColor = CRT_colors[PROCESS];
      megabytesColor = CRT_colors[PROCESS];
   }

   if (!isNonnegative(rate)) {
      RichString_appendAscii(str, shadowColor, "        N/A ");
      return;
   }

   size_t i = 0;
   double scaled = rate;
   while (scaled >= ONE_K && i < ARRAYSIZE(unitPrefixes)) {
      scaled /= ONE_K;
      i++;
   }

   int color = baseColor;
   if (rate < 0.005)
      color = shadowColor;
   else if (i == 2)
      color = megabytesColor;
   else if (i >= 3)
      color = largeNumberColor;

   char prefix = (i == 0) ? 'B' : unitPrefixes[i - 1];
   int len = xSnprintf(buffer, sizeof(buffer), "%7.2f %c/s ", scaled, prefix);
   RichString_appendnAscii(str, color, buffer, len);
}

void Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
   int columns = width;
   RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
   RichString_appendChr(str, attr, ' ', width + 1 - columns);
}

int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) {
   assert(n >= 6 && width >= 4 && "Invalid width in Row_printPercentage()");
   // truncate in favour of abort in xSnprintf()
   width = (uint8_t)CLAMP(width, 4, n - 2);
   assert(width < n - 1 && "Insufficient space to print column");

   if (isNonnegative(val)) {
      if (val < 0.05F)
         *attr = CRT_colors[PROCESS_SHADOW];
      else if (val >= 99.9F)
         *attr = CRT_colors[PROCESS_MEGABYTES];

      int precision = 1;

      // Display "val" as "100" for columns like "MEM%".
      if (width == 4 && val > 99.9F) {
         precision = 0;
         val = 100.0F;
      }

      return xSnprintf(buffer, n, "%*.*f ", width, precision, val);
   }

   *attr = CRT_colors[PROCESS_SHADOW];
   return xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
}

void Row_toggleTag(Row* this) {
   this->tag = !this->tag;
}

int Row_compare(const void* v1, const void* v2) {
   const Row* r1 = (const Row*)v1;
   const Row* r2 = (const Row*)v2;

   return SPACESHIP_NUMBER(r1->id, r2->id);
}

int Row_compareByParent_Base(const void* v1, const void* v2) {
   const Row* r1 = (const Row*)v1;
   const Row* r2 = (const Row*)v2;

   int result = SPACESHIP_NUMBER(
      r1->isRoot ? 0 : Row_getGroupOrParent(r1),
      r2->isRoot ? 0 : Row_getGroupOrParent(r2)
   );

   if (result != 0)
      return result;

   return Row_compare(v1, v2);
}

const RowClass Row_class = {
   .super = {
      .extends = Class(Object),
      .compare = Row_compare
   },
};
