/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#undef NDEBUG
#include <assert.h>
#include "commands.h"

static struct {
    bool called;
    const char *name;
    void *cookie;
    int argc;
    const char **argv;
    PermissionRequestList *permissions;
    int returnValue;
    char *functionResult;
} gTestCommandState;

static int
testCommand(const char *name, void *cookie, int argc, const char *argv[],
        PermissionRequestList *permissions)
{
    gTestCommandState.called = true;
    gTestCommandState.name = name;
    gTestCommandState.cookie = cookie;
    gTestCommandState.argc = argc;
    gTestCommandState.argv = argv;
    gTestCommandState.permissions = permissions;
    return gTestCommandState.returnValue;
}

static int
testFunction(const char *name, void *cookie, int argc, const char *argv[],
        char **result, size_t *resultLen, PermissionRequestList *permissions)
{
    gTestCommandState.called = true;
    gTestCommandState.name = name;
    gTestCommandState.cookie = cookie;
    gTestCommandState.argc = argc;
    gTestCommandState.argv = argv;
    gTestCommandState.permissions = permissions;
    if (result != NULL) {
        *result = gTestCommandState.functionResult;
        if (resultLen != NULL) {
            *resultLen = strlen(*result);
        }
    }
    return gTestCommandState.returnValue;
}

static int
test_commands()
{
    Command *cmd;
    int ret;
    CommandArgumentType argType;

    ret = commandInit();
    assert(ret == 0);

    /* Make sure we can't initialize twice.
     */
    ret = commandInit();
    assert(ret < 0);

    /* Try calling with some bad values.
     */
    ret = registerCommand(NULL, CMD_ARGS_UNKNOWN, NULL, NULL);
    assert(ret < 0);

    ret = registerCommand("hello", CMD_ARGS_UNKNOWN, NULL, NULL);
    assert(ret < 0);

    ret = registerCommand("hello", CMD_ARGS_WORDS, NULL, NULL);
    assert(ret < 0);

    cmd = findCommand(NULL);
    assert(cmd == NULL);

    argType = getCommandArgumentType(NULL);
    assert((int)argType < 0);

    ret = callCommand(NULL, -1, NULL);
    assert(ret < 0);

    ret = callBooleanCommand(NULL, false);
    assert(ret < 0);

    /* Register some commands.
     */
    ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
            &gTestCommandState);
    assert(ret == 0);

    ret = registerCommand("two", CMD_ARGS_WORDS, testCommand,
            &gTestCommandState);
    assert(ret == 0);

    ret = registerCommand("bool", CMD_ARGS_BOOLEAN, testCommand,
            &gTestCommandState);
    assert(ret == 0);

    /* Make sure that all of those commands exist and that their
     * argument types are correct.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);
    argType = getCommandArgumentType(cmd);
    assert(argType == CMD_ARGS_WORDS);

    cmd = findCommand("two");
    assert(cmd != NULL);
    argType = getCommandArgumentType(cmd);
    assert(argType == CMD_ARGS_WORDS);

    cmd = findCommand("bool");
    assert(cmd != NULL);
    argType = getCommandArgumentType(cmd);
    assert(argType == CMD_ARGS_BOOLEAN);

    /* Make sure that no similar commands exist.
     */
    cmd = findCommand("on");
    assert(cmd == NULL);

    cmd = findCommand("onee");
    assert(cmd == NULL);

    /* Make sure that a double insertion fails.
     */
    ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
            &gTestCommandState);
    assert(ret < 0);

    /* Make sure that bad args fail.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);

    ret = callCommand(cmd, -1, NULL);   // argc must be non-negative
    assert(ret < 0);

    ret = callCommand(cmd, 1, NULL);    // argv can't be NULL if argc > 0
    assert(ret < 0);

    /* Make sure that you can't make a boolean call on a regular command.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);

    ret = callBooleanCommand(cmd, false);
    assert(ret < 0);

    /* Make sure that you can't make a regular call on a boolean command.
     */
    cmd = findCommand("bool");
    assert(cmd != NULL);

    ret = callCommand(cmd, 0, NULL);
    assert(ret < 0);

    /* Set up some arguments.
     */
    int argc = 4;
    const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };

    /* Make a call and make sure that it occurred.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 25;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    ret = callCommand(cmd, argc, argv);
//xxx also try calling with a null argv element (should fail)
    assert(ret == 25);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == NULL);

    /* Make a boolean call and make sure that it occurred.
     */
    cmd = findCommand("bool");
    assert(cmd != NULL);

    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 12;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    ret = callBooleanCommand(cmd, false);
    assert(ret == 12);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "bool") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == 0);
    assert(gTestCommandState.argv == NULL);
    assert(gTestCommandState.permissions == NULL);

    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 13;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    ret = callBooleanCommand(cmd, true);
    assert(ret == 13);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "bool") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == 1);
    assert(gTestCommandState.argv == NULL);
    assert(gTestCommandState.permissions == NULL);

    /* Try looking up permissions.
     */
    PermissionRequestList permissions;
    cmd = findCommand("one");
    assert(cmd != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 27;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    argv[1] = NULL; // null out an arg, which should be ok
    ret = getCommandPermissions(cmd, argc, argv, &permissions);
    assert(ret == 27);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == &permissions);

    /* Boolean command permissions
     */
    cmd = findCommand("bool");
    assert(cmd != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 55;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    // argv[1] is still NULL
    ret = getBooleanCommandPermissions(cmd, true, &permissions);
    assert(ret == 55);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "bool") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == 1);
    assert(gTestCommandState.argv == NULL);
    assert(gTestCommandState.permissions == &permissions);


    /* Smoke test commandCleanup().
     */
    commandCleanup();

    return 0;
}

static int
test_functions()
{
    Function *fn;
    int ret;

    ret = commandInit();
    assert(ret == 0);

    /* Try calling with some bad values.
     */
    ret = registerFunction(NULL, NULL, NULL);
    assert(ret < 0);

    ret = registerFunction("hello", NULL, NULL);
    assert(ret < 0);

    fn = findFunction(NULL);
    assert(fn == NULL);

    ret = callFunction(NULL, -1, NULL, NULL, NULL);
    assert(ret < 0);

    /* Register some functions.
     */
    ret = registerFunction("one", testFunction, &gTestCommandState);
    assert(ret == 0);

    ret = registerFunction("two", testFunction, &gTestCommandState);
    assert(ret == 0);

    ret = registerFunction("three", testFunction, &gTestCommandState);
    assert(ret == 0);

    /* Make sure that all of those functions exist.
     * argument types are correct.
     */
    fn = findFunction("one");
    assert(fn != NULL);

    fn = findFunction("two");
    assert(fn != NULL);

    fn = findFunction("three");
    assert(fn != NULL);

    /* Make sure that no similar functions exist.
     */
    fn = findFunction("on");
    assert(fn == NULL);

    fn = findFunction("onee");
    assert(fn == NULL);

    /* Make sure that a double insertion fails.
     */
    ret = registerFunction("one", testFunction, &gTestCommandState);
    assert(ret < 0);

    /* Make sure that bad args fail.
     */
    fn = findFunction("one");
    assert(fn != NULL);

    // argc must be non-negative
    ret = callFunction(fn, -1, NULL, (char **)1, NULL);
    assert(ret < 0);

    // argv can't be NULL if argc > 0
    ret = callFunction(fn, 1, NULL, (char **)1, NULL);
    assert(ret < 0);

    // result can't be NULL
    ret = callFunction(fn, 0, NULL, NULL, NULL);
    assert(ret < 0);

    /* Set up some arguments.
     */
    int argc = 4;
    const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };

    /* Make a call and make sure that it occurred.
     */
    char *functionResult;
    size_t functionResultLen;
    fn = findFunction("one");
    assert(fn != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 25;
    gTestCommandState.functionResult = "1234";
    gTestCommandState.permissions = (PermissionRequestList *)1;
    functionResult = NULL;
    functionResultLen = 55;
    ret = callFunction(fn, argc, argv,
            &functionResult, &functionResultLen);
//xxx also try calling with a null resultLen arg (should succeed)
//xxx also try calling with a null argv element (should fail)
    assert(ret == 25);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == NULL);
    assert(strcmp(functionResult, "1234") == 0);
    assert(functionResultLen == strlen(functionResult));

    /* Try looking up permissions.
     */
    PermissionRequestList permissions;
    fn = findFunction("one");
    assert(fn != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 27;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    argv[1] = NULL; // null out an arg, which should be ok
    ret = getFunctionPermissions(fn, argc, argv, &permissions);
    assert(ret == 27);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert(gTestCommandState.cookie == &gTestCommandState);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == &permissions);

    /* Smoke test commandCleanup().
     */
    commandCleanup();

    return 0;
}

static int
test_interaction()
{
    Command *cmd;
    Function *fn;
    int ret;

    ret = commandInit();
    assert(ret == 0);

    /* Register some commands.
     */
    ret = registerCommand("one", CMD_ARGS_WORDS, testCommand, (void *)0xc1);
    assert(ret == 0);

    ret = registerCommand("two", CMD_ARGS_WORDS, testCommand, (void *)0xc2);
    assert(ret == 0);

    /* Register some functions, one of which shares a name with a command.
     */
    ret = registerFunction("one", testFunction, (void *)0xf1);
    assert(ret == 0);

    ret = registerFunction("three", testFunction, (void *)0xf3);
    assert(ret == 0);

    /* Look up each of the commands, and make sure no command exists
     * with the name used only by our function.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);

    cmd = findCommand("two");
    assert(cmd != NULL);

    cmd = findCommand("three");
    assert(cmd == NULL);

    /* Look up each of the functions, and make sure no function exists
     * with the name used only by our command.
     */
    fn = findFunction("one");
    assert(fn != NULL);

    fn = findFunction("two");
    assert(fn == NULL);

    fn = findFunction("three");
    assert(fn != NULL);

    /* Set up some arguments.
     */
    int argc = 4;
    const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };

    /* Call the overlapping command and make sure that the cookie is correct.
     */
    cmd = findCommand("one");
    assert(cmd != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 123;
    gTestCommandState.permissions = (PermissionRequestList *)1;
    ret = callCommand(cmd, argc, argv);
    assert(ret == 123);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert((int)gTestCommandState.cookie == 0xc1);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == NULL);

    /* Call the overlapping function and make sure that the cookie is correct.
     */
    char *functionResult;
    size_t functionResultLen;
    fn = findFunction("one");
    assert(fn != NULL);
    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
    gTestCommandState.called = false;
    gTestCommandState.returnValue = 125;
    gTestCommandState.functionResult = "5678";
    gTestCommandState.permissions = (PermissionRequestList *)2;
    functionResult = NULL;
    functionResultLen = 66;
    ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
    assert(ret == 125);
    assert(gTestCommandState.called);
    assert(strcmp(gTestCommandState.name, "one") == 0);
    assert((int)gTestCommandState.cookie == 0xf1);
    assert(gTestCommandState.argc == argc);
    assert(gTestCommandState.argv == argv);
    assert(gTestCommandState.permissions == NULL);
    assert(strcmp(functionResult, "5678") == 0);
    assert(functionResultLen == strlen(functionResult));

    /* Clean up.
     */
    commandCleanup();

    return 0;
}

int
test_cmd_fn()
{
    int ret;

    ret = test_commands();
    if (ret != 0) {
        fprintf(stderr, "test_commands() failed: %d\n", ret);
        return ret;
    }

    ret = test_functions();
    if (ret != 0) {
        fprintf(stderr, "test_functions() failed: %d\n", ret);
        return ret;
    }

    ret = test_interaction();
    if (ret != 0) {
        fprintf(stderr, "test_interaction() failed: %d\n", ret);
        return ret;
    }

    return 0;
}