// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 1997-2007 Ralf S. Engelschall.

#include "config.h"
#include "iselect.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <locale.h>

#define VERSION                                                           \
    ISELECT_VERSION_STR "\n"                                              \
    "Copyright (c) 1997-2000 Ralf S. Engelschall <rse@engelschall.com>\n" \
    "\n"                                                                  \
    "This program is distributed in the hope that it will be useful,\n"   \
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"    \
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"     \
    "GNU General Public License for more details.\n"

#define USAGE(self)                                                              \
    "Usage: %s [options] line...\n"                                              \
    "       %s [options] < lines\n"                                              \
    "\n"                                                                         \
    "Input Options:\n"                                                           \
    "  -d, --delimiter=beg,end selection tag delimiters\n"                       \
    "  -c, --strip-comments    strip sharp-comments in input buffer\n"           \
    "  -f, --force-browse      browse even if input has 0 or only 1 line\n"      \
    "  -a, --all-select        force all lines to be selectable\n"               \
    "  -e, --exit-no-select    exit immediately if no lines are selectable\n"    \
    "\n"                                                                         \
    "Display Options:\n"                                                         \
    "  -p, --position=linenum  initial line position of cursor\n"                \
    "  -k, --key=key[:okey]    enable an additional input key\n"                 \
    "  -m, --multi-line        allow multiple lines to be selected\n"            \
    "  -n, --name=name         program name shown flush-left on status bar\n"    \
    "  -t, --title=title       title string shown centered on status bar\n"      \
    "\n"                                                                         \
    "Output Options:\n"                                                          \
    "  -S, --strip-result      strip whitespaces in result string\n"             \
    "  -P, --position-result   prefix result string with `N:' (N=line number)\n" \
    "  -K, --key-result        prefix result string with `K:' (K=select key)\n"  \
    "  -Q, --quit-result=fallb result string on quit\n"                          \
    "\n"                                                                         \
    "Giving Feedback:\n"                                                         \
    "  -V, --version           display version string\n"                         \
    "  -h, --help              display this page\n", self, self

static const struct option options[] = {
    { "delimiter",       required_argument, NULL, 'd'  },
    { "strip-comments",  no_argument,       NULL, 'c'  },
    { "force-browse",    no_argument,       NULL, 'f'  },
    { "all-select",      no_argument,       NULL, 'a'  },
    { "exit-no-select",  no_argument,       NULL, 'e'  },
    { "position",        required_argument, NULL, 'p'  },
    { "key",             required_argument, NULL, 'k'  },
    { "multi-line",      no_argument,       NULL, 'm'  },
    { "name",            required_argument, NULL, 'n'  },
    { "title",           required_argument, NULL, 't'  },
    { "strip-result",    no_argument,       NULL, 'S'  },
    { "position-result", no_argument,       NULL, 'P'  },
    { "key-result",      no_argument,       NULL, 'K'  },
    { "quit-result",     required_argument, NULL, 'Q'  },
    { "version",         no_argument,       NULL, 'V'  },
    { "help",            no_argument,       NULL, 'h'  },
    { NULL,              no_argument,       NULL, '\0' },
};

int main(int argc, char **argv)
{
    setlocale(LC_ALL, "");

    size_t pos = 0;
    const char *title = "";
    const char *name = "iSelect";
    bool stripco = false;
    bool stripws = false;
    bool resultline = false;
    bool keyresultline = false;
    bool browsealways = false;
    bool allselectable = false;
    bool multiselect = false;
    bool exitnoselect = false;
    const char *abortstr = NULL;
    const char *tagbegin = "<";
    const char *tagend   = ">";
    for (int c; (c = getopt_long(argc, argv, "d:cfaep:k:mn:t:SPKQ:Vh", options, NULL)) != -1;) {
        char *cp;
        switch (c) {
            case 'd':
                tagbegin = optarg;
                if ((cp = strchr(tagbegin, ',')) == NULL)
                    return fprintf(stderr, "%s: -d missing ,\n", argv[0]), 1;
                *cp++ = '\0';
                tagend = cp;
                break;
            case 'c':
                stripco = true;
                break;
            case 'f':
                browsealways = true;
                break;
            case 'a':
                allselectable = true;
                break;
            case 'e':
                exitnoselect = true;
                break;
            case 'p': {
                char *pend;
                pos = strtoull(optarg, &pend, 0);
                if (*pend)
                    return fprintf(stderr, "%s: -p %s: %s\n", argv[0], optarg, strerror(errno ? errno : EINVAL)), 1;
              } break;
            case 'k':
                configure_custom_key(optarg);
                break;
            case 'm':
                multiselect = true;
                break;
            case 'n':
                name = optarg;
                break;
            case 't':
                title = optarg;
                break;
            case 'S':
                stripws = true;
                break;
            case 'P':
                resultline = true;
                break;
            case 'K':
                keyresultline = true;
                break;
            case 'Q':
                abortstr = optarg;
                break;
            case 'V':
                fputs(VERSION, stdout);
                return 0;
            case 'h':
                fprintf(stderr, USAGE(argv[0]));
                return 0;
        }
    }

    /*
     *  read input
     */
    if (optind < argc) {
        /* browsing text is given as arguments */
        spaLines = reallocarray(spaLines, argc - optind, sizeof(*spaLines));
        if (!spaLines)
            return fprintf(stderr, "iSelect: %s\n", strerror(errno)), 1;
        for (int i = 0; i + optind < argc; ++i) {
            if(stripco && argv[i + optind][0] == '#')
                continue;

            spaLines[nLines++] = (Line){.cpLine = argv[i + optind], .nLine = strlen(argv[i + optind])};
        }
    }
    else {
        /* browsing text is given on stdin */
        char *line     = NULL;
        size_t linecap = 0;
        for(ssize_t len; (len = getline(&line, &linecap, stdin)) != -1;) {
            if(stripco && line[0] == '#')
                continue;

            if(line[len - 1] == '\n')
                --len;
            char *dupline = malloc(len);

            spaLines = reallocarray(spaLines, ++nLines, sizeof(*spaLines));
            if (!spaLines || !dupline)
                return fprintf(stderr, "iSelect: %s\n", strerror(errno)), 1;

            memcpy(dupline, line, len);
            spaLines[nLines - 1] = (Line){.cpLine = dupline, .nLine = len};
        }
        if (ferror(stdin))
            return fprintf(stderr, "iSelect: /dev/tty: %s\n", strerror(errno)), 1;
        free(line);

        /* reconnect stdin filehandle to tty */
        close(0);
        if (open("/dev/tty", O_RDONLY) == -1)
            return fprintf(stderr, "iSelect: /dev/tty: %s\n", strerror(errno)), 1;
    }

    /*
     *  preserve stdout filehandle for result string, i.e.
     *  use the terminal directly for output
     */
    int fpStdout = dup(1);
    close(1);
    if (open("/dev/tty", O_RDWR) == -1)
        return fprintf(stderr, "iSelect: /dev/tty: %s\n", strerror(errno)), 1;

    enum iSelect_key unmapped_key = L'\n';
    ssize_t p = iSelect(pos, title, name,
                tagbegin, tagend, stripws,
                browsealways, allselectable, multiselect, exitnoselect, &unmapped_key);

    /*
     *  give back the result string to the user via
     *  the stdout file handle
     */
    if (p != -1) {
        FILE *out = fdopen(fpStdout, "w");
        for (size_t i = 0; i < nLines; ++i) {
            if (spaLines[i].fSelected) {
                if (resultline)
                    fprintf(out, "%zu:", i+1);
                if (keyresultline)
                    name_key(unmapped_key, out), fputc(':', out);
                if (spaLines[i].cpFmtResult) {
                    spaLines[i].cpResult = spaLines[i].cpFmtResult;
                    spaLines[i].nResult  = spaLines[i].nFmtResult;
                }
                fwrite(spaLines[i].cpResult, 1, spaLines[i].nResult, out);
                fputc('\n', out);
            }
        }
    }
    else {
        if (abortstr != NULL)
            write(fpStdout, abortstr, strlen(abortstr));
    }
}
