/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 <inttypes.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
#include "os/mynewt.h"
#include "console/console.h"
#include "parse/parse.h"
#include "shell/shell.h"
#include "fault/fault.h"

static const struct shell_cmd_help fault_cli_help = {
    .summary = "Fault management",
    .usage = 
        "\n"
        "fault chronls\n"
        "    Lists the chronic fault failure counts.\n"
        "fault chronset <fault-domain> <count>\n"
        "    Sets and persists the specified chronic fault failure count for\n"
        "    the given domain.\n"
        "fault chronsetr <fault-domain> <count>\n"
        "    Performs a chronset and then immediately reboots the device.\n"
        "fault chronclr\n"
        "    Sets all chronic fault failure counts to 0 and persists the\n"
        "    counts.\n"
        "fault fatalfail <fault-domain> [uint-param]\n"
        "    Simulates a fatal error for the given domain.\n"
        "fault fatalgood <fault-domain> [uint-param]\n"
        "    Simulates a fatal success for the given domain.\n",
};

static int
fault_cli_parse_domain(const char *arg)
{
    int dom;
    const char *name;
    int rc;

    /* First, try to parse a numeric ID. */
    dom = parse_ull_bounds(arg, 0, MYNEWT_VAL(FAULT_MAX_DOMAINS), &rc);
    if (rc == 0) {
        return dom;
    }

    /* Otherwise, see if the user typed the name of a domain. */
    for (dom = 0; dom < MYNEWT_VAL(FAULT_MAX_DOMAINS); dom++) {
        name = fault_domain_name(dom);
        if (name != NULL && strcasecmp(arg, name) == 0) {
            return dom;
        }
    }

    return -1;
}

static int
fault_cli_chronls(int argc, char **argv)
{
    const char *name;
    uint8_t count;
    int rc;
    int i;

    for (i = 0; i < MYNEWT_VAL(FAULT_MAX_DOMAINS); i++) {
        rc = fault_get_chronic_count(i, &count);
        if (rc == 0) {
            name = fault_domain_name(i);
            if (name == NULL) {
                name = "";
            }
            console_printf("(%d) %s: %d\n", i, name, count);
        }
    }

    return 0;
}

static int
fault_cli_chronset(int argc, char **argv)
{
    uint8_t count;
    int dom;
    int rc;

    if (argc < 2) {
        return SYS_EINVAL;
    }

    dom = fault_cli_parse_domain(argv[0]);
    if (dom == -1) {
        console_printf("invalid domain string\n");
        return SYS_EINVAL;
    }

    count = parse_ull_bounds(argv[1], 0, UINT8_MAX, &rc);
    if (rc != 0) {
        console_printf("invalid failure count\n");
        return rc;
    }

    rc = fault_set_chronic_count(dom, count);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

static int
fault_cli_chronsetr(int argc, char **argv)
{
    int rc;

    rc = fault_cli_chronset(argc, argv);
    if (rc != 0) {
        return rc;
    }

    os_reboot(0);

    return 0;
}

static int
fault_cli_chronclr(int argc, char **argv)
{
    int dom;
    int rc;

    for (dom = 0; dom < MYNEWT_VAL(FAULT_MAX_DOMAINS); dom++) {
        if (fault_domain_is_registered(dom)) {
            rc = fault_set_chronic_count(dom, 0);
            if (rc != 0) {
                return rc;
            }
        }
    }

    return 0;
}

static int
fault_cli_fatal_gen(int argc, char **argv, bool is_failure)
{
    uintptr_t int_arg;
    int dom;
    int rc;

    if (argc < 1) {
        return SYS_EINVAL;
    }

    dom = fault_cli_parse_domain(argv[0]);
    if (dom == -1) {
        console_printf("invalid domain string\n");
        return SYS_EINVAL;
    }

    if (argc >= 2) {
        int_arg = parse_ull_bounds(argv[1], 0, UINT_MAX, &rc);
        if (rc != 0)
        {
            console_printf("invalid fault argument (`arg`)\n");
            return rc;
        }
    } else {
        int_arg = 0;
    }

    fault_fatal(dom, (void *)int_arg, is_failure);

    return 0;
}

static int
fault_cli_fatalfail(int argc, char **argv)
{
    return fault_cli_fatal_gen(argc, argv, true);
}

static int
fault_cli_fatalgood(int argc, char **argv)
{
    return fault_cli_fatal_gen(argc, argv, false);
}

static int
fault_cli_cmd_fn(int argc, char **argv)
{
    /* Shift initial "fault" argument. */
    argc--;
    argv++;

    if (argc < 1) {
        return SYS_EINVAL;
    }

    if (strcmp(argv[0], "chronls") == 0) {
        return fault_cli_chronls(argc - 1, argv + 1);
    }

    if (strcmp(argv[0], "chronset") == 0) {
        return fault_cli_chronset(argc - 1, argv + 1);
    }

    if (strcmp(argv[0], "chronsetr") == 0) {
        return fault_cli_chronsetr(argc - 1, argv + 1);
    }

    if (strcmp(argv[0], "chronclr") == 0) {
        return fault_cli_chronclr(argc - 1, argv + 1);
    }

    if (strcmp(argv[0], "fatalfail") == 0) {
        return fault_cli_fatalfail(argc - 1, argv + 1);
    }

    if (strcmp(argv[0], "fatalgood") == 0) {
        return fault_cli_fatalgood(argc - 1, argv + 1);
    }

    return SYS_EINVAL;
}

MAKE_SHELL_CMD(fault, fault_cli_cmd_fn, &fault_cli_help)
