/*
htop - OptionItem.c
(C) 2004-2011 Hisham H. Muhammad
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

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

#include "OptionItem.h"

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "CRT.h"
#include "Macros.h"
#include "RichString.h"
#include "XUtils.h"


static void OptionItem_delete(Object* cast) {
   OptionItem* this = (OptionItem*)cast;
   assert(this != NULL);

   free(this->text);
   free(this);
}

static void TextItem_display(const Object* cast, RichString* out) {
   const TextItem* this = (const TextItem*)cast;
   assert(this != NULL);

   RichString_appendWide(out, CRT_colors[HELP_BOLD], this->super.text);
}

static void CheckItem_display(const Object* cast, RichString* out) {
   const CheckItem* this = (const CheckItem*)cast;
   assert(this != NULL);

   RichString_writeAscii(out, CRT_colors[CHECK_BOX], "[");
   if (CheckItem_get(this)) {
      RichString_appendAscii(out, CRT_colors[CHECK_MARK], "x");
   } else {
      RichString_appendAscii(out, CRT_colors[CHECK_MARK], " ");
   }
   RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]    ");
   RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);
}

static void NumberItem_display(const Object* cast, RichString* out) {
   const NumberItem* this = (const NumberItem*)cast;
   assert(this != NULL);

   char buffer[12];
   RichString_writeAscii(out, CRT_colors[CHECK_BOX], "[");
   int written;
   if (this->editing) {
      written = this->editLen;
      RichString_appendnAscii(out, CRT_colors[CHECK_MARK], this->editBuffer, written);
   } else if (this->scale < 0) {
      written = xSnprintf(buffer, sizeof(buffer), "%.*f", -this->scale, pow(10, this->scale) * NumberItem_get(this));
      RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);
   } else if (this->scale > 0) {
      written = xSnprintf(buffer, sizeof(buffer), "%d", (int) (pow(10, this->scale) * NumberItem_get(this)));
      RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);
   } else {
      written = xSnprintf(buffer, sizeof(buffer), "%d", NumberItem_get(this));
      RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);
   }
   RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]");
   for (int i = written; i < 5; i++) {
      RichString_appendAscii(out, CRT_colors[CHECK_BOX], " ");
   }
   RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);
}

const OptionItemClass OptionItem_class = {
   .super = {
      .extends = Class(Object),
      .delete = OptionItem_delete
   }
};

const OptionItemClass TextItem_class = {
   .super = {
      .extends = Class(OptionItem),
      .delete = OptionItem_delete,
      .display = TextItem_display
   },
   .kind = OPTION_ITEM_TEXT
};


const OptionItemClass CheckItem_class = {
   .super = {
      .extends = Class(OptionItem),
      .delete = OptionItem_delete,
      .display = CheckItem_display
   },
   .kind = OPTION_ITEM_CHECK
};


const OptionItemClass NumberItem_class = {
   .super = {
      .extends = Class(OptionItem),
      .delete = OptionItem_delete,
      .display = NumberItem_display
   },
   .kind = OPTION_ITEM_NUMBER
};

TextItem* TextItem_new(const char* text) {
   TextItem* this = AllocThis(TextItem);
   this->super.text = xStrdup(text);
   return this;
}

CheckItem* CheckItem_newByRef(const char* text, bool* ref) {
   CheckItem* this = AllocThis(CheckItem);
   this->super.text = xStrdup(text);
   this->value = false;
   this->ref = ref;
   return this;
}

CheckItem* CheckItem_newByVal(const char* text, bool value) {
   CheckItem* this = AllocThis(CheckItem);
   this->super.text = xStrdup(text);
   this->value = value;
   this->ref = NULL;
   return this;
}

bool CheckItem_get(const CheckItem* this) {
   if (this->ref) {
      return *(this->ref);
   } else {
      return this->value;
   }
}

void CheckItem_set(CheckItem* this, bool value) {
   if (this->ref) {
      *(this->ref) = value;
   } else {
      this->value = value;
   }
}

void CheckItem_toggle(CheckItem* this) {
   if (this->ref) {
      *(this->ref) = !*(this->ref);
   } else {
      this->value = !this->value;
   }
}

NumberItem* NumberItem_newByRef(const char* text, int* ref, int scale, int min, int max) {
   assert(min <= max);

   NumberItem* this = AllocThis(NumberItem);
   this->super.text = xStrdup(text);
   this->value = 0;
   this->ref = ref;
   this->scale = scale;
   this->min = min;
   this->max = max;
   this->editing = false;
   this->editLen = 0;
   this->editBuffer[0] = '\0';
   this->savedValue = 0;
   return this;
}

NumberItem* NumberItem_newByVal(const char* text, int value, int scale, int min, int max) {
   assert(min <= max);

   NumberItem* this = AllocThis(NumberItem);
   this->super.text = xStrdup(text);
   this->value = CLAMP(value, min, max);
   this->ref = NULL;
   this->scale = scale;
   this->min = min;
   this->max = max;
   this->editing = false;
   this->editLen = 0;
   this->editBuffer[0] = '\0';
   this->savedValue = 0;
   return this;
}

int NumberItem_get(const NumberItem* this) {
   if (this->ref) {
      return *(this->ref);
   } else {
      return this->value;
   }
}

void NumberItem_decrease(NumberItem* this) {
   if (this->ref) {
      *(this->ref) = CLAMP(*(this->ref) - 1, this->min, this->max);
   } else {
      this->value = CLAMP(this->value - 1, this->min, this->max);
   }
}

void NumberItem_increase(NumberItem* this) {
   if (this->ref) {
      *(this->ref) = CLAMP(*(this->ref) + 1, this->min, this->max);
   } else {
      this->value = CLAMP(this->value + 1, this->min, this->max);
   }
}

void NumberItem_toggle(NumberItem* this) {
   if (this->ref) {
      if (*(this->ref) >= this->max)
         *(this->ref) = this->min;
      else
         *(this->ref) += 1;
   } else {
      if (this->value >= this->max)
         this->value = this->min;
      else
         this->value += 1;
   }
}

void NumberItem_startEditing(NumberItem* this) {
   this->savedValue = NumberItem_get(this);
   this->editing = true;
   this->editLen = 0;
   this->editBuffer[0] = '\0';
}

void NumberItem_startEditingFromValue(NumberItem* this) {
   this->savedValue = NumberItem_get(this);
   this->editing = true;
   char tmp[NUMBERITEM_EDIT_MAX + 1];
   int len;
   /* Format the initial edit buffer using scale for decimal display */
   if (this->scale < 0) {
      len = xSnprintf(tmp, sizeof(tmp), "%.*f", -this->scale, pow(10, this->scale) * this->savedValue);
   } else {
      len = xSnprintf(tmp, sizeof(tmp), "%d", this->savedValue);
   }
   this->editLen = MINIMUM(len, NUMBERITEM_EDIT_MAX);
   memcpy(this->editBuffer, tmp, this->editLen);
   this->editBuffer[this->editLen] = '\0';
}

void NumberItem_cancelEditing(NumberItem* this) {
   this->editing = false;
   this->editLen = 0;
   this->editBuffer[0] = '\0';
}

bool NumberItem_applyEditing(NumberItem* this) {
   this->editing = false;
   if (this->editLen == 0) {
      this->editBuffer[0] = '\0';
      return false;
   }
   this->editBuffer[this->editLen] = '\0';
   int newValue;
   char* endptr;
   if (this->scale < 0) {
      double displayValue = strtod(this->editBuffer, &endptr);
      if (endptr == this->editBuffer) {
         this->editLen = 0;
         this->editBuffer[0] = '\0';
         return false;
      }
      newValue = (int) round(displayValue / pow(10, this->scale));
   } else {
      newValue = (int) strtol(this->editBuffer, &endptr, 10);
      if (endptr == this->editBuffer) {
         this->editLen = 0;
         this->editBuffer[0] = '\0';
         return false;
      }
   }
   newValue = CLAMP(newValue, this->min, this->max);
   if (this->ref) {
      *(this->ref) = newValue;
   } else {
      this->value = newValue;
   }
   this->editLen = 0;
   this->editBuffer[0] = '\0';
   return true;
}

bool NumberItem_addChar(NumberItem* this, char c) {
   if (c == ',') {
      c = '.';
   }
   if (c == '.') {
      if (this->scale >= 0) {
         return false;
      }
      if (memchr(this->editBuffer, '.', this->editLen) != NULL) {
         return false;
      }
   } else if (c < '0' || c > '9') {
      return false;
   }
   if (this->editLen >= NUMBERITEM_EDIT_MAX) {
      return false;
   }
   this->editBuffer[this->editLen++] = c;
   this->editBuffer[this->editLen] = '\0';
   return true;
}

void NumberItem_deleteChar(NumberItem* this) {
   if (this->editLen > 0) {
      this->editLen--;
      this->editBuffer[this->editLen] = '\0';
   }
}
