/*
htop - FileDescriptorMeter.c
(C) 2022 htop dev team
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

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

#include "FileDescriptorMeter.h"

#include <math.h>

#include "CRT.h"
#include "Macros.h"
#include "Meter.h"
#include "Object.h"
#include "Platform.h"
#include "RichString.h"
#include "XUtils.h"


#define FD_EFFECTIVE_UNLIMITED(x) (!isgreaterequal((double)(1<<30), (x)))

static const int FileDescriptorMeter_attributes[] = {
   FILE_DESCRIPTOR_USED,
   FILE_DESCRIPTOR_MAX
};

static void FileDescriptorMeter_updateValues(Meter* this) {
   this->values[0] = 0;
   this->values[1] = 1;

   Platform_getFileDescriptors(&this->values[0], &this->values[1]);

   /* only print bar for first value */
   this->curItems = 1;

   /* Use maximum value for scaling of bar mode
    *
    * As the plain total value can be very large compared to
    * the actually used value, this is capped in the following way:
    *
    * 1. If the maximum value is below (or equal to) 1<<16, use it directly
    * 2. If the maximum value is above, use powers of 2 starting at 1<<16 and
    *    double it until it's larger than 16 times the used file handles
    *    (capped at the maximum number of files)
    * 3. If the maximum is effectively unlimited (AKA > 1<<30),
    *    Do the same as for 2, but cap at 1<<30.
    */
   if (this->values[1] <= 1 << 16) {
      this->total = this->values[1];
   } else {
      if (this->total < 16 * this->values[0]) {
         for (this->total = 1 << 16; this->total < 16 * this->values[0]; this->total *= 2) {
            if (this->total >= 1 << 30) {
               break;
            }
         }
      }

      if (this->total > this->values[1]) {
         this->total = this->values[1];
      }

      if (this->total > 1 << 30) {
         this->total = 1 << 30;
      }
   }

   if (!isNonnegative(this->values[0])) {
      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "unknown/unknown");
   } else if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/unlimited", this->values[0]);
   } else {
      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.0lf/%.0lf", this->values[0], this->values[1]);
   }
}

static void FileDescriptorMeter_display(const Object* cast, RichString* out) {
   const Meter* this = (const Meter*)cast;
   char buffer[50];
   int len;

   if (!isNonnegative(this->values[0])) {
      RichString_appendAscii(out, CRT_colors[METER_TEXT], "unknown");
      return;
   }

   RichString_appendAscii(out, CRT_colors[METER_TEXT], "used: ");
   len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[0]);
   RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_USED], buffer, len);

   RichString_appendAscii(out, CRT_colors[METER_TEXT], " max: ");
   if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {
      RichString_appendAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], "unlimited");
   } else {
      len = xSnprintf(buffer, sizeof(buffer), "%.0lf", this->values[1]);
      RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], buffer, len);
   }
}

const MeterClass FileDescriptorMeter_class = {
   .super = {
      .extends = Class(Meter),
      .delete = Meter_delete,
      .display = FileDescriptorMeter_display,
   },
   .updateValues = FileDescriptorMeter_updateValues,
   .defaultMode = TEXT_METERMODE,
   .supportedModes = METERMODE_DEFAULT_SUPPORTED,
   .maxItems = 2,
   .isPercentChart = false,
   .total = 65536.0,
   .attributes = FileDescriptorMeter_attributes,
   .name = "FileDescriptors",
   .uiName = "File Descriptors",
   .caption = "FDs: ",
   .description = "Number of allocated/available file descriptors"
};
