/*=====================================================================

QGroundControl Open Source Ground Control Station

(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL 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 3 of the License, or
    (at your option) any later version.

    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

======================================================================*/

/**
 * @file
 *   @brief This file implements the Debug Console, a serial console built-in to QGC. 
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */
#include <QPainter>
#include <QSettings>
#include <QScrollBar>
#include <QDebug>

#include "DebugConsole.h"
#include "ui_DebugConsole.h"
#include "LinkManager.h"
#include "UASManager.h"
#include "protocol.h"
#include "QGC.h"

const float DebugConsole::inDataRateThreshold = 0.4f;

DebugConsole::DebugConsole(QWidget *parent) :
    QWidget(parent),
    currLink(NULL),
    holdOn(false),
    convertToAscii(true),
    filterMAVLINK(false),
    autoHold(true),
    bytesToIgnore(0),
    lastByte(-1),
    escReceived(false),
    escIndex(0),
    sentBytes(),
    holdBuffer(),
    lineBuffer(""),
    lastLineBuffer(0),
    lineBufferTimer(),
    snapShotTimer(),
    lowpassInDataRate(0.0f),
    lowpassOutDataRate(0.0f),
    commandIndex(0),
    m_ui(new Ui::DebugConsole)
{
    // Setup basic user interface
    m_ui->setupUi(this);
    // Hide sent text field - it is only useful after send has been hit
    m_ui->sentText->setVisible(false);
    // Hide auto-send checkbox
    //m_ui->specialCheckBox->setVisible(false);
    // Make text area not editable
    m_ui->receiveText->setReadOnly(false);
    // Limit to 500 lines
    m_ui->receiveText->setMaximumBlockCount(500);
    // Allow to wrap everywhere
    m_ui->receiveText->setWordWrapMode(QTextOption::WrapAnywhere);

    // Load settings for this widget
    loadSettings();

    // Enable traffic measurements. We only start/stop the timer as our links change, as
    // these calculations are dependent on the specific link.
    connect(&snapShotTimer, SIGNAL(timeout()), this, SLOT(updateTrafficMeasurements()));
    snapShotTimer.setInterval(snapShotInterval);

    // First connect management slots, then make sure to add all existing objects
    // Connect to link manager to get notified about new links
    connect(LinkManager::instance(), SIGNAL(newLink(LinkInterface*)), this, SLOT(addLink(LinkInterface*)));
    // Connect to UAS manager to get notified about new UAS
    connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(uasCreated(UASInterface*)));

    // Get a list of all existing links
    links = QList<LinkInterface*>();
    foreach (LinkInterface* link, LinkManager::instance()->getLinks()) {
        addLink(link);
    }

    // Get a list of all existing UAS
    foreach (UASInterface* uas, UASManager::instance()->getUASList()) {
        uasCreated(uas);
    }

    // Connect link combo box
    connect(m_ui->linkComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(linkSelected(int)));
    // Connect send button
    connect(m_ui->transmitButton, SIGNAL(clicked()), this, SLOT(sendBytes()));
    // Connect HEX conversion and MAVLINK filter checkboxes
    connect(m_ui->mavlinkCheckBox, SIGNAL(clicked(bool)), this, SLOT(MAVLINKfilterEnabled(bool)));
    connect(m_ui->hexCheckBox, SIGNAL(clicked(bool)), this, SLOT(hexModeEnabled(bool)));
    connect(m_ui->holdCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAutoHold(bool)));
    // Connect hold button
    connect(m_ui->holdButton, SIGNAL(toggled(bool)), this, SLOT(hold(bool)));
    // Connect connect button
    connect(m_ui->connectButton, SIGNAL(clicked()), this, SLOT(handleConnectButton()));
    // Connect the special chars combo box
    connect(m_ui->addSymbolButton, SIGNAL(clicked()), this, SLOT(appendSpecialSymbol()));
    // Connect Checkbox
    connect(m_ui->specialComboBox, SIGNAL(highlighted(QString)), this, SLOT(specialSymbolSelected(QString)));
    // Allow to send via return
    connect(m_ui->sendText, SIGNAL(returnPressed()), this, SLOT(sendBytes()));
}

void DebugConsole::hideEvent(QHideEvent* event)
{
    Q_UNUSED(event);
    storeSettings();
}

DebugConsole::~DebugConsole()
{
    storeSettings();
    delete m_ui;
}

void DebugConsole::loadSettings()
{
    // Load defaults from settings
    QSettings settings;
    settings.sync();
    settings.beginGroup("QGC_DEBUG_CONSOLE");
    m_ui->specialComboBox->setCurrentIndex(settings.value("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex()).toInt());
    m_ui->specialCheckBox->setChecked(settings.value("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked()).toBool());
    hexModeEnabled(settings.value("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked()).toBool());
    MAVLINKfilterEnabled(settings.value("MAVLINK_FILTER_ENABLED", filterMAVLINK).toBool());
    setAutoHold(settings.value("AUTO_HOLD_ENABLED", autoHold).toBool());
    settings.endGroup();
}

void DebugConsole::storeSettings()
{
    // Store settings
    QSettings settings;
    settings.beginGroup("QGC_DEBUG_CONSOLE");
    settings.setValue("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex());
    settings.setValue("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked());
    settings.setValue("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked());
    settings.setValue("MAVLINK_FILTER_ENABLED", filterMAVLINK);
    settings.setValue("AUTO_HOLD_ENABLED", autoHold);
    settings.endGroup();
    settings.sync();
}

void DebugConsole::uasCreated(UASInterface* uas)
{
    connect(uas, SIGNAL(textMessageReceived(int,int,int,QString)),
            this, SLOT(receiveTextMessage(int,int,int,QString)), Qt::UniqueConnection);
}

/**
 * Add a link to the debug console output
 */
void DebugConsole::addLink(LinkInterface* link)
{
    // Add link to link list
    links.insert(link->getId(), link);

    m_ui->linkComboBox->insertItem(link->getId(), link->getName());
    // Set new item as current
    m_ui->linkComboBox->setCurrentIndex(qMax(0, links.size() - 1));
    linkSelected(m_ui->linkComboBox->currentIndex());

    // Register for name changes
    connect(link, SIGNAL(nameChanged(QString)), this, SLOT(updateLinkName(QString)), Qt::UniqueConnection);
    connect(link, SIGNAL(deleteLink(LinkInterface* const)), this, SLOT(removeLink(LinkInterface* const)), Qt::UniqueConnection);
}

void DebugConsole::removeLink(LinkInterface* const linkInterface)
{
    // Add link to link list
    if (links.contains(linkInterface)) {
        int linkIndex = links.indexOf(linkInterface);

        links.removeAt(linkIndex);

        m_ui->linkComboBox->removeItem(linkIndex);
    }
    // Now if this was the current link, clean up some stuff.
    if (linkInterface == currLink)
    {
        // Like disable the update time for the UI.
        snapShotTimer.stop();

        currLink = NULL;
    }
}
void DebugConsole::linkStatusUpdate(const QString& name,const QString& text)
{
    Q_UNUSED(name);
    m_ui->receiveText->appendPlainText(text);
    // Ensure text area scrolls correctly
    m_ui->receiveText->ensureCursorVisible();
}

void DebugConsole::linkSelected(int linkId)
{
    // Disconnect
    if (currLink)
    {
        disconnect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray)));
        disconnect(currLink, SIGNAL(connected(bool)), this, SLOT(setConnectionState(bool)));
        disconnect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString)));
        snapShotTimer.stop();
    }

    // Clear data
    m_ui->receiveText->clear();

    // Connect new link
    if (linkId != -1) {
        currLink = links[linkId];
        connect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray)));
        connect(currLink, SIGNAL(connected(bool)), this, SLOT(setConnectionState(bool)));
        connect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString)));
        setConnectionState(currLink->isConnected());
        snapShotTimer.start();
    }
}

/**
 * @param name new name for this link - the link is determined to the sender to this slot by QObject::sender()
 */
void DebugConsole::updateLinkName(QString name)
{
	// Set name if signal came from a link
    LinkInterface* link = qobject_cast<LinkInterface*>(sender());
	if((link != NULL) && (links.contains(link)))
	{
		const qint16 &linkIndex(links.indexOf(link));
		m_ui->linkComboBox->setItemText(linkIndex,name);
	}
}

void DebugConsole::setAutoHold(bool hold)
{
    // Disable current hold if hold had been enabled
    if (autoHold && holdOn && !hold) {
        this->hold(false);
        m_ui->holdButton->setChecked(false);
    }
    // Set auto hold checkbox
    if (m_ui->holdCheckBox->isChecked() != hold) {
        m_ui->holdCheckBox->setChecked(hold);
    }

    if (!hold)
    {
        // Warn user about not activated hold
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QColor(Qt::red).name(), tr("WARNING: You have NOT enabled auto-hold (stops updating the console if huge amounts of serial data arrive). Updating the console consumes significant CPU load, so if you receive more than about 5 KB/s of serial data, make sure to enable auto-hold if not using the console.")));
    }
    else
    {
        m_ui->receiveText->clear();
    }

    // Set new state
    autoHold = hold;
}

/**
 * Prints the message in the UAS color
 */
void DebugConsole::receiveTextMessage(int id, int component, int severity, QString text)
{
    Q_UNUSED(severity);
    if (isVisible())
    {
        QString name = UASManager::instance()->getUASForId(id)->getUASName();
        QString comp;
        // Get a human readable name if possible
        switch (component) {
            // TODO: To be completed
        case MAV_COMP_ID_IMU:
            comp = tr("IMU");
            break;
        case MAV_COMP_ID_MAPPER:
            comp = tr("MAPPER");
            break;
        case MAV_COMP_ID_MISSIONPLANNER:
            comp = tr("MISSION");
            break;
        case MAV_COMP_ID_SYSTEM_CONTROL:
            comp = tr("SYS-CONTROL");
            break;
        default:
            comp = QString::number(component);
            break;
        }

        //turn off updates while we're appending content to avoid breaking the autoscroll behavior
        m_ui->receiveText->setUpdatesEnabled(false);
        QScrollBar *scroller = m_ui->receiveText->verticalScrollBar();

        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">(%2:%3) %4</font>\n").arg(UASManager::instance()->getUASForId(id)->getColor().name(), name, comp, text));

        // Ensure text area scrolls correctly
        scroller->setValue(scroller->maximum());
        m_ui->receiveText->setUpdatesEnabled(true);
    }
}

/**
 * This function updates the speed indicator text in the GUI.
 * Additionally, if this speed is too high, the display of incoming characters is disabled.
 */
void DebugConsole::updateTrafficMeasurements()
{
    // Calculate the rate of incoming data, converting to
    // kilobytes per second from the received bits per second.
    qint64 inDataRate = currLink->getCurrentInDataRate() / 1000.0f;
    lowpassInDataRate = lowpassInDataRate * 0.9f + (0.1f * inDataRate / 8.0f);

    // If the incoming data rate is faster than our threshold, don't display the data.
    // We don't use the low-passed data rate as we want the true data rate. The low-passed data
    // is just for displaying to the user to remove jitter.
    if ((inDataRate > inDataRateThreshold) && autoHold) {
        // Enable auto-hold
        m_ui->holdButton->setChecked(true);
        hold(true);
    }

    // Update the incoming data rate label.
    m_ui->downSpeedLabel->setText(tr("%L1 kB/s").arg(lowpassInDataRate, 4, 'f', 1, '0'));

    // Calculate the rate of outgoing data, converting to
    // kilobytes per second from the received bits per second.
    lowpassOutDataRate = lowpassOutDataRate * 0.9f + (0.1f * currLink->getCurrentOutDataRate() / 8.0f / 1000.0f);
   
    // Update the outoing data rate label.
    m_ui->upSpeedLabel->setText(tr("%L1 kB/s").arg(lowpassOutDataRate, 4, 'f', 1, '0'));
}

void DebugConsole::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
}

void DebugConsole::receiveBytes(LinkInterface* link, QByteArray bytes)
{
    int len = bytes.size();
    int lastSpace = 0;
    if ((this->bytesToIgnore > 260) || (this->bytesToIgnore < -2)) this->bytesToIgnore = 0;
    // Only add data from current link
    if (link == currLink && !holdOn)
    {
        // Parse all bytes
        for (int j = 0; j < len; j++)
        {
            unsigned char byte = bytes.at(j);
            // Filter MAVLink (http://qgroundcontrol.org/mavlink/) messages out of the stream.
            if (filterMAVLINK)
            {
                if (this->bytesToIgnore > 0)
                {
                    if ( (j + this->bytesToIgnore) < len )
                        j += this->bytesToIgnore - 1, this->bytesToIgnore = 1;
                    else
                        this->bytesToIgnore -= (len - j - 1), j = len - 1;
                } else
                if (this->bytesToIgnore == -2)
                {   // Payload plus header - but we got STX already
                    this->bytesToIgnore = static_cast<unsigned int>(byte) + MAVLINK_NUM_NON_PAYLOAD_BYTES - 1;
                    if ( (j + this->bytesToIgnore) < len )
                        j += this->bytesToIgnore - 1, this->bytesToIgnore = 1;
                    else
                        this->bytesToIgnore -= (len - j - 1), j = len - 1;
                } else
                // Filtering is done by setting an ignore counter based on the MAVLINK packet length
                if (static_cast<unsigned char>(byte) == MAVLINK_STX)
                {
                    this->bytesToIgnore = -1;
                } else
                    this->bytesToIgnore = 0;
            } else this->bytesToIgnore = 0;

            if ( (this->bytesToIgnore <= 0) && (this->bytesToIgnore != -1) )
            {
                QString str;
                // Convert to ASCII for readability
                if (convertToAscii)
                {
                    if (escReceived)
                    {
                        if (escIndex < static_cast<int>(sizeof(escBytes)))
                        {
                            escBytes[escIndex] = byte;
                            if (/*escIndex == 1 && */escBytes[escIndex] == 0x48)
                            {
                                // Handle sequence
                                // for this one, clear all text
                                m_ui->receiveText->clear();
                                escReceived = false;
                            }
                            else if (escBytes[escIndex] == 0x4b)
                            {
                                // Handle sequence
                                // for this one, do nothing
                                escReceived = false;
                            }
                            else if (byte == 0x5b)
                            {
                                // Do nothing, this is still a valid escape sequence
                            }
                            else
                            {
                                escReceived = false;
                            }
                         }
                        else
                        {
                            // Obviously something went wrong, reset
                            escReceived = false;
                            escIndex = 0;
                        }
                    }
                    else if ((byte <= 32) || (byte > 126))
                    {
                        switch (byte)
                        {
                            case (unsigned char)'\n':   // Accept line feed
                                if (lastByte != '\r')   // Do not break line again for LF+CR
                                    str.append(byte);   // only break line for single LF or CR bytes
                            break;
                            case (unsigned char)' ':    // space of any type means don't add another on hex output
                            case (unsigned char)'\t':   // Accept tab
                            case (unsigned char)'\r':   // Catch and carriage return
                                if (lastByte != '\n')   // Do not break line again for CR+LF
                                str.append(byte);       // only break line for single LF or CR bytes
                                lastSpace = 1;
                            break;
                            /* VT100 emulation (partially */
                            case 0x1b:                  // ESC received
                                escReceived = true;
                                escIndex = 0;
                                //qDebug() << "GOT ESC";
                                break;
                            case 0x08:                  // BS (backspace) received
                                // Do nothing for now
                                break;
                            default:                    // Append replacement character (box) if char is not ASCII
                                QString str2;
                                if ( lastSpace == 1)
                                    str2.sprintf("0x%02x ", byte);
                                else str2.sprintf(" 0x%02x ", byte);
                                str.append(str2);
                                lastSpace = 1;
                                escReceived = false;
                            break;
                        }
                    }
                    else
                    {
                        // Ignore carriage return, because that
                        // is auto-added with '\n'
                        if (byte != '\r') str.append(byte);           // Append original character
                        lastSpace = 0;
                    }
                }
                else
                {
                    QString str2;
                    str2.sprintf("%02x ", byte);
                    str.append(str2);
                }
                lineBuffer.append(str);
                lastByte = byte;
            }
            else
            {
                if (filterMAVLINK) this->bytesToIgnore--;
            }

        }
        // Plot every 200 ms if windows is visible
        if (lineBuffer.length() > 0 && (QGC::groundTimeMilliseconds() - lastLineBuffer) > 200) {
            if (isVisible())
            {
                m_ui->receiveText->appendPlainText(lineBuffer);
                lineBuffer.clear();
                lastLineBuffer = QGC::groundTimeMilliseconds();
                // Ensure text area scrolls correctly
                m_ui->receiveText->ensureCursorVisible();
            }
            if (lineBuffer.size() > 8192)
            {
                lineBuffer.remove(0, 4096);
            }
        }
    }
    else if (link == currLink && holdOn)
    {
        holdBuffer.append(bytes);
        if (holdBuffer.size() > 8192)
            holdBuffer.remove(0, 4096); // drop old stuff
    }
}

QByteArray DebugConsole::symbolNameToBytes(const QString& text)
{
    QByteArray b;
    if (text.contains("CR+LF")) {
        b.append(static_cast<char>(0x0D));
        b.append(static_cast<char>(0x0A));
    } else if (text.contains("LF")) {
        b.append(static_cast<char>(0x0A));
    } else if (text.contains("FF")) {
        b.append(static_cast<char>(0x0C));
    } else if (text.contains("CR")) {
        b.append(static_cast<char>(0x0D));
    } else if (text.contains("TAB")) {
        b.append(static_cast<char>(0x09));
    } else if (text.contains("NUL")) {
        b.append(static_cast<char>(0x00));
    } else if (text.contains("ESC")) {
        b.append(static_cast<char>(0x1B));
    } else if (text.contains("~")) {
        b.append(static_cast<char>(0x7E));
    } else if (text.contains("<Space>")) {
        b.append(static_cast<char>(0x20));
    }
    return b;
}

QString DebugConsole::bytesToSymbolNames(const QByteArray& b)
{
    QString text;
    if (b.size() > 1 && b.contains(0x0D) && b.contains(0x0A)) {
        text = "<CR+LF>";
    } else if (b.contains(0x0A)) {
        text = "<LF>";
    } else if (b.contains(0x0C)) {
        text = "<FF>";
    } else if (b.contains(0x0D)) {
        text = "<CR>";
    } else if (b.contains(0x09)) {
        text = "<TAB>";
    } else if (b.contains((char)0x00)) {
        text = "<NUL>";
    } else if (b.contains(0x1B)) {
        text = "<ESC>";
    } else if (b.contains(0x7E)) {
        text = "<~>";
    } else if (b.contains(0x20)) {
        text = "<Space>";
    } else {
        text.append(b);
    }
    return text;
}

void DebugConsole::specialSymbolSelected(const QString& text)
{
    Q_UNUSED(text);
}

void DebugConsole::appendSpecialSymbol(const QString& text)
{
    QString line = m_ui->sendText->text();
    QByteArray symbols = symbolNameToBytes(text);
    // The text is appended to the enter field
    if (convertToAscii) {
        line.append(symbols);
    } else {

        for (int i = 0; i < symbols.size(); i++) {
            QString add(" 0x%1");
            line.append(add.arg(static_cast<char>(symbols.at(i)), 2, 16, QChar('0')));
        }
    }
    m_ui->sendText->setText(line);
}

void DebugConsole::appendSpecialSymbol()
{
    appendSpecialSymbol(m_ui->specialComboBox->currentText());
}

void DebugConsole::sendBytes()
{
    // FIXME This store settings should be removed
    // once all threading issues have been resolved
    // since its called in the destructor, which
    // is absolutely sufficient
    storeSettings();

    // Store command history
    commandHistory.append(m_ui->sendText->text());
    // Since text was just sent, we're at position commandHistory.length()
    // which is the current text
    commandIndex = commandHistory.length();

    if (!m_ui->sentText->isVisible()) {
        m_ui->sentText->setVisible(true);
    }

    if (!currLink->isConnected()) {
        m_ui->sentText->setText(tr("Nothing sent. The link %1 is unconnected. Please connect first.").arg(currLink->getName()));
        return;
    }

    QString transmitUnconverted = m_ui->sendText->text();
    QByteArray specialSymbol;

    // Append special symbol if checkbox is checked
    if (m_ui->specialCheckBox->isChecked()) {
        // Get auto-add special symbols
        specialSymbol = symbolNameToBytes(m_ui->specialComboBox->currentText());

        // Convert them if needed
        if (!convertToAscii) {
            QString specialSymbolConverted;
            for (int i = 0; i < specialSymbol.length(); i++) {
                QString add(" 0x%1");
                specialSymbolConverted.append(add.arg(static_cast<char>(specialSymbol.at(i)), 2, 16, QChar('0')));
            }
            specialSymbol.clear();
            specialSymbol.append(specialSymbolConverted);
        }
    }

    QByteArray transmit;
    QString feedback;
    bool ok = true;
    if (convertToAscii) {
        // ASCII text is not converted
        transmit = transmitUnconverted.toLatin1();
        // Auto-add special symbol handling
        transmit.append(specialSymbol);

        QString translated;

        // Replace every occurence of a special symbol with its text name
        for (int i = 0; i < transmit.size(); ++i) {
            QByteArray specialChar;
            specialChar.append(transmit.at(i));
            translated.append(bytesToSymbolNames(specialChar));
        }

        feedback.append(translated);
    } else {
        // HEX symbols are converted to bytes
        QString str = transmitUnconverted.toLatin1();
        str.append(specialSymbol);
        str.remove(' ');
        str.remove("0x");
        str = str.simplified();
        int bufferIndex = 0;
        if ((str.size() % 2) == 0) {
            for (int i = 0; i < str.size(); i=i+2) {
                bool okByte;
                QString strBuf = QString(str.at(i));
                strBuf.append(str.at(i+1));
                unsigned char hex = strBuf.toInt(&okByte, 16);
                ok = (ok && okByte);
                transmit[bufferIndex++] = hex;

                if (okByte) {
                    // Feedback
                    feedback.append(str.at(i).toUpper());
                    feedback.append(str.at(i+1).toUpper());
                    feedback.append(" ");
                } else {
                    feedback = tr("HEX format error near \"") + strBuf + "\"";
                }
            }
        } else {
            ok = false;
            feedback = tr("HEX values have to be in pairs, e.g. AA or AA 05");
        }
    }

    // Transmit ASCII or HEX formatted text, only if more than one symbol
    if (ok && m_ui->sendText->text().toLatin1().size() > 0) {
        // Transmit only if conversion succeeded
        currLink->writeBytes(transmit, transmit.size());
        m_ui->sentText->setText(tr("Sent: ") + feedback);
    } else if (m_ui->sendText->text().toLatin1().size() > 0) {
        // Conversion failed, display error message
        m_ui->sentText->setText(tr("Not sent: ") + feedback);
    }

    // Select text to easy follow-up input from user
    m_ui->sendText->selectAll();
    m_ui->sendText->setFocus(Qt::OtherFocusReason);
}

/**
 * @param mode true to convert all in and output to/from HEX, false to send and receive ASCII values
 */
void DebugConsole::hexModeEnabled(bool mode)
{
    if (convertToAscii == mode) {
        convertToAscii = !mode;
        if (m_ui->hexCheckBox->isChecked() != mode) {
            m_ui->hexCheckBox->setChecked(mode);
        }
        m_ui->receiveText->clear();
        m_ui->sendText->clear();
        m_ui->sentText->clear();
        commandHistory.clear();
    }
}

/**
 * @param filter true to ignore all MAVLINK raw data in output, false, to display all incoming data
 */
void DebugConsole::MAVLINKfilterEnabled(bool filter)
{
    if (filterMAVLINK != filter) {
        filterMAVLINK = filter;
        this->bytesToIgnore = 0;
        if (m_ui->mavlinkCheckBox->isChecked() != filter) {
            m_ui->mavlinkCheckBox->setChecked(filter);
        }
    }
}
/**
 * @param hold Freeze the input and thus any scrolling
 */
void DebugConsole::hold(bool hold)
{
    if (holdOn != hold) {
        // Check if we need to append bytes from the hold buffer
        if (this->holdOn && !hold) {
            // TODO No conversion is done to the bytes in the hold buffer
            m_ui->receiveText->appendPlainText(QString(holdBuffer));
            holdBuffer.clear();
            lowpassInDataRate = 0.0f;
        }

        this->holdOn = hold;

        // Change text interaction mode
        if (hold) {
            m_ui->receiveText->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse | Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse);
        } else {
            m_ui->receiveText->setTextInteractionFlags(Qt::NoTextInteraction);
        }
        if (m_ui->holdCheckBox->isChecked() != hold) {
            m_ui->holdCheckBox->setChecked(hold);
        }
    }
}

/**
 * Sets the connection state the widget shows to this state
 */
void DebugConsole::setConnectionState(bool connected)
{
    if(connected) {
        m_ui->connectButton->setText(tr("Disconn."));
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QGC::colorGreen.name(), tr("Link %1 is connected.").arg(currLink->getName())));
    } else {
        m_ui->connectButton->setText(tr("Connect"));
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QGC::colorOrange.name(), tr("Link %1 is unconnected.").arg(currLink->getName())));
    }
}

/** @brief Handle the connect button */
void DebugConsole::handleConnectButton()
{
    if (currLink) {
        if (currLink->isConnected()) {
            currLink->disconnect();
        } else {
            currLink->connect();
        }
    }
}

void DebugConsole::keyPressEvent(QKeyEvent * event)
{
    if (event->key() == Qt::Key_Up) {
        cycleCommandHistory(true);
    } else if (event->key() == Qt::Key_Down) {
        cycleCommandHistory(false);
    } else {
        QWidget::keyPressEvent(event);
    }
}

void DebugConsole::cycleCommandHistory(bool up)
{
    // Only cycle if there is a history
    if (commandHistory.length() > 0) {
        // Store current command if we're not in history yet
        if (commandIndex == commandHistory.length() && up) {
            currCommand = m_ui->sendText->text();
        }

        if (up) {
            // UP
            commandIndex--;
            if (commandIndex >= 0) {
                m_ui->sendText->setText(commandHistory.at(commandIndex));
            }

            // If the index
        } else {
            // DOWN
            commandIndex++;
            if (commandIndex < commandHistory.length()) {
                m_ui->sendText->setText(commandHistory.at(commandIndex));
            }
            // If the index is at history length, load the last current command

        }

        // Restore current command if we went out of history
        if (commandIndex == commandHistory.length()) {
            m_ui->sendText->setText(currCommand);
        }

        // If we are too far down or too far up, wrap around to current command
        if (commandIndex < 0 || commandIndex > commandHistory.length()) {
            commandIndex = commandHistory.length();
            m_ui->sendText->setText(currCommand);
        }

        // Bound the index
        if (commandIndex < 0) commandIndex = 0;
        if (commandIndex > commandHistory.length()) commandIndex = commandHistory.length();
    }
}

void DebugConsole::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}