//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
//
// Modified 2018 iceman <iceman at iuse.se>
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// Main command parser entry point
//-----------------------------------------------------------------------------

// ensure gmtime_r is available even with -std=c99; must be included before
#if !defined(_WIN32)
#define _POSIX_C_SOURCE 200112L
#endif
#include "cmdmain.h"

#include <string.h>
#include <ctype.h>
#include <time.h> // MingW
#include <stdlib.h>  // calloc

#include "comms.h"
#include "cmdhf.h"
#include "cmddata.h"
#include "cmdhw.h"
#include "cmdlf.h"
#include "cmdtrace.h"
#include "cmdscript.h"
#include "cmdcrc.h"
#include "cmdanalyse.h"
#include "emv/cmdemv.h"   // EMV
#include "cmdflashmem.h"  // rdv40 flashmem commands
#include "cmdsmartcard.h" // rdv40 smart card ISO7816 commands
#include "cmdusart.h"     // rdv40 FPC USART commands
#include "cmdwiegand.h"   // wiegand commands
#include "ui.h"
#include "util_posix.h"
#include "commonutil.h"   // ARRAYLEN
#include "preferences.h"
#include "cliparser.h"

static int CmdHelp(const char *Cmd);

static void AppendDate(char *s, size_t slen, const char *fmt) {
    struct tm *ct, tm_buf;
    time_t now = time(NULL);
#if defined(_WIN32)
    ct = gmtime_s(&tm_buf, &now) == 0 ? &tm_buf : NULL;
#else
    ct = gmtime_r(&now, &tm_buf);
#endif
    if (fmt == NULL)
        strftime(s, slen, "%Y-%m-%dT%H:%M:%SZ", ct);  // ISO8601
    else
        strftime(s, slen, fmt, ct);
}

static int lf_search_plus(const char *Cmd) {

    sample_config oldconfig;
    memset(&oldconfig, 0, sizeof(sample_config));

    int retval = lf_getconfig(&oldconfig);

    if (retval != PM3_SUCCESS) {
        PrintAndLogEx(ERR, "failed to get current device config");
        return retval;
    }

    // Divisor : frequency(khz)
    // 95      88      47      31      23
    // 125.00  134.83  250.00  375.00  500.00

    int16_t default_divisor[] = {95, 88, 47, 31, 23};

    /*
      default LF config is set to:
      decimation = 1
      bits_per_sample = 8
      averaging = YES
      divisor = 95 (125kHz)
      trigger_threshold = 0
      samples_to_skip = 0
      verbose = YES
    */
    sample_config config = {
        .decimation = 1,
        .bits_per_sample = 8,
        .averaging = 1,
        .trigger_threshold = 0,
        .samples_to_skip = 0,
        .verbose = false
    };

    // Iteration defaults
    for (int i = 0; i < ARRAYLEN(default_divisor); ++i) {

        if (kbd_enter_pressed()) {
            PrintAndLogEx(INFO, "Keyboard pressed. Done.");
            break;
        }
        // Try to change config!
        uint32_t d;
        d = config.divisor = default_divisor[i];
        PrintAndLogEx(INFO, "-->  trying  (" _GREEN_("%d.%02d kHz")")", 12000 / (d + 1), ((1200000 + (d + 1) / 2) / (d + 1)) - ((12000 / (d + 1)) * 100));

        retval = lf_config(&config);
        if (retval != PM3_SUCCESS)
            break;

        // The config for pm3 is changed, we can trying search!
        retval = CmdLFfind(Cmd);
        if (retval == PM3_SUCCESS)
            break;

    }

    lf_config(&oldconfig);
    return retval;
}

static int CmdAuto(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "auto",
                  "Run LF SEARCH / HF SEARCH / DATA PLOT / DATA SAVE",
                  "auto"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    CLIParserFree(ctx);

    PrintAndLogEx(INFO, "lf search");
    int ret = CmdLFfind("");
    if (ret == PM3_SUCCESS)
        return ret;

    PrintAndLogEx(INFO, "hf search");
    ret = CmdHFSearch("");
    if (ret == PM3_SUCCESS)
        return ret;

    PrintAndLogEx(INFO, "lf search - unknown");
    ret = lf_search_plus("");
    if (ret == PM3_SUCCESS)
        return ret;

    PrintAndLogEx(INFO, "Failed both LF / HF SEARCH,");
    PrintAndLogEx(INFO, "Trying " _YELLOW_("`lf read`") " and save a trace for you");

    CmdPlot("");
    lf_read(false, 40000);
    char *fname = calloc(100, sizeof(uint8_t));
    AppendDate(fname, 100, "-f lf_unknown_%Y-%m-%d_%H:%M");
    CmdSave(fname);
    free(fname);
    return PM3_SUCCESS;
}

int CmdRem(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "rem",
                  "Add a text line in log file",
                  "rem my message    -> adds a timestamp with `my message`"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_strx1(NULL, NULL, NULL, "message line you want inserted"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    struct arg_str* foo = arg_get_str(ctx, 1);
    size_t count = 0;
    size_t len = 0;
    do {
        count += strlen(foo->sval[len]);
    } while (len++ < (foo->count - 1));

    char s[count + foo->count];
    memset(s, 0, sizeof(s));

    len = 0;
    do {
        snprintf(s + strlen(s), sizeof(s) - strlen(s), "%s ", foo->sval[len]);
    } while (len++ < (foo->count - 1));

    CLIParserFree(ctx);
    char buf[22] = {0};
    AppendDate(buf, sizeof(buf), NULL);
    PrintAndLogEx(SUCCESS, "%s remark: %s", buf, s);
    return PM3_SUCCESS;
}

static int CmdHints(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hints",
                  "Turn on/off hints",
                  "hints --on\n"
                  "hints -1\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("1", "on", "turn on hints"),
        arg_lit0("0", "off", "turn off hints"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool turn_on = arg_get_lit(ctx, 1);
    bool turn_off = arg_get_lit(ctx, 2);
    CLIParserFree(ctx);

    if (turn_on && turn_off) {
        PrintAndLogEx(ERR, "you can't turn off and on at the same time");
        return PM3_EINVARG;
    }

    if (turn_off) {
        session.show_hints = false;
    } else if (turn_on) {
        session.show_hints = true;
    }

    PrintAndLogEx(INFO, "Hints are %s", (session.show_hints) ? "ON" : "OFF");
    return PM3_SUCCESS;
}

static int CmdMsleep(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "msleep",
                  "Sleep for given amount of milliseconds",
                  "msleep 100"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_int0("t", "ms", "<ms>", "time in milliseconds"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);
    uint32_t ms = arg_get_u32_def(ctx, 1, 0);
    CLIParserFree(ctx);

    if (ms == 0) {
        PrintAndLogEx(ERR, "Specified invalid input. Can't be zero");
        return PM3_EINVARG;
    }

    msleep(ms);
    return PM3_SUCCESS;
}

static int CmdQuit(const char *Cmd) {
    (void)Cmd; // Cmd is not used so far
    return PM3_EFATAL;
}

static int CmdRev(const char *Cmd) {
    CmdCrc(Cmd);
    return PM3_SUCCESS;
}

static int CmdPref(const char *Cmd) {
    CmdPreferences(Cmd);
    return PM3_SUCCESS;
}

static int CmdClear(const char *Cmd) {
    PrintAndLogEx(NORMAL, _CLEAR_ _TOP_ "");
    return PM3_SUCCESS;
}

static command_t CommandTable[] = {

    {"--------", CmdHelp,      AlwaysAvailable,         "----------------------- " _CYAN_("Technology") " -----------------------"},

    {"analyse", CmdAnalyse,   AlwaysAvailable,         "{ Analyse utils... }"},
    {"data",    CmdData,      AlwaysAvailable,         "{ Plot window / data buffer manipulation... }"},
    {"emv",     CmdEMV,       AlwaysAvailable,         "{ EMV ISO-14443 / ISO-7816... }"},
    {"hf",      CmdHF,        AlwaysAvailable,         "{ High frequency commands... }"},
    {"hw",      CmdHW,        AlwaysAvailable,         "{ Hardware commands... }"},
    {"lf",      CmdLF,        AlwaysAvailable,         "{ Low frequency commands... }"},
    {"mem",     CmdFlashMem,  IfPm3Flash,              "{ Flash memory manipulation... }"},
    {"reveng",  CmdRev,       AlwaysAvailable,         "{ CRC calculations from RevEng software... }"},
    {"smart",   CmdSmartcard, AlwaysAvailable,         "{ Smart card ISO-7816 commands... }"},
    {"script",  CmdScript,    AlwaysAvailable,         "{ Scripting commands... }"},
    {"trace",   CmdTrace,     AlwaysAvailable,         "{ Trace manipulation... }"},
    {"usart",   CmdUsart,     IfPm3FpcUsartFromUsb,    "{ USART commands... }"},
    {"wiegand", CmdWiegand,   AlwaysAvailable,         "{ Wiegand format manipulation... }"},
    {"--------", CmdHelp,      AlwaysAvailable,         "----------------------- " _CYAN_("General") " -----------------------"},
    {"auto",    CmdAuto,      IfPm3Present,            "Automated detection process for unknown tags"},
    {"clear",   CmdClear,     AlwaysAvailable,         "Clear screen"},
    {"help",    CmdHelp,      AlwaysAvailable,         "This help. Use " _YELLOW_("'<command> help'") " for details of a particular command."},
    {"hints",   CmdHints,     AlwaysAvailable,         "Turn hints on / off"},
    {"msleep",  CmdMsleep,    AlwaysAvailable,         "Add a pause in milliseconds"},
    {"pref",    CmdPref,      AlwaysAvailable,         "Edit preferences"},
    {"rem",     CmdRem,       AlwaysAvailable,         "Add a text line in log file"},
    {"quit",    CmdQuit,      AlwaysAvailable,         ""},
    {"exit",    CmdQuit,      AlwaysAvailable,         "Exit program"},
    {NULL, NULL, NULL, NULL}
};

static int CmdHelp(const char *Cmd) {
    (void)Cmd; // Cmd is not used so far
    CmdsHelp(CommandTable);
    return PM3_SUCCESS;
}

//-----------------------------------------------------------------------------
// Entry point into our code: called whenever the user types a command and
// then presses Enter, which the full command line that they typed.
//-----------------------------------------------------------------------------
int CommandReceived(char *Cmd) {
    return CmdsParse(CommandTable, Cmd);
}

int CommandReceivedCB(char *Cmd, print_cb_t callback) {
    g_printCallback = callback;
    int res = CmdsParse(CommandTable, Cmd);
    g_printCallback = NULL;
   return res;
}

command_t *getTopLevelCommandTable(void) {
    return CommandTable;
}

