#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <map>
#include <algorithm>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#include <sys/time.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>

#include <locale.h>

#if HAVE_NCURSES_H
#include <ncurses.h>
#else
#include <curses.h>
#endif

#if HAVE_TERM_H
#include <term.h>
#elif HAVE_NCURSES_TERM_H
#include <ncurses/term.h>
#endif

#include <signal.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#include <errno.h>
#include <vector>

#include "fallback.h"
#include "util.h"

#include "wutil.h"
#include "common.h"
#include "complete.h"
#include "output.h"
#include "input_common.h"
#include "env_universal.h"
#include "print_help.h"

enum
{
    LINE_UP = R_NULL+1,
    LINE_DOWN,
    PAGE_UP,
    PAGE_DOWN
}
;


enum
{
    HIGHLIGHT_PAGER_PREFIX,
    HIGHLIGHT_PAGER_COMPLETION,
    HIGHLIGHT_PAGER_DESCRIPTION,
    HIGHLIGHT_PAGER_PROGRESS,
    HIGHLIGHT_PAGER_SECONDARY
}
;

enum
{
    /*
      Returnd by the pager if no more displaying is needed
    */
    PAGER_DONE,
    /*
      Returned by the pager if the completions would not fit in the specified number of columns
    */
    PAGER_RETRY,
    /*
      Returned by the pager if the terminal changes size
    */
    PAGER_RESIZE
}
;

/**
   The minimum width (in characters) the terminal may have for fish_pager to not refuse showing the completions
*/
#define PAGER_MIN_WIDTH 16

/**
   The maximum number of columns of completion to attempt to fit onto the screen
*/
#define PAGER_MAX_COLS 6

/**
   The string describing the single-character options accepted by fish_pager
*/
#define GETOPT_STRING "c:hr:qvp:"

/**
   Error to use when given an invalid file descriptor for reading completions or writing output
*/
#define ERR_NOT_FD _( L"%ls: Argument '%s' is not a valid file descriptor\n" )

/**
   This struct should be continually updated by signals as the term
   resizes, and as such always contain the correct current size.
*/
static struct winsize termsize;

/**
   The termios modes the terminal had when the program started. These
   should be restored on exit
*/
static struct termios saved_modes;

/**
   This flag is set to 1 of we have sent the enter_ca_mode terminfo
   sequence to save the previous terminal contents.
*/
static int is_ca_mode = 0;

/**
   This buffer is used to buffer the output of the pager to improve
   screen redraw performance bu cutting down the number of write()
   calls to only one.
*/
static std::vector<char> pager_buffer;

/**
   The environment variables used to specify the color of different
   tokens.
*/
static const wchar_t *hightlight_var[] =
{
    L"fish_pager_color_prefix",
    L"fish_pager_color_completion",
    L"fish_pager_color_description",
    L"fish_pager_color_progress",
    L"fish_pager_color_secondary"
}
;

/**
   This string contains the text that should be sent back to the calling program
*/
static wcstring out_buff;
/**
   This is the file to which the output text should be sent. It is really a pipe.
*/
static FILE *out_file;

/**
   Data structure describing one or a group of related completions
*/
struct comp_t
{
    /**
       The list of all completin strings this entry applies to
    */
    wcstring_list_t comp;
    /**
       The description
    */
    wcstring desc;
    /**
       On-screen width of the completion string
    */
    int comp_width;
    /**
       On-screen width of the description information
    */
    int desc_width;
    /**
       Preffered total width
    */
    int pref_width;
    /**
       Minimum acceptable width
    */
    int min_width;
};

/**
   This function translates from a highlight code to a specific color
   by check invironement variables
*/
static rgb_color_t get_color(int highlight)
{
    const wchar_t *val;

    if (highlight < 0)
        return rgb_color_t::normal();
    if (highlight >= (5))
        return rgb_color_t::normal();

    val = wgetenv(hightlight_var[highlight]);

    if (!val)
    {
        val = env_universal_get(hightlight_var[highlight]);
    }

    if (!val)
    {
        return rgb_color_t::normal();
    }

    return parse_color(val, false);
}

/**
   This function calculates the minimum width for each completion
   entry in the specified array_list. This width depends on the
   terminal size, so this function should be called when the terminal
   changes size.
*/
static void recalc_width(std::vector<comp_t *> &lst, const wchar_t *prefix)
{
    for (size_t i=0; i<lst.size(); i++)
    {
        comp_t *c = lst.at(i);

        c->min_width = mini(c->desc_width, maxi(0,termsize.ws_col/3 - 2)) +
                       mini(c->desc_width, maxi(0,termsize.ws_col/5 - 4)) +4;
    }

}

/**
   Test if the specified character sequence has been entered on the
   keyboard
*/
static int try_sequence(const char *seq)
{
    int j, k;
    wint_t c=0;

    for (j=0;
            seq[j] != '\0' && seq[j] == (c=input_common_readch(j>0));
            j++)
        ;

    if (seq[j] == '\0')
    {
        return 1;
    }
    else
    {
        input_common_unreadch(c);
        for (k=j-1; k>=0; k--)
            input_common_unreadch(seq[k]);
    }
    return 0;
}

/**
   Read a character from keyboard
*/
static wint_t readch()
{
    struct mapping
    {
        const char *seq;
        wint_t bnd;
    }
    ;

    struct mapping m[]=
    {
        {
            "\x1b[A", LINE_UP
        }
        ,
        {
            key_up, LINE_UP
        }
        ,
        {
            "\x1b[B", LINE_DOWN
        }
        ,
        {
            key_down, LINE_DOWN
        }
        ,
        {
            key_ppage, PAGE_UP
        }
        ,
        {
            key_npage, PAGE_DOWN
        }
        ,
        {
            " ", PAGE_DOWN
        }
        ,
        {
            "\t", PAGE_DOWN
        }
        ,
        {
            0, 0
        }

    }
    ;
    int i;

    for (i=0; m[i].bnd; i++)
    {
        if (!m[i].seq)
        {
            continue;
        }

        if (try_sequence(m[i].seq))
            return m[i].bnd;
    }
    return input_common_readch(0);
}

/**
   Write specified character to the output buffer \c pager_buffer
*/
static int pager_buffered_writer(char c)
{
    pager_buffer.push_back(c);
    return 0;
}

/**
   Flush \c pager_buffer to stdout
*/
static void pager_flush()
{
    if (! pager_buffer.empty())
    {
        write_loop(1, & pager_buffer.at(0), pager_buffer.size() * sizeof(char));
        pager_buffer.clear();
    }
}

/**
   Print the specified string, but use at most the specified amount of
   space. If the whole string can't be fitted, ellipsize it.

   \param str the string to print
   \param max the maximum space that may be used for printing
   \param has_more if this flag is true, this is not the entire string, and the string should be ellisiszed even if the string fits but takes up the whole space.
*/
static int print_max(const wchar_t *str, int max, int has_more)
{
    int i;
    int written = 0;
    for (i=0; str[i]; i++)
    {

        if (written + wcwidth(str[i]) > max)
            break;
        if ((written + wcwidth(str[i]) == max) && (has_more || str[i+1]))
        {
            writech(ellipsis_char);
            written += wcwidth(ellipsis_char);
            break;
        }

        writech(str[i]);
        written+= wcwidth(str[i]);
    }
    return written;
}

/**
   Print the specified item using at the specified amount of space
*/
static void completion_print_item(const wchar_t *prefix, comp_t *c, int width, bool secondary)
{
    int comp_width=0, desc_width=0;
    int written=0;

    if (c->pref_width <= width)
    {
        /*
          The entry fits, we give it as much space as it wants
        */
        comp_width = c->comp_width;
        desc_width = c->desc_width;
    }
    else
    {
        /*
          The completion and description won't fit on the
          allocated space. Give a maximum of 2/3 of the
          space to the completion, and whatever is left to
          the description.
        */
        int desc_all = c->desc_width?c->desc_width+4:0;

        comp_width = maxi(mini(c->comp_width,
                               2*(width-4)/3),
                          width - desc_all);
        if (c->desc_width)
            desc_width = width-comp_width-4;
        else
            c->desc_width=0;

    }

    rgb_color_t bg = secondary ? get_color(HIGHLIGHT_PAGER_SECONDARY) : rgb_color_t::normal();
    for (size_t i=0; i<c->comp.size(); i++)
    {
        const wcstring &comp = c->comp.at(i);
        if (i != 0)
            written += print_max(L"  ", comp_width - written, 2);
        set_color(get_color(HIGHLIGHT_PAGER_PREFIX), bg);
        written += print_max(prefix, comp_width - written, comp.empty()?0:1);
        set_color(get_color(HIGHLIGHT_PAGER_COMPLETION), bg);
        written += print_max(comp.c_str(), comp_width - written, i!=(c->comp.size()-1));
    }


    if (desc_width)
    {
        while (written < (width-desc_width-2))
        {
            written++;
            writech(L' ');
        }
        set_color(get_color(HIGHLIGHT_PAGER_DESCRIPTION), bg);
        written += print_max(L"(", 1, 0);
        written += print_max(c->desc.c_str(), desc_width, 0);
        written += print_max(L")", 1, 0);
    }
    else
    {
        while (written < width)
        {
            written++;
            writech(L' ');
        }
    }
    if (secondary)
        set_color(rgb_color_t::normal(), rgb_color_t::normal());
}

/**
   Print the specified part of the completion list, using the
   specified column offsets and quoting style.

   \param l The list of completions to print
   \param cols number of columns to print in
   \param width An array specifying the width of each column
   \param row_start The first row to print
   \param row_stop the row after the last row to print
   \param prefix The string to print before each completion
   \param is_quoted Whether to print the completions are in a quoted environment
*/

static void completion_print(int cols,
                             int *width,
                             int row_start,
                             int row_stop,
                             const wchar_t *prefix,
                             int is_quoted,
                             const std::vector<comp_t *> &lst)
{

    size_t rows = (lst.size()-1)/cols+1;
    size_t i, j;

    for (i = row_start; i<row_stop; i++)
    {
        for (j = 0; j < cols; j++)
        {
            comp_t *el;

            int is_last = (j==(cols-1));

            if (lst.size() <= j*rows + i)
                continue;

            el = lst.at(j*rows + i);

            completion_print_item(prefix, el, width[j] - (is_last?0:2), i%2);

            if (!is_last)
                writestr(L"  ");
        }
        writech(L'\n');
    }
}


/**
   Try to print the list of completions l with the prefix prefix using
   cols as the number of columns. Return 1 if the completion list was
   printed, 0 if the terminal is to narrow for the specified number of
   columns. Always succeeds if cols is 1.

   If all the elements do not fit on the screen at once, make the list
   scrollable using the up, down and space keys to move. The list will
   exit when any other key is pressed.

   \param cols the number of columns to try to fit onto the screen
   \param prefix the character string to prefix each completion with
   \param is_quoted whether the completions should be quoted
   \param l the list of completions

   \return one of PAGER_RETRY, PAGER_DONE and PAGER_RESIZE
*/

static int completion_try_print(int cols,
                                const wchar_t *prefix,
                                int is_quoted,
                                std::vector<comp_t *> &lst)
{
    /*
      The calculated preferred width of each column
    */
    int pref_width[PAGER_MAX_COLS];
    /*
      The calculated minimum width of each column
    */
    int min_width[PAGER_MAX_COLS];
    /*
      If the list can be printed with this width, width will contain the width of each column
    */
    int *width=pref_width;
    /*
      Set to one if the list should be printed at this width
    */
    int print=0;

    long i, j;

    int rows = (int)((lst.size()-1)/cols+1);

    int pref_tot_width=0;
    int min_tot_width = 0;
    int res=PAGER_RETRY;
    /*
      Skip completions on tiny terminals
    */

    if (termsize.ws_col < PAGER_MIN_WIDTH)
        return PAGER_DONE;

    memset(pref_width, 0, sizeof(pref_width));
    memset(min_width, 0, sizeof(min_width));

    /* Calculate how wide the list would be */
    for (j = 0; j < cols; j++)
    {
        for (i = 0; i<rows; i++)
        {
            int pref,min;
            comp_t *c;
            if (lst.size() <= j*rows + i)
                continue;

            c = lst.at(j*rows + i);
            pref = c->pref_width;
            min = c->min_width;

            if (j != cols-1)
            {
                pref += 2;
                min += 2;
            }
            min_width[j] = maxi(min_width[j],
                                min);
            pref_width[j] = maxi(pref_width[j],
                                 pref);
        }
        min_tot_width += min_width[j];
        pref_tot_width += pref_width[j];
    }
    /*
      Force fit if one column
    */
    if (cols == 1)
    {
        if (pref_tot_width > termsize.ws_col)
        {
            pref_width[0] = termsize.ws_col;
        }
        width = pref_width;
        print=1;
    }
    else if (pref_tot_width <= termsize.ws_col)
    {
        /* Terminal is wide enough. Print the list! */
        width = pref_width;
        print=1;
    }
    else
    {
        long next_rows = (lst.size()-1)/(cols-1)+1;
        /*    fwprintf( stderr,
          L"cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d\n",
          cols,
          min_tot_width, termsize.ws_col,
          rows, next_rows, termsize.ws_row,
          pref_tot_width-termsize.ws_col );
        */
        if (min_tot_width < termsize.ws_col &&
                (((rows < termsize.ws_row) && (next_rows >= termsize.ws_row)) ||
                 (pref_tot_width-termsize.ws_col< 4 && cols < 3)))
        {
            /*
              Terminal almost wide enough, or squeezing makes the
              whole list fit on-screen.

              This part of the code is really important. People hate
              having to scroll through the completion list. In cases
              where there are a huge number of completions, it can't
              be helped, but it is not uncommon for the completions to
              _almost_ fit on one screen. In those cases, it is almost
              always desirable to 'squeeze' the completions into a
              single page.

              If we are using N columns and can get everything to
              fit using squeezing, but everything would also fit
              using N-1 columns, don't try.
            */

            int tot_width = min_tot_width;
            width = min_width;

            while (tot_width < termsize.ws_col)
            {
                for (i=0; (i<cols) && (tot_width < termsize.ws_col); i++)
                {
                    if (width[i] < pref_width[i])
                    {
                        width[i]++;
                        tot_width++;
                    }
                }
            }
            print=1;
        }
    }

    if (print)
    {
        res=PAGER_DONE;
        if (rows < termsize.ws_row)
        {
            /* List fits on screen. Print it and leave */
            if (is_ca_mode)
            {
                is_ca_mode = 0;
                writembs(exit_ca_mode);
            }

            completion_print(cols, width, 0, rows, prefix, is_quoted, lst);
            pager_flush();
        }
        else
        {
            int npos, pos = 0;
            int do_loop = 1;

            /*
              Enter ca_mode, which means that the terminal
              content will be restored to the current
              state on exit.
            */
            if (enter_ca_mode && exit_ca_mode)
            {
                is_ca_mode=1;
                writembs(enter_ca_mode);
            }


            completion_print(cols,
                             width,
                             0,
                             termsize.ws_row-1,
                             prefix,
                             is_quoted,
                             lst);
            /*
              List does not fit on screen. Print one screenfull and
              leave a scrollable interface
            */
            while (do_loop)
            {
                set_color(rgb_color_t::black(), get_color(HIGHLIGHT_PAGER_PROGRESS));
                wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+termsize.ws_row-1, rows);
                msg.append(L"   \r");

                writestr(msg.c_str());
                set_color(rgb_color_t::normal(), rgb_color_t::normal());
                pager_flush();
                int c = readch();

                switch (c)
                {
                    case LINE_UP:
                    {
                        if (pos > 0)
                        {
                            pos--;
                            writembs(tparm(cursor_address, 0, 0));
                            writembs(scroll_reverse);
                            completion_print(cols,
                                             width,
                                             pos,
                                             pos+1,
                                             prefix,
                                             is_quoted,
                                             lst);
                            writembs(tparm(cursor_address,
                                           termsize.ws_row-1, 0));
                            writembs(clr_eol);

                        }

                        break;
                    }

                    case LINE_DOWN:
                    {
                        if (pos <= (rows - termsize.ws_row))
                        {
                            pos++;
                            completion_print(cols,
                                             width,
                                             pos+termsize.ws_row-2,
                                             pos+termsize.ws_row-1,
                                             prefix,
                                             is_quoted,
                                             lst);
                        }
                        break;
                    }

                    case PAGE_DOWN:
                    {

                        npos = mini((int)(rows - termsize.ws_row+1), (int)(pos + termsize.ws_row-1));
                        if (npos != pos)
                        {
                            pos = npos;
                            completion_print(cols,
                                             width,
                                             pos,
                                             pos+termsize.ws_row-1,
                                             prefix,
                                             is_quoted,
                                             lst);
                        }
                        else
                        {
                            if (flash_screen)
                                writembs(flash_screen);
                        }

                        break;
                    }

                    case PAGE_UP:
                    {
                        npos = maxi(0,
                                    pos - termsize.ws_row+1);

                        if (npos != pos)
                        {
                            pos = npos;
                            completion_print(cols,
                                             width,
                                             pos,
                                             pos+termsize.ws_row-1,
                                             prefix,
                                             is_quoted,
                                             lst);
                        }
                        else
                        {
                            if (flash_screen)
                                writembs(flash_screen);
                        }
                        break;
                    }

                    case R_NULL:
                    {
                        do_loop=0;
                        res=PAGER_RESIZE;
                        break;

                    }

                    default:
                    {
                        out_buff.push_back(c);
                        do_loop = 0;
                        break;
                    }
                }
            }
            writembs(clr_eol);
        }
    }
    return res;
}

/**
   Substitute any series of whitespace with a single space character
   inside completion descriptions. Remove all whitespace from
   beginning/end of completion descriptions.
*/
static void mangle_descriptions(wcstring_list_t &lst)
{
    int skip;
    for (size_t i=0; i<lst.size(); i++)
    {
        wcstring &next = lst.at(i);
        size_t in, out;
        skip=1;

        size_t next_idx = 0;
        while (next_idx < next.size() && next[next_idx] != COMPLETE_SEP)
            next_idx++;

        if (next_idx == next.size())
            continue;

        in=out=next_idx + 1;

        while (in < next.size())
        {
            if (next[in] == L' ' || next[in]==L'\t' || next[in]<32)
            {
                if (!skip)
                    next[out++]=L' ';
                skip=1;
            }
            else
            {
                next[out++] = next[in];
                skip=0;
            }
            in++;
        }
        next.resize(out);
    }
}

/**
   Merge multiple completions with the same description to the same line
*/
static void join_completions(wcstring_list_t lst)
{
    std::map<wcstring, long> desc_table;

    for (size_t i=0; i<lst.size(); i++)
    {
        const wchar_t *item = lst.at(i).c_str();
        const wchar_t *desc = wcschr(item, COMPLETE_SEP);
        long prev_idx;

        if (!desc)
            continue;
        desc++;
        prev_idx = desc_table[desc] - 1;
        if (prev_idx == -1)
        {
            desc_table[desc] = (long)(i+1);
        }
        else
        {
            const wchar_t *old = lst.at(i).c_str();
            const wchar_t *old_end = wcschr(old, COMPLETE_SEP);

            if (old_end)
            {

                wcstring foo;
                foo.append(old, old_end - old);
                foo.push_back(COMPLETE_ITEM_SEP);
                foo.append(item);

                lst.at(prev_idx) = foo;
                lst.at(i).clear();
            }

        }

    }

    /* Remove empty strings */
    lst.erase(remove(lst.begin(), lst.end(), wcstring()), lst.end());
}

/**
   Replace completion strings with a comp_t structure
*/
static std::vector<comp_t *> mangle_completions(wcstring_list_t &lst, const wchar_t *prefix)
{
    std::vector<comp_t *> result;
    for (size_t i=0; i<lst.size(); i++)
    {
        wcstring &next = lst.at(i);
        size_t start, end;

        comp_t zerod = {};
        comp_t *comp = new comp_t(zerod);

        for (start=end=0; 1; end++)
        {
            wchar_t c = next.c_str()[end];

            if ((c == COMPLETE_ITEM_SEP) || (c==COMPLETE_SEP) || !c)
            {
                wcstring start2 = wcstring(next, start, end - start);
                wcstring str = escape_string(start2, ESCAPE_ALL | ESCAPE_NO_QUOTED);
                comp->comp_width += my_wcswidth(str.c_str());
                comp->comp.push_back(str);
                start = end+1;
            }

            if (c == COMPLETE_SEP)
            {
                comp->desc = next.c_str() + start;
                break;
            }

            if (!c)
                break;

        }

        comp->comp_width  += (int)(my_wcswidth(prefix)*comp->comp.size() + 2*(comp->comp.size()-1));
        comp->desc_width = comp->desc.empty()?0:my_wcswidth(comp->desc.c_str());

        comp->pref_width = comp->comp_width + comp->desc_width + (comp->desc_width?4:0);

        result.push_back(comp);
    }

    recalc_width(result, prefix);
    return result;
}



/**
   Respond to a winch signal by checking the terminal size
*/
static void handle_winch(int sig)
{
    if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
    {
        return;
    }
}

/**
   The callback function that the keyboard reading function calls when
   an interrupt occurs. This makes sure that R_NULL is returned at
   once when an interrupt has occured.
*/
static int interrupt_handler()
{
    return R_NULL;
}

/**
   Initialize various subsystems. This also closes stdin and replaces
   it with a copy of stderr, so the reading of completion strings must
   be done before init is called.
*/
static void init(int mangle_descriptors, int out)
{
    struct sigaction act;

    static struct termios pager_modes;
    char *term;

    if (mangle_descriptors)
    {

        /*
          Make fd 1 output to screen, and use some other fd for writing
          the resulting output back to the caller
        */
        int in;
        out = dup(1);
        close(1);
        close(0);

        /* OK to not use CLO_EXEC here because fish_pager is single threaded */
        if ((in = open(ttyname(2), O_RDWR)) != -1)
        {
            if (dup2(2, 1) == -1)
            {
                debug(0, _(L"Could not set up output file descriptors for pager"));
                exit(1);
            }

            if (dup2(in, 0) == -1)
            {
                debug(0, _(L"Could not set up input file descriptors for pager"));
                exit(1);
            }
        }
        else
        {
            debug(0, _(L"Could not open tty for pager"));
            exit(1);
        }
    }

    if (!(out_file = fdopen(out, "w")))
    {
        debug(0, _(L"Could not initialize result pipe"));
        exit(1);
    }


    env_universal_init("", 0, 0, 0);
    input_common_init(&interrupt_handler);
    output_set_writer(&pager_buffered_writer);

    sigemptyset(& act.sa_mask);
    act.sa_flags=0;
    act.sa_handler=SIG_DFL;
    act.sa_flags = 0;
    act.sa_handler= &handle_winch;
    if (sigaction(SIGWINCH, &act, 0))
    {
        wperror(L"sigaction");
        exit(1);
    }

    handle_winch(0);                  /* Set handler for window change events */

    tcgetattr(0,&pager_modes);        /* get the current terminal modes */
    memcpy(&saved_modes,
           &pager_modes,
           sizeof(saved_modes));     /* save a copy so we can reset the terminal later */

    pager_modes.c_lflag &= ~ICANON;   /* turn off canonical mode */
    pager_modes.c_lflag &= ~ECHO;     /* turn off echo mode */
    pager_modes.c_cc[VMIN]=1;
    pager_modes.c_cc[VTIME]=0;

    /*

    */
    if (tcsetattr(0,TCSANOW,&pager_modes))      /* set the new modes */
    {
        wperror(L"tcsetattr");
        exit(1);
    }

    int errret;
    if (setupterm(0, STDOUT_FILENO, &errret) == ERR)
    {
        debug(0, _(L"Could not set up terminal"));
        exit(1);
    }

    term = getenv("TERM");
    if (term)
    {
        wcstring wterm = str2wcstring(term);
        output_set_term(wterm);
    }

    /* Infer term256 support */
    char *fish_term256 = getenv("fish_term256");
    bool support_term256;
    if (fish_term256)
    {
        support_term256 = from_string<bool>(fish_term256);
    }
    else
    {
        support_term256 = term && strstr(term, "256color");
    }
    output_set_supports_term256(support_term256);
}

/**
   Free memory used by various subsystems.
*/
static void destroy()
{
    env_universal_destroy();
    input_common_destroy();
    wutil_destroy();
    if (fish_del_curterm(cur_term) == ERR)
    {
        debug(0, _(L"Error while closing terminfo"));
    }

    fclose(out_file);
}

/**
   Read lines of input from the specified file, unescape them and
   insert them into the specified list.
*/
static void read_array(FILE* file, wcstring_list_t &comp)
{
    std::vector<char> buffer;
    int c;

    while (!feof(file))
    {
        buffer.clear();

        while (1)
        {
            c = getc(file);
            if (c == EOF)
            {
                break;
            }

            if (c == '\n')
            {
                break;
            }

            buffer.push_back(static_cast<char>(c));
        }

        if (! buffer.empty())
        {
            buffer.push_back(0);
            wcstring wcs = str2wcstring(&buffer.at(0));
            if (unescape_string(wcs, false))
            {
                comp.push_back(wcs);
            }
        }
    }

}

static int get_fd(const char *str)
{
    char *end;
    long fd;

    errno = 0;
    fd = strtol(str, &end, 10);
    if (fd < 0 || *end || errno)
    {
        debug(0, ERR_NOT_FD, program_name, optarg);
        exit(1);
    }
    return (int)fd;
}


int main(int argc, char **argv)
{
    int i;
    int is_quoted=0;
    wcstring_list_t comp;
    wcstring prefix;

    int mangle_descriptors = 0;
    int result_fd = -1;
    set_main_thread();
    setup_fork_guards();

    /*
      This initialization is made early, so that the other init code
      can use global_context for memory managment
    */
    program_name = L"fish_pager";


    wsetlocale(LC_ALL, L"");

    /*
      The call signature for fish_pager is a mess. Because we want
      to be able to upgrade fish without breaking running
      instances, we need to support all previous
      modes. Unfortunatly, the two previous ones are a mess. The
      third one is designed to be extensible, so hopefully it will
      be the last.
    */

    if (argc > 1 && argv[1][0] == '-')
    {
        /*
          Third mode
        */

        int completion_fd = -1;
        FILE *completion_file;

        while (1)
        {
            static struct option
                    long_options[] =
            {
                {
                    "result-fd", required_argument, 0, 'r'
                }
                ,
                {
                    "completion-fd", required_argument, 0, 'c'
                }
                ,
                {
                    "prefix", required_argument, 0, 'p'
                }
                ,
                {
                    "is-quoted", no_argument, 0, 'q'
                }
                ,
                {
                    "help", no_argument, 0, 'h'
                }
                ,
                {
                    "version", no_argument, 0, 'v'
                }
                ,
                {
                    0, 0, 0, 0
                }
            }
            ;

            int opt_index = 0;

            int opt = getopt_long(argc,
                                  argv,
                                  GETOPT_STRING,
                                  long_options,
                                  &opt_index);

            if (opt == -1)
                break;

            switch (opt)
            {
                case 0:
                {
                    break;
                }

                case 'r':
                {
                    result_fd = get_fd(optarg);
                    break;
                }

                case 'c':
                {
                    completion_fd = get_fd(optarg);
                    break;
                }

                case 'p':
                {
                    prefix = str2wcstring(optarg);
                    break;
                }

                case 'h':
                {
                    print_help(argv[0], 1);
                    exit(0);
                }

                case 'v':
                {
                    debug(0, L"%ls, version %s\n", program_name, PACKAGE_VERSION);
                    exit(0);
                }

                case 'q':
                {
                    is_quoted = 1;
                }

            }
        }

        if (completion_fd == -1 || result_fd == -1)
        {
            debug(0, _(L"Unspecified file descriptors"));
            exit(1);
        }


        if ((completion_file = fdopen(completion_fd, "r")))
        {
            read_array(completion_file, comp);
            fclose(completion_file);
        }
        else
        {
            debug(0, _(L"Could not read completions"));
            wperror(L"fdopen");
            exit(1);
        }

    }
    else
    {
        /*
          Second or first mode. These suck, but we need to support
          them for backwards compatibility. At least for some
          time.

          Third mode was implemented in January 2007, and previous
          modes should be considered deprecated from that point
          forward. A reasonable time frame for removal of the code
          below has yet to be determined.
        */

        if (argc < 3)
        {
            print_help(argv[0], 1);
            exit(0);
        }
        else
        {
            mangle_descriptors = 1;

            prefix = str2wcstring(argv[2]);
            is_quoted = strcmp("1", argv[1])==0;

            if (argc > 3)
            {
                /*
                  First mode
                */
                for (i=3; i<argc; i++)
                {
                    wcstring wcs = str2wcstring(argv[i]);
                    comp.push_back(wcs);
                }
            }
            else
            {
                /*
                  Second mode
                */
                read_array(stdin, comp);
            }
        }

    }

//    debug( 3, L"prefix is '%ls'", prefix );

    init(mangle_descriptors, result_fd);

    mangle_descriptions(comp);

    if (prefix == L"-")
        join_completions(comp);

    std::vector<comp_t *> completions = mangle_completions(comp, prefix.c_str());

    /**
       Try to print the completions. Start by trying to print the
       list in PAGER_MAX_COLS columns, if the completions won't
       fit, reduce the number of columns by one. Printing a single
       column never fails.
    */
    for (i = PAGER_MAX_COLS; i>0; i--)
    {
        switch (completion_try_print(i, prefix.c_str(), is_quoted, completions))
        {

            case PAGER_RETRY:
                break;

            case PAGER_DONE:
                i=0;
                break;

            case PAGER_RESIZE:
                /*
                  This means we got a resize event, so we start
                  over from the beginning. Since it the screen got
                  bigger, we might be able to fit all completions
                  on-screen.
                */
                i=PAGER_MAX_COLS+1;
                break;

        }
    }

    fwprintf(out_file, L"%ls", out_buff.c_str());
    if (is_ca_mode)
    {
        writembs(exit_ca_mode);
        pager_flush();
    }
    destroy();

}

