/*
 * ftop - show progress of open files and file systems
 * Copyright (C) 2009  Jason Todd <jtodd1@earthlink.net>
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* ui.c - ftop user interface implementations */

#include <stdio.h>          /* fprintf, stderr, vsnprintf, printf, fflush, */
                            /* stdout */
#include <unistd.h>         /* isatty, STDIN_FILENO, STDOUT_FILENO, */
                            /* tcgetattr, tcsetattr, read */
#ifdef HAVE_LIBCURSES
#include <curses.h>         /* initscr, noecho, nodelay, TRUE, keypad, */
                            /* getmaxyx, attrset, A_NORMAL, endwin, printw, */
                            /* move, attron, attroff, clrtobot, refresh, */
                            /* getch, A_BOLD, KEY_BACKSPACE */
#endif
#include <sys/ioctl.h>      /* ioctl, TIOCGWINSZ */
#include <string.h>         /* strerror, memcpy, strlen */
#include <errno.h>          /* errno */
#include <termios.h>        /* IGNBRK, BRKINT, PARMRK, ISTRIP, INLCR, IGNCR, */
                            /* ICRNL, IXON, ECHO, ECHONL, ICANON, ISIG, */
                            /* IEXTEN, CSIZE, PARENB, CS8, VMIN, VTIME, */
                            /* TCSAFLUSH */
#include <stdarg.h>         /* va_list, va_start, va_end */
#include <stdlib.h>         /* free, malloc */
#include <ctype.h>          /* isprint, isspace */
#include <sys/time.h>       /* gettimeofday */
#include <time.h>           /* gettimeofday */

#include "ftop.h"
#include "ui.h"

int init_terminal(ftop_data *p)
{
    if (!isatty(STDIN_FILENO))
    {
        fprintf(stderr, "Error: stdin must come from the terminal\n");
        return 0;
    }

    if (p->mode == MODE_AUTO)
    {
        if (isatty(STDOUT_FILENO))
            p->mode = MODE_FULL;
        else
            p->mode = MODE_LIMITED;
    }

    if (p->mode == MODE_FULL)
    {
        if (isatty(STDOUT_FILENO))  /* check, in case of not autodetecting */
        {
#ifdef HAVE_LIBCURSES
            initscr();
            noecho();
            nodelay(stdscr, TRUE);
            keypad(stdscr, TRUE);
            getmaxyx(stdscr, p->term_size.ws_row, p->term_size.ws_col);
            attrset(A_NORMAL);
            return 1;
#endif
        }

        p->mode = MODE_LIMITED;
    }

    if ((ioctl(STDIN_FILENO, TIOCGWINSZ, &(p->term_size)) < 0) ||
        (p->term_size.ws_row == 0) || (p->term_size.ws_col == 0))
    {
        fprintf(stderr, "Error: could not get terminal size (%s)\n",
                strerror(errno));

        return 0;
    }

    if (tcgetattr(STDIN_FILENO, &(p->term_orig)) < 0)
    {
        fprintf(stderr, "Error: could not get terminal settings (%s)\n",
                strerror(errno));

        return 0;
    }

    /* same as cfmakeraw(), but without: p->term_raw.c_oflag &= ~OPOST; */
    memcpy(&(p->term_raw), &(p->term_orig), sizeof(p->term_raw));
    p->term_raw.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR |
                             IGNCR | ICRNL | IXON);

    p->term_raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    p->term_raw.c_cflag &= ~(CSIZE | PARENB);
    p->term_raw.c_cflag |= CS8;

    /* set 0 minimum chars for read completion, timeout 0.0 seconds */
    p->term_raw.c_cc[VMIN] = 0;
    p->term_raw.c_cc[VTIME] = 0;

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &(p->term_raw)) < 0)
    {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &(p->term_orig));
        fprintf(stderr, "Error: could not get terminal settings (%s)\n",
                strerror(errno));

        return 0;
    }

    return 1;
}

int restore_terminal(ftop_data *p)
{
    if (p->mode == MODE_FULL)
    {
#ifdef HAVE_LIBCURSES
        if (endwin() == ERR)
            return 0;
#endif
    }
    else if (p->mode == MODE_LIMITED)
    {
        if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &(p->term_orig)) < 0)
        {
            fprintf(stderr,
                    "Error: could not restore terminal settings (%s)\n",
                    strerror(errno));

            return 0;
        }
    }

    p->mode = MODE_AUTO;
    return 1;
}

int p_printf(ftop_data *p, const char *fmt, ...)
{
    int remain_width;
    size_t bytes;
    va_list arg;

    remain_width = p->term_size.ws_col - p->cur_col;
    if (!(p->do_printf) || (remain_width < 2))
        return 0;

    if (p->term_size.ws_col > p->partial_buf_size)
    {
        if (p->partial_buf != NULL)
            free(p->partial_buf);

        p->partial_buf_size = p->term_size.ws_col;
        if ((p->partial_buf = malloc(p->partial_buf_size)) == NULL)
        {
            p->partial_buf_size = 0;
            return 1;
        }
    }

    va_start(arg, fmt);
    bytes = vsnprintf(p->partial_buf, remain_width, fmt, arg);
    va_end(arg);

    if ((bytes > remain_width) && (remain_width >= 4))
    {
        p->partial_buf[remain_width - 2] = '.';
        p->partial_buf[remain_width - 3] = '.';
        p->partial_buf[remain_width - 4] = '.';
    }

    p->cur_col += bytes;

    if (p->mode == MODE_FULL)
    {
#ifdef HAVE_LIBCURSES
        printw("%s", p->partial_buf);
#endif
    }
    else
    {
        printf("%s", p->partial_buf);
    }

    return 0;
}

int p_repeat(ftop_data *p, int cols, int min, char c)
{
    int i;

    if (cols <= 0)
        cols = min;

    for (i = 0; i < cols; i++)
        p_printf(p, "%c", c);

    return 0;
}

int p_eol(ftop_data *p, int part)
{
    int cur_row;

    if (p->do_printf || (part == 2) || (p->scrolled_rows != 0))
    {
        if (p->do_printf)
        {
            if (p->mode == MODE_FULL)
            {
                p_repeat(p, p->term_size.ws_col - p->cur_col, 0, ' ');
#ifdef HAVE_LIBCURSES
                printw("\n");
#endif
            }
            else
            {
                printf("\n");
                fflush(stdout);
            }
        }

        p->cur_row++;
    }

    p->cur_col = 0;
    p->output_rows++;
    cur_row = p->cur_row;
    if ((p->scrolled_rows != 0) && (cur_row >= SCROLL_BASE))
    {
        if (cur_row < (SCROLL_BASE + p->scrolled_rows))
        {
            p->do_printf = 0;
        }
        else
        {
            cur_row -= p->scrolled_rows;
            p->do_printf = 1;
        }
    }

    if ((part == 1) && (cur_row >= (p->term_size.ws_row - 1)))
    {
        if ((p->mode != MODE_FULL) ||
            (cur_row != (p->term_size.ws_row - 1)))
        {
            p->do_printf = 0;
        }
    }
    else if ((part == 2) && (cur_row >= p->shift_rows))
    {
        p->do_printf = 1;
    }

    return 0;
}

int p_move(ftop_data *p, int y, int x)
{
    p->cur_row = y;
    p->cur_col = x;

#ifdef HAVE_LIBCURSES
    if (p->mode == MODE_FULL)
        return move(y, x);
#endif

    return 0;
}

int p_attron(ftop_data *p, int attrs)
{
#ifdef HAVE_LIBCURSES
    if (p->mode == MODE_FULL)
        return attron(attrs);
#endif

    return 0;
}

int p_attroff(ftop_data *p, int attrs)
{
#ifdef HAVE_LIBCURSES
    if (p->mode == MODE_FULL)
        return attroff(attrs);
#endif

    return 0;
}

int p_endoutput(ftop_data *p)
{
#ifdef HAVE_LIBCURSES
    if (p->mode == MODE_FULL)
        return clrtobot();
#endif

    return 0;
}

int p_refresh(ftop_data *p)
{
#ifdef HAVE_LIBCURSES
    if (p->mode == MODE_FULL)
    {
        getmaxyx(stdscr, p->term_size.ws_row, p->term_size.ws_col);
        return refresh();
    }
#endif

    if (ioctl(STDIN_FILENO, TIOCGWINSZ, &(p->term_size)) < 0)
    {
        fprintf(stderr, "Error: could not get terminal size (%s)\n",
                strerror(errno));

        return 1;
    }

    fflush(stdout);
    return 0;
}

int p_getkey(ftop_data *p, int *c)
{
    int key;
    char read_c;

    key = 0;
    if (p->mode == MODE_FULL)
    {
#ifdef HAVE_LIBCURSES
        if ((key = getch()) == ERR)
            return 0;
#else
        return 0;
#endif
    }
    else if (p->mode == MODE_LIMITED)
    {
        if (read(STDIN_FILENO, &read_c, 1) != 1)
            return 0;

        key = read_c;
    }

    if ((key == 0x7F) || (key == KEY_BACKSPACE))
        key = '\b';

    if (key == '\r')
        key = '\n';

    *c = key;
    return 1;
}

int show_paragraph(ftop_data *p, char *str, int indent, int part)
{
    int done, bold;
    size_t len, idx, tab_count, newline_count;
    char *next;

    if ((len = strlen(str)) == 0)
        return 1;

    newline_count = 0;
    while (str[--len] == '\n')
        newline_count++;

    idx = 0;
    tab_count = 1;
    bold = 0;
    next = str;
    while (!isprint(*next))
    {
        if (*next == '\b')
        {
            if (!bold)
                p_attron(p, A_BOLD);

            bold = 1;
        }
        else if (*next == '\t')
        {
            tab_count++;
        }
        else if (*next == '\n')
        {
            p_eol(p, part);
        }

        next++;
    }

    indent *= tab_count;
    do
    {
        if ((len = strlen(next)) == 0)
            break;

        p_repeat(p, indent, 0, ' ');
        done = 0;
        if (len > (p->term_size.ws_col - indent - 2))
            len = p->term_size.ws_col - indent - 2;
        else
            done = 1;

        memcpy(p->scratch_buf, next, len);
        idx = len - 1;
        if (!done)
        {
            while ((idx > 0) && !isspace(p->scratch_buf[idx]))
                idx--;

            if (idx == 0)
                p->scratch_buf[len++] = '-';
            else
                len = idx;
        }

        p->scratch_buf[len++] = '\0';
        while ((idx > 0) && isspace(p->scratch_buf[idx]))
            p->scratch_buf[idx--] = '\0';

        next += len;
        if (!done && (idx == 0))
            next -= 2;

        p_printf(p, "%s", p->scratch_buf);
        p_eol(p, part);
    } while (!done);

    if (bold)
        p_attroff(p, A_BOLD);

    while (newline_count-- != 0)
        p_eol(p, part);

    return 1;
}

int p_input_key(ftop_data *p, char c)
{
    size_t idx;

    idx = strlen(p->input_buf);
    if (c == '\n')
    {
        p_eol(p, 1);
        p_refresh(p);
        if (!(p->parse_input(p, p->input_buf)))
        {
            p->error = "Error: invalid value '%s'";
            gettimeofday(&(p->error_time), NULL);
        }
        else
        {
           p->prompt = NULL;
        }

        return 1;
    }
    else if (c == '\b')
    {
        if (idx > 0)
        {
            if (p->input_buf[idx - 1] == '.')
                p->input_dots--;
            else if (p->input_buf[idx - 1] == '=')
                p->input_need_eq = 1;
            else if (p->input_buf[idx - 1] == ',')
                p->input_need_eq = 0;

            p->input_buf[--idx] = '\0';
            if (p->mode == MODE_FULL)
            {
#ifdef HAVE_LIBCURSES
                printw("\b \b");
#endif
            }
            else
            {
                printf("\b \b");
            }

            p->cur_col--;
            p_refresh(p);
        }

        return 1;
    }
    else if (isprint(c))
    {
        if ((p->input_type == READ_TYPE_NUM) && ((c < '0') || (c > '9')))
            return 0;

        if ((p->input_type == READ_TYPE_NUM_FRAC) &&
            ((((c < '0') || (c > '9')) && (c != '.')) ||
             ((c == '.') && (p->input_dots != 0)) ||
             ((idx >= 3) && (p->input_buf[idx - 3] == '.'))))
        {
            return 0;
        }

        if ((p->input_type == READ_TYPE_LIST) && (c == ',') &&
            ((idx == 0) || (p->input_buf[idx - 1] == ',')))
        {
            return 0;
        }

        if ((p->input_type == READ_TYPE_LIST_EQ) &&
            (((c == ',') && p->input_need_eq) ||
             ((c == '=') && !(p->input_need_eq))))
        {
            return 0;
        }

        if (idx < (p->input_max - 1))
        {
            p->input_buf[idx++] = c;
            p->input_buf[idx] = '\0';
            p_printf(p, "%c", c);
            p_refresh(p);
            if (c == '.')
                p->input_dots++;
            else if (c == '=')
                p->input_need_eq = 0;
            else if (c == ',')
                p->input_need_eq = 1;
        }

        return 1;
    }

    return 0;
}
