/*=====================================================================
  isqlopts.h - the common command-line options for every ISQL shell and
  command-line server.

      -dbhome <dir>   database home directory
      -ip <addr>      server address      (network tools; ignored otherwise)
      -port <n>       server port         (network tools; ignored otherwise)
      -u <user>       user name
      -pwd <pass>     password  (-p is accepted as an alias)
      -cmd <file>     read and execute commands from <file>
      -version        print the version and exit
      -? / --help     usage

  WHY ONE HEADER. There are seven programs - four shells, two servers and
  isqlsh - and the four shell sources have diverged from one another over
  the years. Seven hand-rolled parsers would diverge too, and the flags are
  only useful if they mean the same thing everywhere.

  -pwd rather than -p as the documented name. A parser can tell -p from
  -port exactly, but a person cannot, and isqlsh 1.23 shipped with -p
  meaning the PORT. Anyone carrying that habit would hand their password to
  -p and have it read as a port number. -p is still accepted so the agreed
  spelling works, but -pwd is what the help text and manual say.

  NETWORK vs LOCAL. -ip and -port are meaningful only to tools that talk to
  a server. A local tool should call IsqlOptsWarnIgnoredNet() so that
  passing them is visibly ignored rather than silently doing nothing - the
  bug that cost the most time in this codebase was a shell that looked like
  it was working while quietly reaching no database at all.

  Include AFTER the program's own headers; needs <string.h>, <stdio.h> and,
  on Windows, <windows.h> (pulled in here) for the -cmd path resolution.
 =====================================================================*/
#ifndef ISQL_OPTS_H
#define ISQL_OPTS_H

#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>   /* GetFullPathNameA, for resolving -cmd; see below */
#endif

typedef struct
{
    const char *dbhome;
    const char *ip;
    const char *port;
    const char *user;
    const char *pwd;
    const char *cmdfile;
    int         want_version;
    int         want_help;
    const char *unknown;      /* first unrecognised argument, or NULL */
    char        cmdpath[520]; /* cmdfile made absolute; see IsqlParseOpts */
} ISQL_OPTS;

/* -flag or /flag, case-insensitive, whole word. */
static int IsqlOptIs(const char *arg, const char *name)
{
    if (arg == NULL || (arg[0] != '-' && arg[0] != '/')) return 0;
    return _stricmp(arg + 1, name) == 0;
}

static void IsqlParseOpts(int argc, char **argv, ISQL_OPTS *o)
{
    int i;

    memset(o, 0, sizeof(*o));

    for (i = 1; i < argc; i++)
    {
        const char *a = argv[i];
        const char *v = (i + 1 < argc) ? argv[i + 1] : NULL;

        if      (IsqlOptIs(a, "dbhome") && v) { o->dbhome  = v; i++; }
        else if (IsqlOptIs(a, "h")      && v) { o->dbhome  = v; i++; }  /* legacy alias */
        else if (IsqlOptIs(a, "ip")     && v) { o->ip      = v; i++; }
        else if (IsqlOptIs(a, "s")      && v) { o->ip      = v; i++; }  /* legacy alias */
        else if (IsqlOptIs(a, "port")   && v) { o->port    = v; i++; }
        else if (IsqlOptIs(a, "u")      && v) { o->user    = v; i++; }
        else if (IsqlOptIs(a, "user")   && v) { o->user    = v; i++; }
        else if (IsqlOptIs(a, "pwd")    && v) { o->pwd     = v; i++; }
        else if (IsqlOptIs(a, "p")      && v) { o->pwd     = v; i++; }  /* agreed spelling */
        else if (IsqlOptIs(a, "cmd")    && v) { o->cmdfile = v; i++; }
        else if (IsqlOptIs(a, "f")      && v) { o->cmdfile = v; i++; }  /* isqlsh's own name */
        else if (IsqlOptIs(a, "version") || IsqlOptIs(a, "-version")) o->want_version = 1;
        else if (IsqlOptIs(a, "?") || IsqlOptIs(a, "help") || IsqlOptIs(a, "-help")) o->want_help = 1;
        else if (a[0] != '-' && a[0] != '/' && o->dbhome == NULL) o->dbhome = a; /* bare home */
        else if (o->unknown == NULL) o->unknown = a;
    }

    /* Resolve -cmd against the CURRENT directory, here, while it is still the
       one the user typed the command in.

       The engine changes directory into the database home as it starts
       (GotoDatabaseHome), so a shell that opens the file afterwards resolves a
       relative name against the home instead. Measured before this was added:
       `locshell -dbhome C:\h -cmd t.sql` reported "Command file failed - could
       not open it" with t.sql sitting right there in the working directory,
       while the same command worked in shell.exe, which happens to open the
       file earlier. Same flag, same file, two answers.

       Doing it in the parser fixes all seven programs at once and cannot drift,
       and it also makes the error message name the full path that was tried. */
#ifdef _WIN32
    if (o->cmdfile != NULL)
    {
        DWORD n = GetFullPathNameA(o->cmdfile, (DWORD) sizeof(o->cmdpath),
                                   o->cmdpath, NULL);
        if (n > 0 && n < sizeof(o->cmdpath))
            o->cmdfile = o->cmdpath;
        /* On failure keep the name as typed - no worse than before. */
    }
#endif
}

/* Prints the shared part of a usage message, so the seven programs cannot
   drift into describing the same flag differently. `net` selects whether the
   network options are shown. */
static void IsqlOptsUsage(int net)
{
    printf("  -dbhome <dir>  database home directory\n");
    if (net)
    {
        printf("  -ip <addr>     server address\n");
        printf("  -port <n>      server port\n");
    }
    printf("  -u <user>      user name\n");
    printf("  -pwd <pass>    password (-p also accepted)\n");
    printf("  -cmd <file>    read and execute commands from <file>\n");
    printf("  -version       print the version and exit\n");
    printf("  -?             this help\n");
}

/* For local-only tools: say plainly that -ip/-port were ignored. */
static void IsqlOptsWarnIgnoredNet(const ISQL_OPTS *o, const char *progname)
{
    if (o->ip != NULL || o->port != NULL)
        printf("%s: -ip/-port ignored - this tool runs the engine in-process, not over a network\n",
               progname);
}

/* Feeds a file to a shell's own command executor, one line at a time, exactly
   as if the lines had been typed at the prompt.

   It lives here rather than in isqlbatch.h because it needs no engine types
   at all - only a function that takes a string. The shells are split between
   two worlds: some link isqllib and hold INTERNET_SQL_OBJECT *, others go
   through isqlapi and hold KINT64 handles, and a header that mentions either
   one cannot be included by the other half.

   Blank lines and lines starting with -- or # are skipped. Returns 1 if the
   file was read, 0 if it could not be opened. */
static int IsqlFeedCommandFile(const char *filename, void (*exec)(char *))
{
    FILE *fp;
    char line[4096];

    if (filename == NULL || *filename == '\0' || exec == NULL) return 0;
    fp = fopen(filename, "r");
    if (fp == NULL) return 0;

    while (fgets(line, (int) sizeof(line), fp) != NULL)
    {
        char *p = line;
        size_t n = strlen(line);

        while (n && (line[n-1] == '\n' || line[n-1] == '\r')) line[--n] = '\0';
        while (*p == ' ' || *p == '\t') p++;

        if (*p == '\0') continue;                          /* blank */
        if (*p == '#') continue;                           /* comment */
        if (p[0] == '-' && p[1] == '-') continue;          /* comment */

        (*exec)(p);
    }

    fclose(fp);
    return 1;
}

/* Builds "login user,pass;" if both were given. Returns 1 when `out` was
   filled. Every shell needs this and none of them should spell it its own
   way. */
static int IsqlOptsLoginCommand(const ISQL_OPTS *o, char *out, int outsz)
{
    if (o->user == NULL || o->pwd == NULL) return 0;
    if ((int)(strlen(o->user) + strlen(o->pwd) + 10) > outsz) return 0;
    sprintf(out, "login %s,%s;", o->user, o->pwd);
    return 1;
}

#endif /* ISQL_OPTS_H */
