/*
 * This file is part of Skyward Hub.
 *
 * Skyward Hub 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.
 *
 * Skyward Hub 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
 * Skyward Hub. If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "OutgoingMessagesViewerModule.h"

#include <Core/MessageBroker/MessageBroker.h>
#include <Modules/SkywardHubStrings.h>

#include <QDebug>
#include <QTableWidgetItem>

OutgoingMessagesViewerModule::OutgoingMessagesViewerModule()
    : Module(ModuleId::OUTGOING_MESSAGES_VIEWER)
{
    setupUi();
    customContextMenuActionSetup();

    // Subscribe to commands, ACKs and NACKs
    MessageBroker::getInstance().subscribe(
        Filter::fromString(SkywardHubStrings::logCommandsTopic), this,
        [this](const Message& message, const Filter& filter)
        { handleMsg(message); });
    MessageBroker::getInstance().subscribe(
        Filter::fromString(SkywardHubStrings::mavlink_received_msg_ACK_topic),
        this, [this](const Message& message, const Filter& filter)
        { handleAck(message); });
    MessageBroker::getInstance().subscribe(
        Filter::fromString(SkywardHubStrings::mavlink_received_msg_NACK_topic),
        this, [this](const Message& message, const Filter& filter)
        { handleNack(message); });
    MessageBroker::getInstance().subscribe(
        Filter::fromString(SkywardHubStrings::mavlink_received_msg_WACK_topic),
        this, [this](const Message& message, const Filter& filter)
        { handleWack(message); });

    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this,
            &OutgoingMessagesViewerModule::updateElapsedTime);
}

XmlObject OutgoingMessagesViewerModule::toXmlObject()
{
    XmlObject obj = Module::toXmlObject();

    obj.addAttribute("timestamp", timestamp->isChecked());
    obj.addAttribute("elapsed_time", elapsedTime->isChecked());

    return obj;
}

void OutgoingMessagesViewerModule::fromXmlObject(const XmlObject& obj)
{
    int timestampAtt;
    if (obj.getAttribute("timestamp", timestampAtt))
    {
        timestamp->setChecked(timestampAtt != 0);
    }

    int elapsedTimeAtt;
    if (obj.getAttribute("elapsed_time", elapsedTimeAtt))
    {
        elapsedTime->setChecked(elapsedTimeAtt != 0);
    }
}

void OutgoingMessagesViewerModule::setupUi()
{
    QHBoxLayout* outerLayout = new QHBoxLayout;
    outerLayout->setContentsMargins(0, 0, 0, 0);

    table = new QTableWidget;
    outerLayout->addWidget(table);
    table->setColumnCount(3);

    // Hide elapsed time column
    table->setColumnHidden(1, true);

    // Hide headers
    table->verticalHeader()->setVisible(false);
    table->horizontalHeader()->setVisible(false);

    // Timestamp and elapse time columns resize to fit content while message
    // takes all the remaining space
    table->horizontalHeader()->setSectionResizeMode(
        0, QHeaderView::ResizeToContents);
    table->horizontalHeader()->setSectionResizeMode(
        1, QHeaderView::ResizeToContents);
    table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);

    // Hide vertical scrollbar
    table->verticalScrollBar()->setVisible(false);

    // Disable items selection
    table->setSelectionMode(QAbstractItemView::NoSelection);

    setLayout(outerLayout);
}

void OutgoingMessagesViewerModule::customContextMenuActionSetup()
{
    timestamp = new QAction("Timestamp");
    timestamp->setCheckable(true);
    timestamp->setChecked(true);
    connect(timestamp, &QAction::toggled, this,
            [this]() { table->setColumnHidden(0, !timestamp->isChecked()); });
    customContextMenuActions.append(timestamp);

    elapsedTime = new QAction("Elapsed time");
    elapsedTime->setCheckable(true);
    connect(elapsedTime, &QAction::toggled, this,
            [this]()
            {
                table->setColumnHidden(1, !elapsedTime->isChecked());

                if (elapsedTime->isChecked() && !timer->isActive())
                {
                    timer->start(timerPeriod);
                }
                else
                {
                    timer->stop();
                }
            });
    customContextMenuActions.append(elapsedTime);

    qDebug() << "Set context menu for outgoing messages viewer";
}

void OutgoingMessagesViewerModule::resizeEvent(QResizeEvent* event)
{
    if (table->rowCount() == 0)
    {
        return;
    }

    int totalHeight   = table->viewport()->height();
    int contentHeight = table->rowCount() * table->rowHeight(0);

    if (contentHeight <= totalHeight)
    {
        // Calculate how many rows can fit in the available space
        int rowsToAdd = (totalHeight - contentHeight) / table->rowHeight(0);

        // Calculate how many messages are available and not shown
        int rowsAvailable = messages.count() - table->rowCount();

        // Add rows to fill the available space
        for (int i = 0; i < std::min(rowsToAdd, rowsAvailable); i++)
        {
            addMessageToTable(messages.at(table->rowCount()),
                              table->rowCount());
        }
    }
    else
    {
        // Calculate how many rows need to be removed
        int rowsToRemove =
            (contentHeight - totalHeight) / table->rowHeight(0) + 1;

        // Remove rows that don't fit
        int newRowCount = table->rowCount() - rowsToRemove;
        table->setRowCount(newRowCount);
    }
}

void OutgoingMessagesViewerModule::handleMsg(const Message& msg)
{
    // First register the message into the list
    messages.prepend({QDateTime::currentDateTime(), msg});

    // Check if the list is overgrowing
    if (messages.count() > maxListSize)
    {
        messages.removeLast();
    }

    // Insert the message into the table
    addMessageToTable(messages.first());

    // Update the table size
    resizeEvent();
}

void OutgoingMessagesViewerModule::handleAck(const Message& ack)
{
    // Color the message the ack is for with green background
    for (int i = 0; i < messages.count(); i++)
    {
        Message msg = messages.at(i).second;

        if (msg.getField("message_id") == ack.getField("recv_msgid") &&
            msg.getField("sequence_number") == ack.getField("seq_ack"))
        {
            if (table->rowCount() > i)
            {
                table->item(i, 1)->setBackground(QBrush(QColor(0, 255, 0)));
            }
        }
    }
}

void OutgoingMessagesViewerModule::handleNack(const Message& nack)
{
    // Color the message the nack is for with red background
    for (int i = 0; i < messages.count(); i++)
    {
        Message msg = messages.at(i).second;

        if (msg.getField("message_id") == nack.getField("recv_msgid") &&
            msg.getField("sequence_number") == nack.getField("seq_ack"))
        {
            if (table->rowCount() > i)
            {
                table->item(i, 1)->setBackground(QBrush(QColor(255, 0, 0)));
            }
        }
    }
}

void OutgoingMessagesViewerModule::handleWack(const Message& nack)
{
    // Color the message the wack is for with white background
    for (int i = 0; i < messages.count(); i++)
    {
        Message msg = messages.at(i).second;

        if (msg.getField("message_id") == nack.getField("recv_msgid") &&
            msg.getField("sequence_number") == nack.getField("seq_ack"))
        {
            if (table->rowCount() > i)
            {
                table->item(i, 1)->setBackground(QBrush(QColor(255, 255, 255)));
            }
        }
    }
}

void OutgoingMessagesViewerModule::addMessageToTable(
    const QPair<QDateTime, Message>& msg, int row)
{
    // Prepare the row items
    auto timestampTxt   = new QTableWidgetItem(msg.first.toString("hh.mm.ss"));
    auto elapsedTimeTxt = new QTableWidgetItem("-00.00.00");
    auto msgName = new QTableWidgetItem(msg.second.getField("name").toString());
    msgName->setBackground(QBrush(QColor(80, 80, 80)));
    msgName->setForeground(QBrush(QColor(0, 0, 0)));

    // Add a new row
    table->insertRow(row);

    // Fill the row with data
    table->setItem(row, 0, timestampTxt);
    table->setItem(row, 1, elapsedTimeTxt);
    table->setItem(row, 2, msgName);
}

void OutgoingMessagesViewerModule::updateElapsedTime()
{
    auto currentTime = QDateTime::currentDateTime();

    for (int i = 0; i < std::min(table->rowCount(), messages.count()); i++)
    {
        auto date = messages[i].first;

        auto elapsedSeconds = date.secsTo(currentTime) % 60;
        auto elapsedMinutes = (date.secsTo(currentTime) / 60) % 60;
        auto elapsedHours   = date.secsTo(currentTime) / 3600;

        auto elapsedTimeTxt = QString("-%1:%2:%3")
                                  .arg(elapsedHours, 2, 'f', 0, '0')
                                  .arg(elapsedMinutes, 2, 'f', 0, '0')
                                  .arg(elapsedSeconds, 2, 'f', 0, '0');

        table->item(i, 1)->setText(elapsedTimeTxt);
    }
}