/*
 * White Rabbit Switch CLI (Command Line Interface)
 * Copyright (C) 2011, CERN.
 *
 * Authors:     Miguel Baizan   (miguel.baizan@integrasys.es)
 *              Juan Luis Manas (juan.manas@integrasys.es)
 *
 * Description: Miscellaneous helper functions for the command handlers.
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "cli_commands_utils.h"


/**
 * \brief Command 'help'.
 * The 'help' command is the only one not registered in the tree. It has a
 * special treatment since it can be any command's child.
 * @param cli CLI interpreter.
 * @param cmd command for which help has been requested. If NULL, general help
 *        is printed.
 */
void cli_cmd_help(struct cli_shell *cli, struct cli_cmd *cmd)
{
    struct cli_cmd *c;

    printf("\n");

    /* If not command specified, print general help */
    if (!cmd) {
        printf("\tAvailable commands\n"
               "\t------------------\n\n"
               "\t?                    Display help\n"
               "\thelp                 Display help\n");
        for (c = cli->root_cmd; c; c = c->next)
            printf("\t%-20s %s\n", c->name, c->desc);
    } else {
        printf("\tCommand DESCRIPTION\n"
               "\t-------------------\n"
               "\t%s\n", cmd->desc);
        switch (cmd->opt) {
        case CMD_ARG_MANDATORY:
            printf("\n\tArguments that must be provided:\n"
                   "\t%s\n", cmd->opt_desc);
            break;
        case CMD_ARG_OPTIONAL:
            printf("\n\tOptional arguments:\n"
                   "\t%s\n", cmd->opt_desc);
            break;
        default:
            break;
        }
        if (cmd->child) {
            printf("\n\tSUBCOMMANDS\n");
            for (c = cmd->child; c; c = c->next)
                printf("\t  %-20s %s\n", c->name, c->desc);
        }
    }

    printf("\n");
}

/**
 * \brief Helper function to convert mac address into a string
*/
char *mac_to_str(uint8_t mac[ETH_ALEN], char str[3 * ETH_ALEN])
{
    snprintf(str, 3 * ETH_ALEN, "%02x:%02x:%02x:%02x:%02x:%02x",
             mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
    return str;
}

/* Helper function to compare OIDs */
int cmp_oid(oid old_oid[MAX_OID_LEN], oid new_oid[MAX_OID_LEN],
    int base_oid_length)
{
    /* The OID for the table must be the same */
    if (memcmp(new_oid, old_oid, base_oid_length*sizeof(oid)) == 0) {
        if (memcmp(old_oid, new_oid, ((base_oid_length + 2)*sizeof(oid))) == 0)
            return 0; /* We are still in the same column */
        else
            return 1; /* We have changed the column */
    } else {
        return -1; /* We have changed the table */
    }

}

/**
 * \brief Helper function for debugg
*/
void print_oid(oid _oid[MAX_OID_LEN], int n)
{
    int i;
    for (i = 0; i < n; i++)
        printf(".%d", (int)_oid[i]);
}

/**
 * \brief Helper function to convert port masks into port numbers
*/
void mask_to_ports(char *mask, int *ports_range)
{
    int i, j = 0;

    for (i = 0; i < NUM_PORTS; i++) {
        if (mask[i] == 1) {
            ports_range[j] = i;
            j++;
        }
    }

    if (j < NUM_PORTS)
        ports_range[j] = -1; /* This marks the end of the ports array */

    return;
}

/**
 * \brief Helper function to convert port numbers (which can be formatted as a
 * group of numbers separatted by commas) in port masks.
 * @return 0 on success; a negative value otherwise.
*/
int ports_to_mask(char *ports_range, char *mask)
{
    int i = 0;
    int len = 0;
    int num_ports = 0;
    char *port_start;
    char *port = ports_range;
    char *ports[NUM_PORTS];

    memset(mask, '0', (NUM_PORTS+1));

    for (i = 0; i < NUM_PORTS; i++) {
        if (!isdigit(*port) && (*port != ','))
            goto fail;

        while (*port == ',') /* Detect commas */
            port++;

        port_start = port;

        while (*port && isdigit(*port)) { /* Detect numbers */
            port++;
            len++;
        }

        if (len > 2)
            goto fail; /* A port should not have more than two digits */

        /* Allocate memory for the ports array */
        ports[i] = (char*)malloc(len + 1);
        if (!ports[i])
            cli_error(ENOMEM);

        /* Copy the detected command to the array */
        memcpy(ports[i], port_start, len);
        ports[i][len] = 0;

        if (atoi(ports[i]) < 0 || atoi(ports[i]) >= NUM_PORTS)
            goto fail;

        num_ports++;

        /* Clean for the next loop */
        len = 0;

        /* Be sure that we are not yet at the end of the string */
        if (!*port)
            break;
    }

    /* Create the mask */
    for (i = 0; i < num_ports ; i++)
        mask[atoi(ports[i])] = '1';

    mask[NUM_PORTS] = '\0';

    for (i = 0; i < num_ports ; i++)
        free(ports[i]);

    return 0;

fail:
    printf("\tError. Ports must be decimal numbers separated by commas,\n"
           "\twith no blank spaces in between. Valid range: from 0 to %d\n",
           (NUM_PORTS-1));
    return -1;
}

/* Check the syntax of the port argument */
int is_port(char *port)
{
    int i;

    for (i = 0; port[i]; i++) {
        if (!isdigit(port[i])) {
            printf("\tPort syntax error: only decimal values allowed\n");
            return -1;
        }
    }
    if ((atoi(port) < 0) || (atoi(port) >= NUM_PORTS)) {
        printf("\tPort syntax error: allowed values are in the range 0 to %d\n",
                (NUM_PORTS-1));
        return -1;
    }
    return 0;
}

/* Check the syntax of the vlan argument */
int is_vid(char *vid)
{
    int i;

    for (i = 0; vid[i]; i++) {
        if (!isdigit(vid[i])) {
            printf("\tVID syntax error: only decimal values are allowed\n");
            return -1;
        }
    }
    /* FIXME: VID = 0 not allowed in the Std. 802.1Q, but supported by HW */
    if ((atoi(vid) < 0) || (atoi(vid) > WILDCARD_VID)) {
        printf("\tVID syntax error: allowed values are in the range 0 to %d\n",
                WILDCARD_VID);
        return -1;
    }
    return 0;
}

/* Check the syntax of the aging argument */
int is_aging(char *aging)
{
    int i;

    for (i = 0; aging[i]; i++) {
        if (!isdigit(aging[i])) {
            printf("\tAging syntax error: only decimal values are allowed\n");
            return -1;
        }
    }
    if ((atoi(aging) < MIN_AGING_TIME) || (atoi(aging) > MAX_AGING_TIME)) {
        printf("\tAging syntax error: allowed values are in the range %d to "
               "%d\n", MIN_AGING_TIME, MAX_AGING_TIME);
        return -1;
    }
    return 0;
}

/* Check the syntax of the VLAN Learning Constraints Set ID argument */
int is_sid(char *sid)
{
    int i;

    for (i = 0; sid[i]; i++) {
        if (!isdigit(sid[i])) {
            printf("\tSID syntax error: only decimal values are allowed\n");
            return -1;
        }
    }
    if ((atoi(sid) < 0) || (atoi(sid) > LC_MAX_SET_ID)) {
        printf("\tSID syntax error: allowed values are in the range 0 to "
               "%d\n", LC_MAX_SET_ID);
        return -1;
    }
    return 0;
}

/* Check the syntax of the VLAN Learning Constraints Set ID argument */
int is_fid(char *fid)
{
    int i;

    for (i = 0; fid[i]; i++) {
        if (!isdigit(fid[i])) {
            printf("\tFID syntax error: only decimal values are allowed\n");
            return -1;
        }
    }
    if ((atoi(fid) < 1) || (atoi(fid) >= NUM_FIDS)) { /* TODO: check this */
        printf("\tFID syntax error: allowed values are in the range 1 to "
               "%d\n", (NUM_FIDS - 1));
        return -1;
    }
    return 0;
}
