/*
 * diot_util - diagnostic tool for DIOT system board
 *
 * Author: Adam Wujek for CERN
 * Copyright CERN 2021
 *
 */


#include <stdio.h>
#include <string.h>
#include <gpiod.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>

#include <readline/readline.h>
#include <readline/history.h>
#include "term.h"
#include "diot_util_clk.h"
#include "diot_util_gpio.h"
#include "diot_util_sensors.h"
#include "diot_util_error.h"
#include "fru.h"
#include "fru_utils.h"

#define READLINE_PROMPT "diot_util> "

struct command_entry {
    char *cmd_name;
    int (*cmd_func)(char *params);
    char *cmd_help_string;
    char *cmd_params;
};

/* readline related */
char **command_completion(const char *, int, int);
char *command_name_generator(const char *, int);

/* commands */
int cmd_print_status(char *params);
int cmd_exit(char *params);
int cmd_help(char *params);

struct command_entry commands_list[] = {
    { .cmd_name = "status",            .cmd_func = cmd_print_status,    .cmd_help_string = "Print status\n",                               .cmd_params = NULL},
    { .cmd_name = "exit",              .cmd_func = cmd_exit,            .cmd_help_string = "quit program\n",                               .cmd_params = NULL},
    { .cmd_name = "quit",              .cmd_func = cmd_exit,            .cmd_help_string = "quit program\n",                               .cmd_params = NULL},
    { .cmd_name = "help",              .cmd_func = cmd_help,            .cmd_help_string = "Print help\n",                                 .cmd_params = NULL},
    { .cmd_name = "led2",              .cmd_func = cmd_led2,            .cmd_help_string = "Enable/disable LED2\n",                        .cmd_params = "on|off"},
    { .cmd_name = "peripheral_reset",  .cmd_func = cmd_peripheral_rst,  .cmd_help_string = "Reset peripheral boards\n",                    .cmd_params = NULL},
    { .cmd_name = "power_cycle_crate", .cmd_func = cmd_power_cycle,     .cmd_help_string = "Power cycle DI/OT create\n",                   .cmd_params = NULL},
    { .cmd_name = "sensors_expert",    .cmd_func = cmd_sensors_expert,  .cmd_help_string = "Display sensors in expert mode\n",             .cmd_params = NULL},
    { .cmd_name = "sensors",           .cmd_func = cmd_sensors,         .cmd_help_string = "Display sensors short version\n",              .cmd_params = NULL},
    { .cmd_name = "sensors_refresh",   .cmd_func = cmd_sensors_refresh, .cmd_help_string = "Display sensors short version with refresh\n", .cmd_params = NULL},
    { .cmd_name = "clocks",            .cmd_func = cmd_clocks,          .cmd_help_string = "Display info about clocks\n",                  .cmd_params = NULL},
    { .cmd_name = NULL }
};

int cmd_help(char *params)
{
    struct command_entry *entry = commands_list;
    char buf[50];

    printf("Avaiable commands:\n");
    while (entry->cmd_name) {
	sprintf(buf, "%s %s", entry->cmd_name,
		entry->cmd_params ? entry->cmd_params : "");
	printf("%-18s - %s", buf, entry->cmd_help_string);
	entry++;
    }

    return 0;
}



int cmd_exit(char *params)
{
    printf("Exiting...\n");

    close_gpios();
    exit(0);
}

static void print_slot_status(void)
{
    int i;
    int ret;
    int slot_use;
    uint8_t eeprom_buf[1024*10];
    struct FRU_DATA *fru = NULL;

    printf("Peripheral Boards Info\n");
    printf("-----+---------+----------------------+----------------------+----------------------+----------------------+\n");
    printf("Slot | Present |     Manufacturer     |     Product_name     |     Serial number    |      Part Number     |\n");
    printf("-----+---------+----------------------+----------------------+----------------------+----------------------+\n");

    for (i = 1; i <= 8; i++) {
	slot_use = read_slot_use(i);
	printf("%4d |%8s |", i, slot_use ? "Y": "N");

	if (!slot_use) {
	    printf(" %20s | %20s | %20s | %20s |\n", "", "", "", "");
	    continue;
	}

	ret = read_pb_eeprom(eeprom_buf, sizeof(eeprom_buf), i);
	if (!ret) {
	    printf(" %20s | %20s | %20s | %20s |\n", "read error", "read error", "read error", "read error");
	    continue;
	}

	fru = parse_FRU(eeprom_buf);
	if (!fru || !fru->Board_Area) {
	    printf(" %20s | %20s | %20s | %20s |\n", "parse error", "parse error", "parse error", "parse error");
	    continue;
	}

	printf(" %20s | %20s | %20s | %20s |\n",
	       dump_fru_field_string(fru->Board_Area->manufacturer),
	       dump_fru_field_string(fru->Board_Area->product_name),
	       dump_fru_field_string(fru->Board_Area->serial_number),
	       dump_fru_field_string(fru->Board_Area->part_number)
	       );

	free(fru);
	fru = NULL;
    }

    printf("-----+---------+----------------------+----------------------+----------------------+----------------------+\n");
}

static void print_fmc_status(void)
{
    int ret;
    uint8_t eeprom_buf[1024*10];
    struct FRU_DATA *fru = NULL;

    if (!read_mezzanine_presence())
	return;

    ret = read_fmc_eeprom(eeprom_buf, sizeof(eeprom_buf));
    if (!ret)
	return;

    fru = parse_FRU(eeprom_buf);

    if (!fru || !fru->Board_Area) {
	diot_error_add("Unable to parse FMC's eeprom\n");
	return;
    }

    printf("----------------------+----------------------+----------------------+----------------------+\n");
    printf("     Manufacturer     |     Product_name     |     Serial number    |      Part Number     |\n");
    printf("----------------------+----------------------+----------------------+----------------------+\n");
    printf(" %20s | %20s | %20s | %20s |\n",
	    dump_fru_field_string(fru->Board_Area->manufacturer),
	    dump_fru_field_string(fru->Board_Area->product_name),
	    dump_fru_field_string(fru->Board_Area->serial_number),
	    dump_fru_field_string(fru->Board_Area->part_number)
	    );
    printf("----------------------+----------------------+----------------------+----------------------+\n");

    free(fru);
}

int cmd_print_status(char *params)
{
    diot_error_clear();

    printf("DI/OT Crate Info\n");
    printf("----------------------------+--------------+\n");
    printf("System Board hw revision    | %12d |\n", read_pcb_ver());
    printf("System Board in System Slot | %12s |\n", read_system_slot_presence() ? "yes" : "no");
    printf("FMC mezzanine               | %12s |\n", read_mezzanine_presence() ? "present" : "not present");
    printf("PSU 0                       | %12s |\n", read_psu_presence(1) ? "present" : "not present");
    printf("PSU 1                       | %12s |\n", read_psu_presence(2) ? "present" : "not present");
    printf("PSU Alert                   | %12s |\n", read_psu_alert() ? "yes" : "no");
    printf("----------------------------+--------------+\n");
    printf("\n");

    print_slot_status();
    printf("\n");
    print_fmc_status();
    printf("\n");

    diot_error_print();

    return 1;
}
void main_help(char *prog_name)
{
    printf("Usage:\n");
    printf("%s [-h] [-e <\"cmd1; cmd2; ...\">\n", prog_name);
    printf("   -h - print this help\n");
    printf("   -e <\"cmd1; cmd2; ...\"> - run in script mode, execute all commands passed as argument\n");
}
int main(int argc, char **argv)
{
    struct command_entry *entry = commands_list;

    int ret;
    char *line_read = NULL;
    int cmd_len;
    char *params;
    char *script_commands = NULL;
    char *next_script_cmd = NULL;
    char *curr_script_cmd = NULL;
    int opt;

    printf("DIOT diagnostic tool\n\n");

    while ((opt = getopt(argc, argv, "he:")) != -1) {
	switch(opt)
	{
	    case 'h':
		main_help(argv[0]);
		cmd_help(NULL);
		exit(0);
		break;
	    case 'e':
		next_script_cmd = script_commands = strdup(optarg);
		break;
	    default:
		break;
	}
    }

    if (!script_commands)
	printf("Type \"help\" to list available commands\n");

    ret = open_gpios();

    rl_attempted_completion_function = command_completion;
    rl_completer_word_break_characters = " ";

    while (1) {
	/* If the buffer has already been allocated, return the memory
	to the free pool. */
	if (line_read) {
	    free (line_read);
	    line_read = (char *)NULL;
	}

	if (script_commands) {
	    /* We're in the script mode, parse the input command */
	    curr_script_cmd = next_script_cmd;

	    /* Skip spaces and semicolons */
	    while (curr_script_cmd && *curr_script_cmd && ((*curr_script_cmd == ' ') || (*curr_script_cmd == ';')))
		curr_script_cmd++;

	    if (curr_script_cmd && *curr_script_cmd) {
		/* If there is anything left search for next semicolon */
		next_script_cmd = strchr(curr_script_cmd, ';');

		/* Find the length of the current command */
		if (next_script_cmd) {
		    cmd_len = next_script_cmd - curr_script_cmd;
		} else
		    cmd_len = strlen(curr_script_cmd);

		/* Coppy comand like readline would do */
		line_read = strndup(curr_script_cmd, cmd_len);
	    }
	} else { 
	    /* Get a line from the user. */
	    line_read = readline (READLINE_PROMPT);
	}

	/* Ctrl+D or end of script */
	if (!line_read)
	    cmd_exit(NULL);

	/* If the line has any text in it, save it on the history. */
	if (line_read && *line_read)
	    add_history (line_read);


	params = strchr(line_read, ' ');
	if (params) {
	    cmd_len = params - line_read;
	    /* skip spaces for params */
	    while (*params && (*params == ' '))
		params++;
	} else
	    cmd_len = strlen(line_read);

	entry = commands_list;
	while (entry && entry->cmd_name) {
	    if (cmd_len == strlen(entry->cmd_name)
		&& !strncmp(entry->cmd_name, line_read, cmd_len)
	    ) {
		ret = entry->cmd_func(params);
		if (ret < 0)
		    printf("Command %s failed with error message \"%s\" (%d)\n",
			   entry->cmd_name, strerror(errno), ret);
		break;
	    }
	    entry++;
	}
	if (!(entry && entry->cmd_name)) {
	    printf("Commad \"%s\" not found\n", line_read);
	}
    }

    if (script_commands)
	free(script_commands);
    return 0;
}

char **
command_completion(const char *text, int start, int end)
{
    rl_attempted_completion_over = 1;
    return rl_completion_matches(text, command_name_generator);
}

char *
command_name_generator(const char *text, int state)
{
    static int list_index, len;
    char *name;

    if (!state) {
        list_index = 0;
        len = strlen(text);
    }

    while ((commands_list[list_index].cmd_name) && (name = commands_list[list_index++].cmd_name)) {
        if (strncmp(name, text, len) == 0) {
            return strdup(name);
        }
    }

    return NULL;
}
