/*
 *
 * Copyright (C) 2021 Maxime Schmitt <maxime.schmitt91@gmail.com>
 *
 * This file is part of Nvtop.
 *
 * Nvtop is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Nvtop is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with nvtop.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "nvtop/interface_setup_win.h"
#include "nvtop/interface.h"
#include "nvtop/interface_internal_common.h"
#include "nvtop/interface_options.h"
#include "nvtop/interface_ring_buffer.h"

#include <ncurses.h>

static char *setup_window_category_names[setup_window_selection_count] = {"General", "Devices", "Chart", "Processes",
                                                                          "GPU Select"};

// All the windows used to display the setup
enum setup_window_type {
  setup_window_type_setup,
  setup_window_type_single,
  setup_window_type_split_left,
  setup_window_type_split_right,
  setup_window_type_count
};

// General Options

enum setup_general_options {
  setup_general_color,
  setup_general_show_startup_support_messages,
  setup_general_update_interval,
  setup_general_options_count
};

static const char *setup_general_option_description[setup_general_options_count] = {
    "Disable color (requires save and restart)", "Show support messages on startup", "Update interval (seconds)"};

// Header Options

enum setup_header_options {
  setup_header_toggle_fahrenheit,
  setup_header_enc_dec_timer,
  setup_header_gpu_info_bar,
  setup_header_options_count
};

static const char *setup_header_option_descriptions[setup_header_options_count] = {
    "Temperature in fahrenheit", "Keep displaying Encoder/Decoder rate (after reaching an idle state)",
    "Display extra GPU info bar"};

// Chart Options

enum setup_chart_options {
  setup_chart_reverse,
  setup_chart_all_gpu,
  setup_chart_start_gpu_list,
  setup_chart_options_count
};

static const char *setup_chart_options_descriptions[setup_chart_options_count] = {
    "Reverse plot direction", "Displayed all GPUs", "Displayed GPU"};

static const char *setup_chart_gpu_value_descriptions[plot_information_count] = {
    "GPU utilization rate", "GPU memory utilization rate",   "GPU encoder rate", "GPU decoder rate",
    "GPU temperature",      "Power draw rate (current/max)", "Fan speed",        "GPU clock rate",
    "GPU memory clock rate"};

// Process List Options

enum setup_proc_list_options {
  setup_proc_list_hide_nvtop_process,
  setup_proc_list_sort_ascending,
  setup_proc_list_sort_by,
  setup_proc_list_display,
  setup_proc_list_options_count
};

static const char *setup_proc_list_option_description[setup_proc_list_options_count] = {
    "Hide nvtop process", "Sort Ascending", "Sort by", "Field Displayed"};

static const char *setup_proc_list_value_descriptions[process_field_count] = {
    "Process Id",    "User name",        "Device Id", "Workload type",    "GPU usage", "Encoder usage",
    "Decoder usage", "GPU memory usage", "CPU usage", "CPU memory usage", "Command"};

static unsigned int sizeof_setup_windows[setup_window_type_count] = {[setup_window_type_setup] = 11,
                                                                     [setup_window_type_single] = 0,
                                                                     [setup_window_type_split_left] = 26,
                                                                     [setup_window_type_split_right] = 0};

// For toggle options
// Show * if on, - if partial and nothing if off
enum option_state {
  option_off,
  option_on,
  option_partially_active,
};

static char option_state_char(enum option_state state) {
  switch (state) {
  case option_on:
    return '*';
  case option_partially_active:
    return '-';
  case option_off:
    return ' ';
  default:
    return ' ';
  }
}

void alloc_setup_window(struct window_position *position, struct setup_window *setup_win) {
  setup_win->visible = false;
  setup_win->clean_space = newwin(position->sizeY, position->sizeX, position->posY, position->posX);

  sizeof_setup_windows[setup_window_type_single] = position->sizeX - sizeof_setup_windows[setup_window_type_setup] - 1;
  if (sizeof_setup_windows[setup_window_type_single] > position->sizeX)
    sizeof_setup_windows[setup_window_type_single] = 0;

  sizeof_setup_windows[setup_window_type_split_right] = position->sizeX -
                                                        sizeof_setup_windows[setup_window_type_setup] -
                                                        sizeof_setup_windows[setup_window_type_split_left] - 2;
  if (sizeof_setup_windows[setup_window_type_split_right] > position->sizeX)
    sizeof_setup_windows[setup_window_type_split_right] = 0;

  setup_win->setup =
      newwin(position->sizeY, sizeof_setup_windows[setup_window_type_setup], position->posY, position->posX);

  setup_win->single = newwin(position->sizeY, sizeof_setup_windows[setup_window_type_single], position->posY,
                             position->posX + sizeof_setup_windows[setup_window_type_setup] + 1);

  setup_win->split[0] = newwin(position->sizeY, sizeof_setup_windows[setup_window_type_split_left], position->posY,
                               position->posX + sizeof_setup_windows[setup_window_type_setup] + 1);

  setup_win->split[1] = newwin(position->sizeY, sizeof_setup_windows[setup_window_type_split_right], position->posY,
                               position->posX + sizeof_setup_windows[setup_window_type_setup] +
                                   sizeof_setup_windows[setup_window_type_split_left] + 2);
}

void free_setup_window(struct setup_window *setup_win) {
  delwin(setup_win->clean_space);
  delwin(setup_win->setup);
  delwin(setup_win->single);
  delwin(setup_win->split[0]);
  delwin(setup_win->split[1]);
}

void show_setup_window(struct nvtop_interface *interface) {
  interface->setup_win.visible = true;
  touchwin(interface->setup_win.clean_space);
  wnoutrefresh(interface->setup_win.clean_space);
  interface->setup_win.selected_section = setup_general_selected;
  interface->setup_win.indentation_level = 0;
  interface->setup_win.options_selected[0] = 0;
  interface->setup_win.options_selected[1] = 0;
}

void hide_setup_window(struct nvtop_interface *interface) { interface->setup_win.visible = false; }

static void draw_setup_window_setup(struct nvtop_interface *interface) {
  werase(interface->setup_win.setup);
  mvwprintw(interface->setup_win.setup, 0, 0, "Setup");
  mvwchgat(interface->setup_win.setup, 0, 0, sizeof_setup_windows[setup_window_type_setup], A_STANDOUT, green_color,
           NULL);
  for (enum setup_window_section category = setup_general_selected; category < setup_window_selection_count;
       ++category) {
    mvwprintw(interface->setup_win.setup, category + 1, 0, "%s", setup_window_category_names[category]);
    if (interface->setup_win.selected_section == category) {
      if (interface->setup_win.indentation_level == 0) {
        set_attribute_between(interface->setup_win.setup, category + 1, 0,
                              sizeof_setup_windows[setup_window_type_setup], A_STANDOUT, cyan_color);
      } else {
        mvwprintw(interface->setup_win.setup, category + 1, sizeof_setup_windows[setup_window_type_setup] - 1, ">");
        set_attribute_between(interface->setup_win.setup, category + 1, 0,
                              sizeof_setup_windows[setup_window_type_setup], A_BOLD, cyan_color);
      }
    }
  }
  wnoutrefresh(interface->setup_win.setup);
}

static void draw_setup_window_general(struct nvtop_interface *interface) {
  if (interface->setup_win.indentation_level > 1)
    interface->setup_win.indentation_level = 1;
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] >= setup_general_options_count)
    interface->setup_win.options_selected[0] = setup_general_options_count - 1;

  wattr_set(interface->setup_win.single, A_STANDOUT, green_color, NULL);
  mvwprintw(interface->setup_win.single, 0, 0, "General Options");
  wstandend(interface->setup_win.single);

  unsigned int cur_col, maxcols, tmp;
  (void)tmp;
  getmaxyx(interface->setup_win.single, tmp, maxcols);
  getyx(interface->setup_win.single, tmp, cur_col);
  mvwchgat(interface->setup_win.single, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);

  enum option_state option_state = !interface->options.use_color;
  mvwprintw(interface->setup_win.single, setup_general_color + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_general_option_description[setup_general_color]);
  if (interface->setup_win.indentation_level == 1 && interface->setup_win.options_selected[0] == setup_general_color) {
    mvwchgat(interface->setup_win.single, setup_general_color + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }
  option_state = interface->options.show_startup_messages;
  mvwprintw(interface->setup_win.single, setup_general_show_startup_support_messages + 1, 0, "[%c] %s",
            option_state_char(option_state),
            setup_general_option_description[setup_general_show_startup_support_messages]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_general_show_startup_support_messages) {
    mvwchgat(interface->setup_win.single, setup_general_show_startup_support_messages + 1, 0, 3, A_STANDOUT, cyan_color,
             NULL);
  }

  int update_deciseconds = (interface->options.update_interval / 100) % 10;
  int update_seconds = interface->options.update_interval / 1000;
  mvwprintw(interface->setup_win.single, setup_general_update_interval + 1, 0, "[%2u.%u] %s", update_seconds,
            update_deciseconds, setup_general_option_description[setup_general_update_interval]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_general_update_interval) {
    mvwchgat(interface->setup_win.single, setup_general_update_interval + 1, 0, 6, A_STANDOUT, cyan_color, NULL);
  }
  wnoutrefresh(interface->setup_win.single);
}

static void draw_setup_window_header(struct nvtop_interface *interface) {
  if (interface->setup_win.indentation_level > 1)
    interface->setup_win.indentation_level = 1;
  if (interface->setup_win.options_selected[0] >= setup_header_options_count)
    interface->setup_win.options_selected[0] = setup_header_options_count - 1;

  WINDOW *options_win = interface->setup_win.single;

  wattr_set(options_win, A_STANDOUT, green_color, NULL);
  mvwprintw(options_win, 0, 0, "Devices Display Options");
  wstandend(options_win);

  unsigned int cur_col, maxcols, tmp;
  (void)tmp;
  getmaxyx(options_win, tmp, maxcols);
  getyx(options_win, tmp, cur_col);
  mvwchgat(options_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);

  enum option_state option_state;

  // Fahrenheit Option
  option_state = interface->options.temperature_in_fahrenheit;
  mvwprintw(options_win, setup_header_toggle_fahrenheit + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_header_option_descriptions[setup_header_toggle_fahrenheit]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_header_toggle_fahrenheit) {
    mvwchgat(options_win, setup_header_toggle_fahrenheit + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }

  // Encode/Decode hiding timer
  if (interface->options.encode_decode_hiding_timer > 0) {
    mvwprintw(options_win, setup_header_enc_dec_timer + 1, 0, "[%3.0fsec] %s",
              interface->options.encode_decode_hiding_timer,
              setup_header_option_descriptions[setup_header_enc_dec_timer]);
  } else {
    mvwprintw(options_win, setup_header_enc_dec_timer + 1, 0, "[always] %s",
              setup_header_option_descriptions[setup_header_enc_dec_timer]);
  }
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_header_enc_dec_timer) {
    mvwchgat(options_win, setup_header_enc_dec_timer + 1, 0, 8, A_STANDOUT, cyan_color, NULL);
  }

  // Extra GPU info bar
  option_state = interface->options.has_gpu_info_bar;
  mvwprintw(options_win, setup_header_gpu_info_bar + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_header_option_descriptions[setup_header_gpu_info_bar]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_header_gpu_info_bar) {
    mvwchgat(options_win, setup_header_gpu_info_bar + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }
  wnoutrefresh(options_win);
}

static void draw_setup_window_chart(unsigned devices_count, struct list_head *devices,
                                    struct nvtop_interface *interface) {
  WINDOW *option_list_win;

  // Fix indices for this window
  if (interface->setup_win.options_selected[0] > devices_count + 1)
    interface->setup_win.options_selected[0] = devices_count + 1;
  if (interface->setup_win.options_selected[0] > 0) {
    if (interface->setup_win.options_selected[1] >= plot_information_count)
      interface->setup_win.options_selected[1] = plot_information_count - 1;
    option_list_win = interface->setup_win.split[0];
  } else {
    if (interface->setup_win.indentation_level > 1)
      interface->setup_win.indentation_level = 1;
    option_list_win = interface->setup_win.single;
  }
  werase(interface->setup_win.single);
  wnoutrefresh(interface->setup_win.single);
  touchwin(interface->setup_win.split[0]);
  touchwin(interface->setup_win.split[1]);

  wattr_set(option_list_win, A_STANDOUT, green_color, NULL);
  mvwprintw(option_list_win, 0, 0, "Chart Options");
  wstandend(option_list_win);

  unsigned int cur_col, maxcols, tmp;
  (void)tmp;
  getmaxyx(option_list_win, tmp, maxcols);
  getyx(option_list_win, tmp, cur_col);
  mvwchgat(option_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);

  enum option_state option_state;

  // Reverse plot
  option_state = interface->options.plot_left_to_right;
  mvwprintw(option_list_win, setup_chart_reverse + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_chart_options_descriptions[setup_chart_reverse]);
  if (interface->setup_win.indentation_level == 1 && interface->setup_win.options_selected[0] == setup_chart_reverse) {
    mvwchgat(option_list_win, setup_chart_reverse + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }

  // Set for all GPUs at once
  if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) {
    if (interface->setup_win.indentation_level == 1)
      wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL);
    if (interface->setup_win.indentation_level == 2)
      wattr_set(option_list_win, A_BOLD, cyan_color, NULL);
  }
  mvwaddch(option_list_win, setup_chart_all_gpu + 1, 1, ACS_HLINE);
  waddch(option_list_win, '>');
  wstandend(option_list_win);
  wprintw(option_list_win, " %s", setup_chart_options_descriptions[setup_chart_all_gpu]);

  // GPUs as a list
  for (unsigned i = 0; i < devices_count; ++i) {
    if (interface->setup_win.options_selected[0] == setup_chart_start_gpu_list + i) {
      if (interface->setup_win.indentation_level == 1)
        wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL);
      if (interface->setup_win.indentation_level == 2)
        wattr_set(option_list_win, A_BOLD, cyan_color, NULL);
    }
    mvwaddch(option_list_win, setup_chart_start_gpu_list + 1 + i, 1, ACS_HLINE);
    waddch(option_list_win, '>');
    wstandend(option_list_win);
    wprintw(option_list_win, " %s %u", setup_chart_options_descriptions[setup_chart_start_gpu_list], i);
  }
  wnoutrefresh(option_list_win);

  // Window of list of metric to display in chart (4 maximum)
  if (interface->setup_win.options_selected[0] >= setup_chart_all_gpu) {
    WINDOW *value_list_win = interface->setup_win.split[1];
    wattr_set(value_list_win, A_STANDOUT, green_color, NULL);
    mvwprintw(value_list_win, 0, 0, "Metric Displayed in Graph");
    getmaxyx(value_list_win, tmp, maxcols);
    unsigned selected_gpu = interface->setup_win.options_selected[0] - setup_chart_start_gpu_list;
    if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) {
      wprintw(value_list_win, " (All GPUs)");
    } else {
      // Get the selected device
      struct gpu_info *device;
      unsigned index = 0;
      list_for_each_entry(device, devices, list) {
        if (index == selected_gpu)
          break;
        index++;
      }
      if (IS_VALID(gpuinfo_device_name_valid, device->static_info.valid)) {
        getyx(value_list_win, tmp, cur_col);
        wprintw(value_list_win, " (%.*s)", maxcols - cur_col - 3, device->static_info.device_name);
      } else
        wprintw(value_list_win, " (GPU %u)", selected_gpu);
    }
    wclrtoeol(value_list_win);
    getyx(value_list_win, tmp, cur_col);
    mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);
    wattr_set(value_list_win, A_NORMAL, magenta_color, NULL);
    mvwprintw(value_list_win, 1, 0, "Maximum of 4 metrics per GPU");
    wstandend(value_list_win);

    for (enum plot_information i = plot_gpu_rate; i < plot_information_count; ++i) {
      if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) {
        plot_info_to_draw draw_union = 0, draw_intersection = 0xffff;
        for (unsigned j = 0; j < devices_count; ++j) {
          draw_union |= interface->options.gpu_specific_opts[j].to_draw;
          draw_intersection = draw_intersection & interface->options.gpu_specific_opts[j].to_draw;
        }
        if (plot_isset_draw_info(i, draw_intersection)) {
          option_state = option_on;
        } else {
          if (plot_isset_draw_info(i, draw_union))
            option_state = option_partially_active;
          else
            option_state = option_off;
        }
      } else {
        option_state = plot_isset_draw_info(i, interface->options.gpu_specific_opts[selected_gpu].to_draw);
      }
      mvwprintw(value_list_win, i + 2, 0, "[%c] %s", option_state_char(option_state),
                setup_chart_gpu_value_descriptions[i]);
      if (interface->setup_win.indentation_level == 2 && interface->setup_win.options_selected[1] == i) {
        mvwchgat(value_list_win, i + 2, 0, 3, A_STANDOUT, cyan_color, NULL);
      }
    }
    wnoutrefresh(value_list_win);
  }
}

static void draw_setup_window_proc_list(struct nvtop_interface *interface) {
  WINDOW *option_list_win;
  if (interface->setup_win.options_selected[0] >= setup_proc_list_options_count)
    interface->setup_win.options_selected[0] = setup_proc_list_options_count - 1;
  if (interface->setup_win.options_selected[0] < setup_proc_list_sort_by) {
    option_list_win = interface->setup_win.single;
    if (interface->setup_win.indentation_level > 1)
      interface->setup_win.indentation_level = 1;
  } else {
    option_list_win = interface->setup_win.split[0];
    if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) {
      unsigned fields_count = process_field_displayed_count(interface->options.process_fields_displayed);
      if (!fields_count) {
        if (interface->setup_win.indentation_level > 1)
          interface->setup_win.indentation_level = 1;
      } else {
        if (interface->setup_win.options_selected[1] >= fields_count)
          interface->setup_win.options_selected[1] = fields_count - 1;
      }
    }
    if (interface->setup_win.options_selected[0] == setup_proc_list_display) {
      if (interface->setup_win.options_selected[1] >= process_field_count)
        interface->setup_win.options_selected[1] = process_field_count - 1;
    }
  }

  werase(interface->setup_win.single);
  wnoutrefresh(interface->setup_win.single);
  touchwin(interface->setup_win.split[0]);
  touchwin(interface->setup_win.split[1]);

  wattr_set(option_list_win, A_STANDOUT, green_color, NULL);
  mvwprintw(option_list_win, 0, 0, "Process List Options");
  wstandend(option_list_win);
  unsigned int cur_col, maxcols, tmp;
  (void)tmp;
  getmaxyx(option_list_win, tmp, maxcols);
  getyx(option_list_win, tmp, cur_col);
  mvwchgat(option_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);

  // Sort Ascending
  enum option_state option_state = interface->options.filter_nvtop_pid;
  mvwprintw(option_list_win, setup_proc_list_hide_nvtop_process + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_proc_list_option_description[setup_proc_list_hide_nvtop_process]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_proc_list_hide_nvtop_process) {
    mvwchgat(option_list_win, setup_proc_list_hide_nvtop_process + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }
  option_state = !interface->options.sort_descending_order;
  mvwprintw(option_list_win, setup_proc_list_sort_ascending + 1, 0, "[%c] %s", option_state_char(option_state),
            setup_proc_list_option_description[setup_proc_list_sort_ascending]);
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] == setup_proc_list_sort_ascending) {
    mvwchgat(option_list_win, setup_proc_list_sort_ascending + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }

  for (enum setup_proc_list_options i = setup_proc_list_sort_by; i < setup_proc_list_options_count; ++i) {
    if (interface->setup_win.options_selected[0] == i) {
      if (interface->setup_win.indentation_level == 1)
        wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL);
      if (interface->setup_win.indentation_level == 2)
        wattr_set(option_list_win, A_BOLD, cyan_color, NULL);
    }
    mvwaddch(option_list_win, i + 1, 1, ACS_HLINE);
    waddch(option_list_win, '>');
    wstandend(option_list_win);
    wprintw(option_list_win, " %s", setup_proc_list_option_description[i]);
    wnoutrefresh(option_list_win);
  }

  if (interface->setup_win.options_selected[0] >= setup_proc_list_sort_by) {
    WINDOW *value_list_win = interface->setup_win.split[1];
    // Sort by
    if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) {
      wattr_set(value_list_win, A_STANDOUT, green_color, NULL);
      mvwprintw(value_list_win, 0, 0, "Processes are sorted by:");
      wstandend(value_list_win);
      wclrtoeol(value_list_win);
      getmaxyx(value_list_win, tmp, maxcols);
      getyx(value_list_win, tmp, cur_col);
      mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);
      unsigned index = 0;
      for (enum process_field field = process_pid; field < process_field_count; ++field) {
        if (process_is_field_displayed(field, interface->options.process_fields_displayed)) {
          option_state = interface->options.sort_processes_by == field;
          mvwprintw(value_list_win, index + 1, 0, "[%c] %s", option_state_char(option_state),
                    setup_proc_list_value_descriptions[field]);
          wclrtoeol(value_list_win);
          if (interface->setup_win.indentation_level == 2 && interface->setup_win.options_selected[1] == index) {
            mvwchgat(value_list_win, index + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
            wmove(value_list_win, field + 2, 0);
          }
          index++;
        }
      }
      if (!index) {
        // Nothing displayed
        wcolor_set(value_list_win, magenta_color, NULL);
        mvwprintw(value_list_win, 1, 0, "Nothing to sort: none of the process fields are displayed");
        wstandend(value_list_win);
      }
    }
    // Process field displayed
    if (interface->setup_win.options_selected[0] == setup_proc_list_display) {
      wattr_set(value_list_win, A_STANDOUT, green_color, NULL);
      mvwprintw(value_list_win, 0, 0, "Process Field Displayed:");
      wstandend(value_list_win);
      wclrtoeol(value_list_win);
      getmaxyx(value_list_win, tmp, maxcols);
      getyx(value_list_win, tmp, cur_col);
      mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);
      for (enum process_field field = process_pid; field < process_field_count; ++field) {
        option_state = process_is_field_displayed(field, interface->options.process_fields_displayed);
        mvwprintw(value_list_win, field + 1, 0, "[%c] %s", option_state_char(option_state),
                  setup_proc_list_value_descriptions[field]);
        wclrtoeol(value_list_win);
        if (interface->setup_win.indentation_level == 2 && interface->setup_win.options_selected[1] == field) {
          mvwchgat(value_list_win, field + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
          wmove(value_list_win, field + 2, 0);
        }
      }
    }
    wclrtobot(value_list_win);
    wnoutrefresh(value_list_win);
  }
}

static void draw_setup_window_gpu_select(struct nvtop_interface *interface) {
  if (interface->setup_win.indentation_level > 1)
    interface->setup_win.indentation_level = 1;
  if (interface->setup_win.indentation_level == 1 &&
      interface->setup_win.options_selected[0] >= interface->total_dev_count)
    interface->setup_win.options_selected[0] = interface->total_dev_count - 1;

  wattr_set(interface->setup_win.single, A_STANDOUT, green_color, NULL);
  mvwprintw(interface->setup_win.single, 0, 0, "Select Monitored GPUs");
  wstandend(interface->setup_win.single);
  unsigned int cur_col, maxcols, tmp;
  (void)tmp;
  getmaxyx(interface->setup_win.single, tmp, maxcols);
  getyx(interface->setup_win.single, tmp, cur_col);
  mvwchgat(interface->setup_win.single, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, NULL);

  for (unsigned devId = 0; devId < interface->total_dev_count; ++devId) {
    mvwprintw(interface->setup_win.single, devId + 1, 0, "[%c] %s",
              option_state_char(!interface->options.gpu_specific_opts[devId].doNotMonitor),
              interface->options.gpu_specific_opts[devId].linkedGpu->static_info.device_name);
    if (interface->setup_win.indentation_level == 1 && interface->setup_win.options_selected[0] == devId)
      mvwchgat(interface->setup_win.single, devId + 1, 0, 3, A_STANDOUT, cyan_color, NULL);
  }

  wnoutrefresh(interface->setup_win.single);
}

static const char *setup_window_shortcuts[] = {"Enter", "ESC", "Arrow keys", "+/-", "F12"};

static const char *setup_window_shortcut_description[] = {"Toggle", "Exit", "Navigate Menu",
                                                          "Increment/Decrement Values", "Save Config"};

void draw_setup_window_shortcuts(struct nvtop_interface *interface) {
  WINDOW *window = interface->shortcut_window;

  wmove(window, 0, 0);
  for (size_t i = 0; i < ARRAY_SIZE(setup_window_shortcuts); ++i) {
    wprintw(window, "%s", setup_window_shortcuts[i]);
    wattr_set(window, A_STANDOUT, cyan_color, NULL);
    wprintw(window, "%s ", setup_window_shortcut_description[i]);
    wstandend(window);
  }
  wclrtoeol(window);
  unsigned int cur_col, tmp;
  (void)tmp;
  getyx(window, tmp, cur_col);
  mvwchgat(window, 0, cur_col, -1, A_STANDOUT, cyan_color, NULL);
  wnoutrefresh(window);
}

void draw_setup_window(unsigned devices_count, struct list_head *devices, struct nvtop_interface *interface) {
  draw_setup_window_setup(interface);
  switch (interface->setup_win.selected_section) {
  case setup_general_selected:
    draw_setup_window_general(interface);
    break;
  case setup_header_selected:
    draw_setup_window_header(interface);
    break;
  case setup_chart_selected:
    draw_setup_window_chart(devices_count, devices, interface);
    break;
  case setup_process_list_selected:
    draw_setup_window_proc_list(interface);
    break;
  case setup_monitored_gpu_list_selected:
    draw_setup_window_gpu_select(interface);
    break;
  default:
    break;
  }
}

void handle_setup_win_keypress(int keyId, struct nvtop_interface *interface) {
  if (interface->setup_win.visible) {
    switch (keyId) {

    case 'l':
    case KEY_RIGHT:
      if (interface->setup_win.indentation_level < 2)
        interface->setup_win.indentation_level++;
      break;

    case 'h':
    case KEY_LEFT:
      if (interface->setup_win.indentation_level > 0)
        interface->setup_win.indentation_level--;
      break;

    case 'k':
    case KEY_UP:
      if (interface->setup_win.indentation_level == 0) {
        if (interface->setup_win.selected_section != setup_general_selected) {
          interface->setup_win.selected_section--;
          interface->setup_win.options_selected[0] = 0;
          interface->setup_win.options_selected[1] = 0;
          werase(interface->setup_win.single);
          werase(interface->setup_win.split[0]);
          werase(interface->setup_win.split[1]);
          wnoutrefresh(interface->setup_win.single);
        }
      } else {
        if (interface->setup_win.indentation_level == 1)
          interface->setup_win.options_selected[1] = 0;
        if (interface->setup_win.options_selected[interface->setup_win.indentation_level - 1] != 0)
          interface->setup_win.options_selected[interface->setup_win.indentation_level - 1]--;
      }
      break;

    case 'j':
    case KEY_DOWN:
      if (interface->setup_win.indentation_level == 0) {
        if (interface->setup_win.selected_section + 1 != setup_window_selection_count) {

          interface->setup_win.selected_section++;
          interface->setup_win.options_selected[0] = 0;
          interface->setup_win.options_selected[1] = 0;
          werase(interface->setup_win.single);
          werase(interface->setup_win.split[0]);
          werase(interface->setup_win.split[1]);
          wnoutrefresh(interface->setup_win.single);
        }
      } else {
        if (interface->setup_win.indentation_level == 1)
          interface->setup_win.options_selected[1] = 0;
        interface->setup_win.options_selected[interface->setup_win.indentation_level - 1]++;
      }
      break;

    case '+':
      // General Options
      if (interface->setup_win.selected_section == setup_general_selected) {
        if (interface->setup_win.options_selected[0] == setup_general_update_interval) {
          if (interface->options.update_interval <= 99800)
            interface->options.update_interval += 100;
        }
      }
      // Header options
      if (interface->setup_win.selected_section == setup_header_selected) {
        if (interface->setup_win.indentation_level == 1) {
          if (interface->setup_win.options_selected[0] == setup_header_enc_dec_timer) {
            interface->options.encode_decode_hiding_timer += 5.;
          }
        }
      }
      break;
    case '-':
      // General Options
      if (interface->setup_win.selected_section == setup_general_selected) {
        if (interface->setup_win.options_selected[0] == setup_general_update_interval) {
          if (interface->options.update_interval >= 200)
            interface->options.update_interval -= 100;
        }
      }
      // Header options
      if (interface->setup_win.selected_section == setup_header_selected) {
        if (interface->setup_win.indentation_level == 1) {
          if (interface->setup_win.options_selected[0] == setup_header_enc_dec_timer) {
            interface->options.encode_decode_hiding_timer -= 5.;
            if (interface->options.encode_decode_hiding_timer < 0.) {
              interface->options.encode_decode_hiding_timer = 0.;
            }
          }
        }
      }
      break;
    case '\n':
    case KEY_ENTER:
      if (interface->setup_win.indentation_level == 0) {
        handle_setup_win_keypress(KEY_RIGHT, interface);
        return;
      }
      // General Options
      if (interface->setup_win.selected_section == setup_general_selected) {
        if (interface->setup_win.options_selected[0] == setup_general_color) {
          interface->options.use_color = !interface->options.use_color;
        }
        if (interface->setup_win.options_selected[0] == setup_general_show_startup_support_messages) {
          interface->options.show_startup_messages = !interface->options.show_startup_messages;
        }
        if (interface->setup_win.options_selected[0] == setup_general_update_interval) {
        }
      }
      // Header Options
      if (interface->setup_win.selected_section == setup_header_selected) {
        if (interface->setup_win.indentation_level == 1) {
          if (interface->setup_win.options_selected[0] == setup_header_toggle_fahrenheit) {
            interface->options.temperature_in_fahrenheit = !interface->options.temperature_in_fahrenheit;
          }
          if (interface->setup_win.options_selected[0] == setup_header_enc_dec_timer) {
            if (interface->options.encode_decode_hiding_timer > 0.) {
              interface->options.encode_decode_hiding_timer = 0.;
            } else {
              interface->options.encode_decode_hiding_timer = 30.;
            }
          }
          if (interface->setup_win.options_selected[0] == setup_header_gpu_info_bar) {
            interface->options.has_gpu_info_bar = !interface->options.has_gpu_info_bar;
          }
        }
      }
      // Chart Options
      if (interface->setup_win.selected_section == setup_chart_selected) {
        if (interface->setup_win.indentation_level == 1) {
          if (interface->setup_win.options_selected[0] == setup_chart_reverse) {
            interface->options.plot_left_to_right = !interface->options.plot_left_to_right;
          }
          if (interface->setup_win.options_selected[0] >= setup_chart_all_gpu) {
            handle_setup_win_keypress(KEY_RIGHT, interface);
          }
        } else if (interface->setup_win.indentation_level == 2) {
          if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) {
            plot_info_to_draw draw_intersection = 0xffff;
            for (unsigned j = 0; j < interface->monitored_dev_count; ++j) {
              draw_intersection = draw_intersection & interface->options.gpu_specific_opts[j].to_draw;
            }
            if (plot_isset_draw_info(interface->setup_win.options_selected[1], draw_intersection)) {
              for (unsigned i = 0; i < interface->monitored_dev_count; ++i) {
                interface->options.gpu_specific_opts[i].to_draw = plot_remove_draw_info(
                    interface->setup_win.options_selected[1], interface->options.gpu_specific_opts[i].to_draw);
                interface_ring_buffer_empty(&interface->saved_data_ring, i);
              }
            } else {
              for (unsigned i = 0; i < interface->monitored_dev_count; ++i) {
                interface->options.gpu_specific_opts[i].to_draw = plot_add_draw_info(
                    interface->setup_win.options_selected[1], interface->options.gpu_specific_opts[i].to_draw);
                interface_ring_buffer_empty(&interface->saved_data_ring, i);
              }
            }
          }
          if (interface->setup_win.options_selected[0] > setup_chart_all_gpu) {
            unsigned selected_gpu = interface->setup_win.options_selected[0] - setup_chart_start_gpu_list;
            if (plot_isset_draw_info(interface->setup_win.options_selected[1],
                                     interface->options.gpu_specific_opts[selected_gpu].to_draw))
              interface->options.gpu_specific_opts[selected_gpu].to_draw = plot_remove_draw_info(
                  interface->setup_win.options_selected[1], interface->options.gpu_specific_opts[selected_gpu].to_draw);
            else
              interface->options.gpu_specific_opts[selected_gpu].to_draw = plot_add_draw_info(
                  interface->setup_win.options_selected[1], interface->options.gpu_specific_opts[selected_gpu].to_draw);
            interface_ring_buffer_empty(&interface->saved_data_ring, selected_gpu);
          }
        }
      }
      // Process List Options
      if (interface->setup_win.selected_section == setup_process_list_selected) {
        if (interface->setup_win.indentation_level == 1) {
          if (interface->setup_win.options_selected[0] == setup_proc_list_sort_ascending) {
            interface->options.sort_descending_order = !interface->options.sort_descending_order;
          } else if (interface->setup_win.options_selected[0] == setup_proc_list_hide_nvtop_process) {
            interface->options.filter_nvtop_pid = !interface->options.filter_nvtop_pid;
          } else if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) {
            handle_setup_win_keypress(KEY_RIGHT, interface);
          }
        } else if (interface->setup_win.indentation_level == 2) {
          if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) {
            unsigned index = 0;
            for (enum process_field field = process_pid; field < process_field_count; ++field) {
              if (process_is_field_displayed(field, interface->options.process_fields_displayed)) {
                if (index == interface->setup_win.options_selected[1])
                  interface->options.sort_processes_by = field;
                index++;
              }
            }
          }
          if (interface->setup_win.options_selected[0] == setup_proc_list_display) {
            if (process_is_field_displayed(interface->setup_win.options_selected[1],
                                           interface->options.process_fields_displayed)) {
              interface->options.process_fields_displayed = process_remove_field_to_display(
                  interface->setup_win.options_selected[1], interface->options.process_fields_displayed);
            } else {
              interface->options.process_fields_displayed = process_add_field_to_display(
                  interface->setup_win.options_selected[1], interface->options.process_fields_displayed);
            }
            if (!process_is_field_displayed(interface->options.sort_processes_by,
                                            interface->options.process_fields_displayed)) {
              interface->options.sort_processes_by =
                  process_default_sort_by_from(interface->options.process_fields_displayed);
            }
          }
        }
      }
      if (interface->setup_win.selected_section == setup_monitored_gpu_list_selected) {
        if (interface->setup_win.indentation_level == 1) {
          interface->options.gpu_specific_opts[interface->setup_win.options_selected[0]].doNotMonitor =
              !interface->options.gpu_specific_opts[interface->setup_win.options_selected[0]].doNotMonitor;
          interface->options.has_monitored_set_changed = true;
        }
      }
      break;
    case KEY_F(2):
    case 27:
      interface->setup_win.visible = false;
      update_window_size_to_terminal_size(interface);
      break;
    case KEY_F(12):
      save_interface_options_to_config_file(interface->total_dev_count, &interface->options);
      break;
    default:
      break;
    }
  }
}
