#include "terminalcommander.h"
#include "loggingcategory.h"

#include <QStringList>

static char **commandCompletion(const char* text, int start, int end);
static char *commandCompletionGenerator(const char *text, int state);

static TerminalCommander *s_instance = nullptr;

TerminalCommander *TerminalCommander::instance()
{
    if (!s_instance)
        s_instance = new TerminalCommander();

    return s_instance;
}

void TerminalCommander::destroy()
{
    qDebug() << "Shut down terminal commander. Have a nice day!";
    stopProcess();
}

QList<TerminalCommand> TerminalCommander::commands() const
{
    return m_commands;
}

void TerminalCommander::setCommands(QList<TerminalCommand> commands)
{
    m_commands.append(commands);
}

TerminalCommander::TerminalCommander(QObject *parent) :
    QThread(parent)
{
    // Native terminal commands
    m_commands.append(TerminalCommand("exit", "Exit this application"));
    m_commands.append(TerminalCommand("quit", "Quit this application"));
    m_commands.append(TerminalCommand("help", "Print this help message"));
}

TerminalCommander::~TerminalCommander()
{
    destroy();
    wait();
}

void TerminalCommander::printToTerminal(const QString &message)
{
    rl_printf("%s", message.toStdString().data());
}

void TerminalCommander::stopProcess()
{
    QMutexLocker locker(&m_mutex);
    m_abort = true;
}

void TerminalCommander::run()
{
    m_abort = false;

    qDebug() << "Command list:";
    foreach (const TerminalCommand &command, m_commands) {
        qDebug() << "  -->" << command;
    }

    rl_attempted_completion_function = commandCompletion;

    rl_set_prompt(QString("%1[zigbee]%2 ").arg(terminalColorBoldGray).arg(terminalColorNormal).toStdString().data());
    rl_redisplay();
    rl_bind_key('\t',rl_complete);

    rl_set_signals();

    //rl_clear_signals();
    rl_catch_signals = 1;

    while (true) {
        char *buffer = readline(QString("%1[zigbee]%2 ").arg(terminalColorBoldGray).arg(terminalColorNormal).toStdString().data());
        rl_bind_key('\t',rl_complete);
        if (buffer) {
            if (!QString(buffer).isEmpty()) {
                QStringList tokens = QString(buffer).split(" ");

                // Check quit
                if (tokens.count() == 1 && (tokens.first() == "quit" || tokens.first() == "exit")) {
                    qDebug() << "";
                    rl_on_new_line();
                    rl_replace_line("", 0);
                    rl_redisplay();
                    qDebug() << "Terminal thread stopped";
                    free(buffer);
                    stopProcess();
                    return;

                } else if (tokens.count() == 1 && (tokens.first() == "?" || tokens.first() == "help")) {
                    printHelpMessage();
                    add_history(buffer);
                    free(buffer);
                } else {
                    emit commandReceived(tokens);
                    add_history(buffer);
                    free(buffer);
                }
            }
        }

        QMutexLocker locker(&m_mutex);
        if (m_abort) {
            //qDebug() << "";
            //rl_on_new_line();
            //rl_replace_line("", 0);
            //rl_redisplay();
            //qDebug() << "Terminal thread stopped";
            return;
        }
    }

    msleep(10);
}

void TerminalCommander::rl_printf(const char *fmt, ...)
{
    va_list args;
    bool save_input;
    char *saved_line;
    int saved_point;

    save_input = !RL_ISSTATE(RL_STATE_DONE);

    if (save_input) {
        saved_point = rl_point;
        saved_line = rl_copy_text(0, rl_end);
        rl_save_prompt();
        rl_replace_line("", 0);
        rl_redisplay();
    }

    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);

    if (save_input) {
        rl_restore_prompt();
        rl_replace_line(saved_line, 0);
        rl_point = saved_point;
        rl_redisplay();
        free(saved_line);
    }
}

void TerminalCommander::signalHandler(int status)
{
    Q_UNUSED(status)
    qDebug() << "Terminal thread stopped";
    TerminalCommander::instance()->stopProcess();
}

void TerminalCommander::printHelpMessage()
{
    for (int i = 0; i < m_commands.count(); i++) {
        QString command = QString("\t%1 %2").arg(m_commands.at(i).command(), -20).arg(m_commands.at(i).description());
        rl_printf("%s\n",command.toStdString().data());
    }
}

char **commandCompletion(const char *text, int start, int end)
{
    Q_UNUSED(end)
    rl_completion_append_character = '\0';

    char **matches = (char **) NULL;
    if (start == 0) {
        matches = rl_completion_matches ((char *) text, &commandCompletionGenerator);
    }

    return matches;
}

char *commandCompletionGenerator(const char *text, int state)
{
    //qDebug() << text << state;

    static int list_index, len;
    const char *name;

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

    while (list_index < TerminalCommander::instance()->commands().count() && (name = TerminalCommander::instance()->commands().at(list_index).command().toStdString().data())) {
        //qDebug() << "          " << TerminalCommander::instance()->commands().at(list_index).command().toStdString().data();
        list_index++;
        if (strncmp (name, text, len) == 0) return strdup (name);
    }

    // If no names matched, then return NULL.
    return ((char *) NULL);
}
