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

#define _GNU_SOURCE
#include <unistd.h>
#include <math.h>
#include <sys/param.h>
#include <ctype.h>
#include <stdbool.h>

#include "ProcessList.h"
#include "CRT.h"
#include "Panel.h"
#include "UsersTable.h"
#include "SignalItem.h"
#include "RichString.h"
#include "Settings.h"
#include "ScreenManager.h"
#include "FunctionBar.h"
#include "ListItem.h"
#include "CategoriesPanel.h"
#include "SignalsPanel.h"
#include "TraceScreen.h"

#include "config.h"
#include "debug.h"

//#link m

#define INCSEARCH_MAX 40

void printVersionFlag() {
   clear();
   printf("htop " VERSION " - (C) 2004-2006 Hisham Muhammad.\n");
   printf("Released under the GNU GPL.\n\n");
   exit(0);
}

void printHelpFlag() {
   clear();
   printf("htop " VERSION " - (C) 2004-2006 Hisham Muhammad.\n");
   printf("Released under the GNU GPL.\n\n");
   printf("-d DELAY     Delay between updates, in tenths of seconds\n\n");
   printf("-u USERNAME  Show only processes of a given user\n\n");
   printf("--sort-key COLUMN  Sort by this column (use --sort-key help for a column list)\n\n");
   printf("Press F1 inside htop for online help.\n");
   printf("See the man page for full information.\n\n");
   exit(0);
}

void showHelp(ProcessList* pl) {
   clear();
   attrset(CRT_colors[HELP_BOLD]);
   mvaddstr(0, 0, "htop " VERSION " - (C) 2004-2006 Hisham Muhammad.");
   mvaddstr(1, 0, "Released under the GNU GPL. See 'man' page for more info.");

   attrset(CRT_colors[DEFAULT_COLOR]);
   mvaddstr(3, 0, "CPU usage bar: ");
   #define addattrstr(a,s) attrset(a);addstr(s)
   addattrstr(CRT_colors[BAR_BORDER], "[");
   if (pl->expandSystemTime) {
      addattrstr(CRT_colors[CPU_NICE], "low"); addstr("/");
      addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
      addattrstr(CRT_colors[CPU_KERNEL], "kernel"); addstr("/");
      addattrstr(CRT_colors[CPU_IOWAIT], "io-wait"); addstr("/");
      addattrstr(CRT_colors[CPU_IRQ], "irq"); addstr("/");
      addattrstr(CRT_colors[CPU_SOFTIRQ], "soft-irq");
      addattrstr(CRT_colors[BAR_SHADOW], " used%");
   } else {
      addattrstr(CRT_colors[CPU_NICE], "low-priority"); addstr("/");
      addattrstr(CRT_colors[CPU_NORMAL], "normal"); addstr("/");
      addattrstr(CRT_colors[CPU_KERNEL], "kernel");
      addattrstr(CRT_colors[BAR_SHADOW], "             used%");
   }
   addattrstr(CRT_colors[BAR_BORDER], "]");
   attrset(CRT_colors[DEFAULT_COLOR]);
   mvaddstr(4, 0, "Memory bar:    ");
   addattrstr(CRT_colors[BAR_BORDER], "[");
   addattrstr(CRT_colors[MEMORY_USED], "used"); addstr("/");
   addattrstr(CRT_colors[MEMORY_BUFFERS], "buffers"); addstr("/");
   addattrstr(CRT_colors[MEMORY_CACHE], "cache");
   addattrstr(CRT_colors[BAR_SHADOW], "                used/total");
   addattrstr(CRT_colors[BAR_BORDER], "]");
   attrset(CRT_colors[DEFAULT_COLOR]);
   mvaddstr(5, 0, "Swap bar:      ");
   addattrstr(CRT_colors[BAR_BORDER], "[");
   addattrstr(CRT_colors[SWAP], "used");
   addattrstr(CRT_colors[BAR_SHADOW], "                              used/total");
   addattrstr(CRT_colors[BAR_BORDER], "]");
   attrset(CRT_colors[DEFAULT_COLOR]);
   mvaddstr(6,0, "Type and layout of header meters are configurable in the setup screen.");
   mvaddstr(7, 0, "Status: R: running; S: sleeping; T: traced/stopped; Z: zombie; D: disk sleep");

   mvaddstr( 9, 0, " Arrows: scroll process list             F5 t: tree view");
   mvaddstr(10, 0, " Digits: incremental PID search             u: show processes of a single user");
   mvaddstr(11, 0, "   F3 /: incremental name search            H: hide/show user threads");
   mvaddstr(12, 0, "                                            K: hide/show kernel threads");
   mvaddstr(13, 0, "  Space: tag processes                      F: cursor follows process");
   mvaddstr(14, 0, "      U: untag all processes");
   mvaddstr(15, 0, "   F9 k: kill process/tagged processes      P: sort by CPU%");
   mvaddstr(16, 0, " + [ F7: lower priority (+ nice)            M: sort by MEM%");
   mvaddstr(17, 0, " - ] F8: higher priority (root only)        T: sort by TIME");
   mvaddstr(18, 0, "                                         F4 I: invert sort order");
   mvaddstr(19, 0, "   F2 S: setup                           F6 >: select sort column");
   mvaddstr(20, 0, "   F1 h: show this help screen");
   mvaddstr(21, 0, "  F10 q: quit                               s: trace syscalls with strace");

   attrset(CRT_colors[HELP_BOLD]);
   mvaddstr( 9, 0, " Arrows"); mvaddstr( 9,40, " F5 t");
   mvaddstr(10, 0, " Digits"); mvaddstr(10,40, "    u");
   mvaddstr(11, 0, "   F3 /"); mvaddstr(11,40, "    H");
                               mvaddstr(12,40, "    K");
   mvaddstr(13, 0, "  Space"); mvaddstr(13,40, "    F");
   mvaddstr(14, 0, "      U");
   mvaddstr(15, 0, "   F9 k"); mvaddstr(15,40, "    P");
   mvaddstr(16, 0, " + [ F7"); mvaddstr(16,40, "    M");
   mvaddstr(17, 0, " - ] F8"); mvaddstr(17,40, "    T");
                               mvaddstr(18,40, " F4 I");
   mvaddstr(19, 0, "   F2 S"); mvaddstr(19,40, " F6 >");
   mvaddstr(20, 0, "   F1 h");
   mvaddstr(21, 0, "  F10 q"); mvaddstr(21,40, "    s");
   attrset(CRT_colors[DEFAULT_COLOR]);

   attrset(CRT_colors[HELP_BOLD]);
   mvaddstr(23,0, "Press any key to return.");
   attrset(CRT_colors[DEFAULT_COLOR]);
   refresh();
   CRT_readKey();
   clear();
}

static void Setup_run(Settings* settings, int headerHeight) {
   ScreenManager* scr = ScreenManager_new(0, headerHeight, 0, -1, HORIZONTAL, true);
   CategoriesPanel* panelCategories = CategoriesPanel_new(settings, scr);
   ScreenManager_add(scr, (Panel*) panelCategories, NULL, 16);
   CategoriesPanel_makeMetersPage(panelCategories);
   Panel* panelFocus;
   int ch;
   ScreenManager_run(scr, &panelFocus, &ch);
   ScreenManager_delete(scr);
}

static bool changePriority(Panel* panel, int delta) {
   bool anyTagged = false;
   for (int i = 0; i < Panel_getSize(panel); i++) {
      Process* p = (Process*) Panel_get(panel, i);
      if (p->tag) {
         Process_setPriority(p, p->nice + delta);
         anyTagged = true;
      }
   }
   if (!anyTagged) {
      Process* p = (Process*) Panel_getSelected(panel);
      Process_setPriority(p, p->nice + delta);
   }
   return anyTagged;
}

static HandlerResult pickWithEnter(Panel* panel, int ch) {
   if (ch == 13)
      return BREAK_LOOP;
   return IGNORED;
}

static Object* pickFromList(Panel* panel, Panel* list, int x, int y, char** keyLabels, FunctionBar* prevBar) {
   char* fuKeys[2] = {"Enter", "Esc"};
   int fuEvents[2] = {13, 27};
   if (!panel->eventHandler)
      Panel_setEventHandler(list, pickWithEnter);
   ScreenManager* scr = ScreenManager_new(0, y, 0, -1, HORIZONTAL, false);
   ScreenManager_add(scr, list, FunctionBar_new(2, keyLabels, fuKeys, fuEvents), x - 1);
   ScreenManager_add(scr, panel, NULL, -1);
   Panel* panelFocus;
   int ch;
   ScreenManager_run(scr, &panelFocus, &ch);
   ScreenManager_delete(scr);
   Panel_move(panel, 0, y);
   Panel_resize(panel, COLS, LINES-y-1);
   FunctionBar_draw(prevBar, NULL);
   if (panelFocus == list && ch == 13) {
      return Panel_getSelected(list);
   }
   return NULL;
}

void addUserToList(int key, void* userCast, void* panelCast) {
   char* user = (char*) userCast;
   Panel* panel = (Panel*) panelCast;
   Panel_add(panel, (Object*) ListItem_new(user, key));
}

void setUserOnly(const char* userName, bool* userOnly, uid_t* userId) {
   struct passwd* user = getpwnam(userName);
   if (user) {
      *userOnly = true;
      *userId = user->pw_uid;
   }
}

int main(int argc, char** argv) {

   int delay = -1;
   bool userOnly = false;
   uid_t userId = 0;
   int sortKey = 0;

   if (argc > 0) {
      if (String_eq(argv[1], "--help")) {
         printHelpFlag();
      } else if (String_eq(argv[1], "--version")) {
         printVersionFlag();
      } else if (String_eq(argv[1], "--sort-key")) {
         if (argc < 2) printHelpFlag();
            if (String_eq(argv[2], "help")) {
            for (int j = 1; j < LAST_PROCESSFIELD; j++)
               printf ("%s\n", Process_fieldNames[j]);
            exit(0);
         }
         sortKey = ColumnsPanel_fieldNameToIndex(argv[2]);
         if (sortKey == -1) {
            fprintf(stderr, "Error: invalid column \"%s\".\n", argv[2]);
            exit(1);
         }
      } else if (String_eq(argv[1], "-d")) {
         if (argc < 2) printHelpFlag();
         sscanf(argv[2], "%d", &delay);
         if (delay < 1) delay = 1;
         if (delay > 100) delay = 100;
      } else if (String_eq(argv[1], "-u")) {
         if (argc < 2) printHelpFlag();
         setUserOnly(argv[2], &userOnly, &userId);
      }
   }
   
   if (access(PROCDIR, R_OK) != 0) {
      fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR);
      exit(1);
   }

   Panel* panel;
   int quit = 0;
   int refreshTimeout = 0;
   int resetRefreshTimeout = 5;
   bool doRefresh = true;
   Settings* settings;
   
   Panel* killPanel = NULL;

   char incSearchBuffer[INCSEARCH_MAX];
   int incSearchIndex = 0;
   incSearchBuffer[0] = 0;
   bool incSearchMode = false;

   ProcessList* pl = NULL;
   UsersTable* ut = UsersTable_new();

   pl = ProcessList_new(ut);
   
   Header* header = Header_new(pl);
   settings = Settings_new(pl, header);
   if (sortKey > 0) {
      pl->sortKey = sortKey;
      pl->treeView = false;
   }
   int headerHeight = Header_calculateHeight(header);

   // FIXME: move delay code to settings
   if (delay != -1)
      settings->delay = delay;
   
   CRT_init(settings->delay, settings->colorScheme);
   
   panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, PROCESS_CLASS, false, NULL);
   Panel_setRichHeader(panel, ProcessList_printHeader(pl));
   
   char* searchFunctions[3] = {"Next  ", "Exit  ", " Search: "};
   char* searchKeys[3] = {"F3", "Esc", "  "};
   int searchEvents[3] = {KEY_F(3), 27, ERR};
   FunctionBar* searchBar = FunctionBar_new(3, searchFunctions, searchKeys, searchEvents);
   
   char* defaultFunctions[10] = {"Help  ", "Setup ", "Search", "Invert", "Tree  ",
       "SortBy", "Nice -", "Nice +", "Kill  ", "Quit  "};
   FunctionBar* defaultBar = FunctionBar_new(10, defaultFunctions, NULL, NULL);

   ProcessList_scan(pl);
   usleep(75000);
   
   FunctionBar_draw(defaultBar, NULL);
   
   int acc = 0;
   bool follow = false;
 
   struct timeval tv;
   double newTime = 0.0;
   double oldTime = 0.0;
   bool recalculate;

   int ch = 0;
   int closeTimeout = 0;

   while (!quit) {
      gettimeofday(&tv, NULL);
      newTime = ((double)tv.tv_sec * 10) + ((double)tv.tv_usec / 100000);
      recalculate = (newTime - oldTime > CRT_delay);
      if (recalculate)
         oldTime = newTime;
      if (doRefresh) {
         incSearchIndex = 0;
         incSearchBuffer[0] = 0;
         int currPos = Panel_getSelectedIndex(panel);
         unsigned int currPid = 0;
         int currScrollV = panel->scrollV;
         if (follow)
            currPid = ProcessList_get(pl, currPos)->pid;
         if (recalculate)
            ProcessList_scan(pl);
         if (refreshTimeout == 0) {
            ProcessList_sort(pl);
            refreshTimeout = 1;
         }
         Panel_prune(panel);
         int size = ProcessList_size(pl);
         int index = 0;
         for (int i = 0; i < size; i++) {
            Process* p = ProcessList_get(pl, i);
            if (!userOnly || (p->st_uid == userId)) {
               Panel_set(panel, index, (Object*)p);
               if ((!follow && index == currPos) || (follow && p->pid == currPid)) {
                  Panel_setSelected(panel, index);
                  panel->scrollV = currScrollV;
               }
               index++;
            }
         }
      }
      doRefresh = true;
      
      Header_draw(header);

      Panel_draw(panel, true);
      int prev = ch;
      ch = getch();

      if (ch == ERR) {
         if (!incSearchMode)
            refreshTimeout--;
         if (prev == ch && !recalculate) {
            closeTimeout++;
            if (closeTimeout == 10)
               break;
         } else
            closeTimeout = 0;
         continue;
      }

      if (incSearchMode) {
         doRefresh = false;
         if (ch == KEY_F(3)) {
            int here = Panel_getSelectedIndex(panel);
            int size = ProcessList_size(pl);
            int i = here+1;
            while (i != here) {
               if (i == size)
                  i = 0;
               Process* p = ProcessList_get(pl, i);
               if (String_contains_i(p->comm, incSearchBuffer)) {
                  Panel_setSelected(panel, i);
                  break;
               }
               i++;
            }
            continue;
         } else if (isprint((char)ch) && (incSearchIndex < INCSEARCH_MAX)) {
            incSearchBuffer[incSearchIndex] = ch;
            incSearchIndex++;
            incSearchBuffer[incSearchIndex] = 0;
         } else if ((ch == KEY_BACKSPACE || ch == 127) && (incSearchIndex > 0)) {
            incSearchIndex--;
            incSearchBuffer[incSearchIndex] = 0;
         } else {
            incSearchMode = false;
            incSearchIndex = 0;
            incSearchBuffer[0] = 0;
            FunctionBar_draw(defaultBar, NULL);
            continue;
         }

         bool found = false;
         for (int i = 0; i < ProcessList_size(pl); i++) {
            Process* p = ProcessList_get(pl, i);
            if (String_contains_i(p->comm, incSearchBuffer)) {
               Panel_setSelected(panel, i);
               found = true;
               break;
            }
         }
         if (found)
            FunctionBar_draw(searchBar, incSearchBuffer);
         else
            FunctionBar_drawAttr(searchBar, incSearchBuffer, CRT_colors[FAILED_SEARCH]);

         continue;
      }
      if (isdigit((char)ch)) {
         unsigned int pid = ch-48 + acc;
         for (int i = 0; i < ProcessList_size(pl) && ((Process*) Panel_getSelected(panel))->pid != pid; i++)
            Panel_setSelected(panel, i);
         acc = pid * 10;
         if (acc > 100000)
            acc = 0;
         continue;
      } else {
         acc = 0;
      }

      if (ch == KEY_MOUSE) {
         MEVENT mevent;
         int ok = getmouse(&mevent);
         if (ok == OK) {
            if (mevent.y >= panel->y + 1 && mevent.y < LINES - 1) {
               Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);
               doRefresh = false;
               refreshTimeout = resetRefreshTimeout;
               follow = true;
               continue;
            } if (mevent.y == LINES - 1) {
               FunctionBar* bar;
               if (incSearchMode) bar = searchBar;
               else bar = defaultBar;
               ch = FunctionBar_synthesizeEvent(bar, mevent.x);
            }

         }
      }

      switch (ch) {
      case KEY_RESIZE:
         Panel_resize(panel, COLS, LINES-headerHeight-1);
         if (incSearchMode)
            FunctionBar_draw(searchBar, incSearchBuffer);
         else
            FunctionBar_draw(defaultBar, NULL);
         break;
      case 'M':
      {
         refreshTimeout = 0;
         pl->sortKey = PERCENT_MEM;
         pl->treeView = false;
         settings->changed = true;
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         break;
      }
      case 'T':
      {
         refreshTimeout = 0;
         pl->sortKey = TIME;
         pl->treeView = false;
         settings->changed = true;
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         break;
      }
      case 'U':
      {
         for (int i = 0; i < Panel_getSize(panel); i++) {
            Process* p = (Process*) Panel_get(panel, i);
            p->tag = false;
         }
         doRefresh = true;
         break;
      }
      case 'P':
      {
         refreshTimeout = 0;
         pl->sortKey = PERCENT_CPU;
         pl->treeView = false;
         settings->changed = true;
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         break;
      }
      case KEY_F(1):
      case 'h':
      {
         showHelp(pl);
         FunctionBar_draw(defaultBar, NULL);
         refreshTimeout = 0;
         break;
      }
      case '\014': // Ctrl+L
      {
         clear();
         FunctionBar_draw(defaultBar, NULL);
         refreshTimeout = 0;
         break;
      }
      case ' ':
      {
         Process* p = (Process*) Panel_getSelected(panel);
         Process_toggleTag(p);
         Panel_onKey(panel, KEY_DOWN);
         break;
      }
      case 's':
      {
         TraceScreen* ts = TraceScreen_new((Process*) Panel_getSelected(panel));
         TraceScreen_run(ts);
         TraceScreen_delete(ts);
         clear();
         FunctionBar_draw(defaultBar, NULL);
         refreshTimeout = 0;
         CRT_enableDelay();
         break;
      }
      case 'S':
      case 'C':
      case KEY_F(2):
      {
         Setup_run(settings, headerHeight);
         // TODO: shouldn't need this, colors should be dynamic
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         headerHeight = Header_calculateHeight(header);
         Panel_move(panel, 0, headerHeight);
         Panel_resize(panel, COLS, LINES-headerHeight-1);
         FunctionBar_draw(defaultBar, NULL);
         refreshTimeout = 0;
         break;
      }
      case 'F':
      {
         follow = true;
         continue;
      }
      case 'u':
      {
         Panel* usersPanel = Panel_new(0, 0, 0, 0, LISTITEM_CLASS, true, ListItem_compare);
         Panel_setHeader(usersPanel, "Show processes of:");
         UsersTable_foreach(ut, addUserToList, usersPanel);
         Vector_sort(usersPanel->items);
         ListItem* allUsers = ListItem_new("All users", -1);
         Panel_insert(usersPanel, 0, (Object*) allUsers);
         char* fuFunctions[2] = {"Show    ", "Cancel "};
         ListItem* picked = (ListItem*) pickFromList(panel, usersPanel, 20, headerHeight, fuFunctions, defaultBar);
         if (picked) {
            if (picked == allUsers) {
               userOnly = false;
               break;
            } else {
               setUserOnly(ListItem_getRef(picked), &userOnly, &userId);
            }
         }
         break;
      }
      case KEY_F(9):
      case 'k':
      {
         if (!killPanel) {
            killPanel = (Panel*) SignalsPanel_new(0, 0, 0, 0);
         }
         SignalsPanel_reset((SignalsPanel*) killPanel);
         char* fuFunctions[2] = {"Send  ", "Cancel "};
         Signal* signal = (Signal*) pickFromList(panel, killPanel, 15, headerHeight, fuFunctions, defaultBar);
         if (signal) {
            if (signal->number != 0) {
               Panel_setHeader(panel, "Sending...");
               Panel_draw(panel, true);
               refresh();
               bool anyTagged = false;
               for (int i = 0; i < Panel_getSize(panel); i++) {
                  Process* p = (Process*) Panel_get(panel, i);
                  if (p->tag) {
                     Process_sendSignal(p, signal->number);
                     anyTagged = true;
                  }
               }
               if (!anyTagged) {
                  Process* p = (Process*) Panel_getSelected(panel);
                  Process_sendSignal(p, signal->number);
               }
               napms(500);
            }
         }
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         refreshTimeout = 0;
         break;
      }
      case KEY_F(10):
      case 'q':
         quit = 1;
         break;
      case '<':
      case ',':
      case KEY_F(18):
      case '>':
      case '.':
      case KEY_F(6):
      {
         Panel* sortPanel = Panel_new(0, 0, 0, 0, LISTITEM_CLASS, true, ListItem_compare);
         Panel_setHeader(sortPanel, "Sort by");
         char* fuFunctions[2] = {"Sort  ", "Cancel "};
         ProcessField* fields = pl->fields;
         for (int i = 0; fields[i]; i++) {
            char* name = String_trim(Process_printField(fields[i]));
            Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));
            if (fields[i] == pl->sortKey)
               Panel_setSelected(sortPanel, i);
            free(name);
         }
         ListItem* field = (ListItem*) pickFromList(panel, sortPanel, 15, headerHeight, fuFunctions, defaultBar);
         if (field) {
            pl->treeView = false;
            settings->changed = true;
            pl->sortKey = field->key;
         }
         ((Object*)sortPanel)->delete((Object*)sortPanel);
         Panel_setRichHeader(panel, ProcessList_printHeader(pl));
         refreshTimeout = 0;
         break;
      }
      case 'I':
      case KEY_F(4):
      {
         refreshTimeout = 0;
         settings->changed = true;
         ProcessList_invertSortOrder(pl);
         break;
      }
      case KEY_F(8):
      case '[':
      case '=':
      case '+':
      {
         doRefresh = changePriority(panel, 1);
         break;
      }
      case KEY_F(7):
      case ']':
      case '-':
      {
         doRefresh = changePriority(panel, -1);
         break;
      }
      case KEY_F(3):
      case '/':
         FunctionBar_draw(searchBar, incSearchBuffer);
         incSearchMode = true;
         break;
      case 't':
      case KEY_F(5):
         refreshTimeout = 0;
         pl->treeView = !pl->treeView;
         settings->changed = true;
         break;
      case 'H':
         refreshTimeout = 0;
         pl->hideThreads = !pl->hideThreads;
         settings->changed = true;
         break;
      case 'K':
         refreshTimeout = 0;
         pl->hideKernelThreads = !pl->hideKernelThreads;
         settings->changed = true;
         break;
      default:
         doRefresh = false;
         refreshTimeout = resetRefreshTimeout;
         Panel_onKey(panel, ch);
         break;
      }
      follow = false;
   }
   attron(CRT_colors[RESET_COLOR]);
   mvhline(LINES-1, 0, ' ', COLS);
   attroff(CRT_colors[RESET_COLOR]);
   refresh();
   
   CRT_done();
   if (settings->changed)
      Settings_write(settings);
   Header_delete(header);
   ProcessList_delete(pl);
   FunctionBar_delete((Object*)searchBar);
   FunctionBar_delete((Object*)defaultBar);
   ((Object*)panel)->delete((Object*)panel);
   if (killPanel)
      ((Object*)killPanel)->delete((Object*)killPanel);
   UsersTable_delete(ut);
   Settings_delete(settings);
   debug_done();
   return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1