/**
 * @file fuse-opt-test.c
 *
 * @copyright 2015-2026 Bill Zissimopoulos
 */
/*
 * This file is part of WinFsp.
 *
 * You can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 3 as published by the Free Software
 * Foundation.
 *
 * Licensees holding a valid commercial license may use this software
 * in accordance with the commercial license agreement provided in
 * conjunction with the software.  The terms and conditions of any such
 * commercial license agreement shall govern, supersede, and render
 * ineffective any application of the GPLv3 license to this software,
 * notwithstanding of any reference thereto in the software or
 * associated repository.
 */

#include <fuse/fuse_opt.h>
#include <tlib/testsuite.h>
#include <stddef.h>
#include <string.h>

#include "winfsp-tests.h"

struct data
{
    int fortytwo;
    int a, b, c, d, e, f, g, h;
    int i, j, k, l, m, n, o, p;
    char *q, *r, *s, *t;
    int u, v, V;
    char *w;
    short x;
    long y;
    long long z;
    int dec, neg, hex, oct;
    int sel;
    char *esc; int ESC;
    int arg_discard, arg_keep;
    int opt_discard, opt_keep;
    int nonopt_discard, nonopt_keep;
};

static int fuse_opt_parse_test_proc(void *data0, const char *arg, int key,
    struct fuse_args *outargs)
{
    struct data *data = data0;

    switch (key)
    {
    default:
        ASSERT(0);
        return -1;
    case FUSE_OPT_KEY_OPT:
        if (0 == strcmp("--arg-discard", arg))
        {
            data->arg_discard++;
            return 0;
        }
        if (0 == strcmp("--arg-keep", arg))
        {
            data->arg_keep++;
            return 1;
        }
        if (0 == strcmp("opt-discard", arg))
        {
            data->opt_discard++;
            return 0;
        }
        if (0 == strcmp("opt-keep", arg))
        {
            data->opt_keep++;
            return 1;
        }
        ASSERT(0);
        return -1;
    case FUSE_OPT_KEY_NONOPT:
        if (0 == strcmp("--discard", arg))
        {
            data->nonopt_discard++;
            return 1;
        }
        if (0 == strcmp("--keep", arg))
        {
            data->nonopt_keep++;
            return 0;
        }
        ASSERT(0);
        return -1;
    case 'a':
        ASSERT(0);
        return -1;
    case 'b':
        ASSERT(0 == strcmp("-b", arg));
        data->b = 'B';
        return 1;
    case 'c':
        ASSERT(0);
        return -1;
    case 'd':
        ASSERT(0 == strcmp("--dlong", arg));
        data->d = 'D';
        return 1;
    case 'e':
        ASSERT(0);
        return -1;
    case 'f':
        ASSERT(0 == strcmp("f", arg));
        data->f = 'F';
        return 1;
    case 'g':
        ASSERT(0);
        return -1;
    case 'h':
        ASSERT(0 == strcmp("hlong", arg));
        data->h = 'H';
        return 1;
    case 'i':
        ASSERT(0);
        return -1;
    case 'j':
        ASSERT(0 == strcmp("-j=74", arg));
        data->j = 'J';
        return 1;
    case 'k':
        ASSERT(0);
        return -1;
    case 'l':
        ASSERT(0 == strcmp("--llong=76", arg));
        data->l = 'L';
        return 1;
    case 'm':
        ASSERT(0);
        return -1;
    case 'n':
        ASSERT(0 == strcmp("n=78", arg));
        data->n = 'N';
        return 1;
    case 'o':
        ASSERT(0);
        return -1;
    case 'p':
        ASSERT(0 == strcmp("plong=80", arg));
        data->p = 'P';
        return 1;
    case 'q':
        ASSERT(0);
        return -1;
    case 'r':
        ASSERT(0);
        return -1;
    case 's':
        ASSERT(0);
        return -1;
    case 't':
        ASSERT(0);
        return -1;
    case 'u':
        ASSERT(0);
        return -1;
    case 'v':
        ASSERT(0 == strcmp("-v86", arg));
        data->v = 'V';
        return 1;
    case 'V':
        ASSERT(0 == strcmp("-V118", arg));
        data->V = 'v';
        return 1;
    case 'w':
        ASSERT(0);
        return -1;
    case 'x':
        ASSERT(0);
        return -1;
    case 'y':
        ASSERT(0);
        return -1;
    case 'z':
        ASSERT(0);
        return -1;
    case 'ESC':
        ASSERT(0 == strcmp("ESC=\\FOO,BAR\\", arg));
        data->ESC = 1;
        return 1;
    }
}

void fuse_opt_parse_test(void)
{
    static struct fuse_opt opts[] =
    {
        { "-a", offsetof(struct data, a), 'a' },
        FUSE_OPT_KEY("-b", 'b'),
        { "--clong", offsetof(struct data, c), 'c' },
        FUSE_OPT_KEY("--dlong", 'd'),
        { "e", offsetof(struct data, e), 'e' },
        FUSE_OPT_KEY("f", 'f'),
        { "glong", offsetof(struct data, g), 'g' },
        FUSE_OPT_KEY("hlong", 'h'),

        { "-i=", offsetof(struct data, i), 'i' },
        FUSE_OPT_KEY("-j=", 'j'),
        { "--klong=", offsetof(struct data, k), 'k' },
        FUSE_OPT_KEY("--llong=", 'l'),
        { "m=", offsetof(struct data, m), 'm' },
        FUSE_OPT_KEY("n=", 'n'),
        { "olong=", offsetof(struct data, o), 'o' },
        FUSE_OPT_KEY("plong=", 'p'),

        { "-q=%s", offsetof(struct data, q), 'q' },
        { "--rlong=%s", offsetof(struct data, r), 'r' },
        { "s=%s", offsetof(struct data, s), 's' },
        { "tlong=%s", offsetof(struct data, t), 't' },

        { "-u ", offsetof(struct data, u), 'u' },
        FUSE_OPT_KEY("-v ", 'v'),
        FUSE_OPT_KEY("-V ", 'V'),

        { "-w %s", offsetof(struct data, w), 'w' },

        { "-x=%hi", offsetof(struct data, x), 'x' },
        { "-y=%li", offsetof(struct data, y), 'y' },
        { "-z=%lli", offsetof(struct data, z), 'z' },

        { "--dec=%d", offsetof(struct data, dec), 'dec' },
        { "--neg=%d", offsetof(struct data, neg), 'neg' },
        { "--hex=%x", offsetof(struct data, hex), 'hex' },
        { "--oct=%o", offsetof(struct data, oct), 'oct' },

        { "--sel=fortyone", offsetof(struct data, sel), 141 },
        { "--sel=fortytwo", offsetof(struct data, sel), 142 },
        { "--sel=fortythree", offsetof(struct data, sel), 143 },

        { "esc=%s", offsetof(struct data, esc), 'esc' },
        FUSE_OPT_KEY("ESC=", 'ESC'),

        FUSE_OPT_KEY("--discard", FUSE_OPT_KEY_DISCARD),
        FUSE_OPT_KEY("--keep", FUSE_OPT_KEY_KEEP),

        FUSE_OPT_END,
    };
    static char *argv[] =
    {
        "exec",
        "-a",
        "-b",
        "--clong",
        "--dlong",
        "-o", "e,f",
        "-oglong,hlong",
        "-i=73",
        "-j=74",
        "--klong=75",
        "--llong=76",
        "-om=77",
        "-o", "n=78,olong=79,plong=80",
        "-q=QqQq",
        "--rlong=RrRrRrRr",
        "-os=SsSs",
        "-otlong=TtTtTtTt",
        "-u85",
        "-v", "86",
        "-V118",
        "-wWwWw",
        "-x=65537",
        "-y=040000000001",
        "-z=0x100000001",
        "--dec=+1234567890",
        "--neg=-1234567890",
        "--hex=ABCDEF",
        "--oct=12345670",
        "--sel=fortytwo",
        "-oesc=\\\\foo\\,bar\\\\,ESC=\\\\FOO\\,BAR\\\\",
        "--discard",
        "--keep",
        "--arg-discard",
        "--arg-keep",
        "-oopt-discard",
        "-oopt-keep",
        "--",
        "--discard",
        "--keep",
        0
    };
    static char *outargv[] =
    {
        "exec",
        "-o", "f,hlong,n=78,plong=80,ESC=\\\\FOO\\,BAR\\\\,opt-keep",
        "-b",
        "--dlong",
        "-j=74",
        "--llong=76",
        "-v86",
        "-V118",
        "--keep",
        "--arg-keep",
        "--",
        "--discard",
        0
    };
    struct fuse_args args = FUSE_ARGS_INIT(0, 0);
    struct data data;
    int result;

    args.argc = sizeof argv / sizeof argv[0] - 1;
    args.argv = argv;

    memset(&data, 0, sizeof data);
    data.fortytwo = 42;
    result = fuse_opt_parse(&args, &data, opts, fuse_opt_parse_test_proc);
    ASSERT(0 == result);

    ASSERT(42 == data.fortytwo);
    ASSERT('a' == data.a);
    ASSERT('B' == data.b);
    ASSERT('c' == data.c);
    ASSERT('D' == data.d);
    ASSERT('e' == data.e);
    ASSERT('F' == data.f);
    ASSERT('g' == data.g);
    ASSERT('H' == data.h);
    ASSERT('i' == data.i);
    ASSERT('J' == data.j);
    ASSERT('k' == data.k);
    ASSERT('L' == data.l);
    ASSERT('m' == data.m);
    ASSERT('N' == data.n);
    ASSERT('o' == data.o);
    ASSERT('P' == data.p);
    ASSERT(0 == strcmp("QqQq", data.q));
    ASSERT(0 == strcmp("RrRrRrRr", data.r));
    ASSERT(0 == strcmp("SsSs", data.s));
    ASSERT(0 == strcmp("TtTtTtTt", data.t));
    ASSERT('u' == data.u);
    ASSERT('V' == data.v);
    ASSERT('v' == data.V);
    ASSERT(0 == strcmp("WwWw", data.w));
    ASSERT(1 == data.x);
    ASSERT((long)040000000001 == data.y);
    ASSERT((long long)0x100000001 == data.z);
    ASSERT(+1234567890 == data.dec);
    ASSERT(-1234567890 == data.neg);
    ASSERT(0xABCDEF == data.hex);
    ASSERT(012345670 == data.oct);
    ASSERT(142 == data.sel);
    ASSERT(0 == strcmp("\\foo,bar\\", data.esc));
    ASSERT(1 == data.ESC);
    ASSERT(1 == data.arg_discard);
    ASSERT(1 == data.arg_keep);
    ASSERT(1 == data.opt_discard);
    ASSERT(1 == data.opt_keep);
    ASSERT(1 == data.nonopt_discard);
    ASSERT(1 == data.nonopt_keep);

    ASSERT(args.argc == sizeof outargv / sizeof outargv[0] - 1);
    for (int i = 0; args.argc > i; i++)
        ASSERT(0 == strcmp(args.argv[i], outargv[i]));
    ASSERT(0 == args.argv[args.argc]);
    ASSERT(args.allocated);

    fuse_opt_free_args(&args);

    free(data.q);
    free(data.r);
    free(data.s);
    free(data.t);
    free(data.w);
    free(data.esc);
}

void fuse_opt_tests(void)
{
    if (OptExternal)
        return;

    TEST(fuse_opt_parse_test);
}
