/*
    This file is part of the KDE games library
    SPDX-FileCopyrightText: 2001 Andreas Beckermann (b_mann@gmx.de)
    SPDX-FileCopyrightText: 2001 Martin Heni (kde at heni-online.de)

    SPDX-License-Identifier: LGPL-2.0-only
*/

#include "kgamedebugdialog.h"

// own
#include "kfourinline_debug.h"
// KDEGames
#define USE_UNSTABLE_LIBKDEGAMESPRIVATE_API
#include <libkdegamesprivate/kgame/kgame.h>
#include <libkdegamesprivate/kgame/kgamemessage.h>
#include <libkdegamesprivate/kgame/kgamepropertyhandler.h>
#include <libkdegamesprivate/kgame/kplayer.h>
// KF
#include <KLocalizedString>
#include <KStandardGuiItem>
// Qt
#include <QFrame>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QTime>
#include <QTreeWidget>
#include <QVBoxLayout>
// Std
#include <typeinfo>

class KGameDebugDialogPrivate
{
public:
    KGameDebugDialogPrivate()
    {
        mGame = nullptr;

        mGamePage = nullptr;
        mGameProperties = nullptr;
        mGameAddress = nullptr;
        mGameId = nullptr;
        mGameCookie = nullptr;
        mGameMaster = nullptr;
        mGameAdmin = nullptr;
        mGameOffering = nullptr;
        mGameStatus = nullptr;
        mGameRunning = nullptr;
        mGameMaxPlayers = nullptr;
        mGameMinPlayers = nullptr;
        mGamePlayerCount = nullptr;

        mPlayerPage = nullptr;
        mPlayerList = nullptr;
        mPlayerProperties = nullptr;
        mPlayerAddress = nullptr;
        mPlayerId = nullptr;
        mPlayerName = nullptr;
        mPlayerGroup = nullptr;
        mPlayerUserId = nullptr;
        mPlayerMyTurn = nullptr;
        mPlayerAsyncInput = nullptr;
        mPlayerKGameAddress = nullptr;
        mPlayerVirtual = nullptr;
        mPlayerActive = nullptr;
        mPlayerRtti = nullptr;
        mPlayerNetworkPriority = nullptr;

        mMessagePage = nullptr;
        mMessageList = nullptr;
        mHideIdList = nullptr;
    }

    const KGame *mGame;

    QFrame *mGamePage;
    QTreeWidget *mGameProperties;
    QTreeWidgetItem *mGameAddress;
    QTreeWidgetItem *mGameId;
    QTreeWidgetItem *mGameCookie;
    QTreeWidgetItem *mGameMaster;
    QTreeWidgetItem *mGameAdmin;
    QTreeWidgetItem *mGameOffering;
    QTreeWidgetItem *mGameStatus;
    QTreeWidgetItem *mGameRunning;
    QTreeWidgetItem *mGameMaxPlayers;
    QTreeWidgetItem *mGameMinPlayers;
    QTreeWidgetItem *mGamePlayerCount;

    QFrame *mPlayerPage;
    QListWidget *mPlayerList;
    QTreeWidget *mPlayerProperties;
    QTreeWidgetItem *mPlayerAddress;
    QTreeWidgetItem *mPlayerId;
    QTreeWidgetItem *mPlayerName;
    QTreeWidgetItem *mPlayerGroup;
    QTreeWidgetItem *mPlayerUserId;
    QTreeWidgetItem *mPlayerMyTurn;
    QTreeWidgetItem *mPlayerAsyncInput;
    QTreeWidgetItem *mPlayerKGameAddress;
    QTreeWidgetItem *mPlayerVirtual;
    QTreeWidgetItem *mPlayerActive;
    QTreeWidgetItem *mPlayerRtti;
    QTreeWidgetItem *mPlayerNetworkPriority;

    QFrame *mMessagePage;
    QTreeWidget *mMessageList;
    QListWidget *mHideIdList;
};

KGameDebugDialog::KGameDebugDialog(KGame *g, QWidget *parent, bool modal)
    : KPageDialog(parent)
    , d(new KGameDebugDialogPrivate)
{
    setWindowTitle(i18nc("@title:window", "KGame Debug Dialog"));
    // QT5 setButtons(Close);
    // QT5 setDefaultButton(Close);
    setModal(modal);
    // QT5 showButtonSeparator(true);
    setFaceType(KPageDialog::Tabbed);

    initGamePage();
    initPlayerPage();
    initMessagePage();

    setKGame(g);
}

KGameDebugDialog::~KGameDebugDialog()
{
    delete d;
}

void KGameDebugDialog::initGamePage()
{
    d->mGamePage = new QFrame();
    addPage(d->mGamePage, i18n("Debug &KGame"));
    QVBoxLayout *topLayout = new QVBoxLayout(d->mGamePage);
    // QT5 topLayout->setMargin( marginHint() );
    // QT5 topLayout->setSpacing( spacingHint() );
    QHBoxLayout *layout = new QHBoxLayout;
    topLayout->addLayout(layout);

    QTreeWidget *v = new QTreeWidget(d->mGamePage);
    QTreeWidgetItem *vheader = new QTreeWidgetItem();
    vheader->setText(0, tr("Data"));
    vheader->setText(1, tr("Value"));
    v->setHeaderItem(vheader);
    layout->addWidget(v);

    d->mGameProperties = new QTreeWidget(d->mGamePage);
    QTreeWidgetItem *mGamePropertiesHeader = new QTreeWidgetItem();
    mGamePropertiesHeader->setText(0, tr("Property"));
    mGamePropertiesHeader->setText(1, tr("Value"));
    mGamePropertiesHeader->setText(2, tr("Policy"));
    d->mGameProperties->setHeaderItem(mGamePropertiesHeader);
    layout->addWidget(d->mGameProperties);

    QPushButton *b = new QPushButton(i18n("Update"), d->mGamePage);
    connect(b, &QPushButton::pressed, this, &KGameDebugDialog::slotUpdateGameData);
    topLayout->addWidget(b);

    // game data
    d->mGameAddress = new QTreeWidgetItem(v, QStringList(i18n("KGame Pointer")));
    d->mGameId = new QTreeWidgetItem(v, QStringList(i18n("Game ID")));
    d->mGameCookie = new QTreeWidgetItem(v, QStringList(i18n("Game Cookie")));
    d->mGameMaster = new QTreeWidgetItem(v, QStringList(i18n("Is Master")));
    d->mGameAdmin = new QTreeWidgetItem(v, QStringList(i18n("Is Admin")));
    d->mGameOffering = new QTreeWidgetItem(v, QStringList(i18n("Is Offering Connections")));
    d->mGameStatus = new QTreeWidgetItem(v, QStringList(i18n("Game Status")));
    d->mGameRunning = new QTreeWidgetItem(v, QStringList(i18n("Game is Running")));
    d->mGameMaxPlayers = new QTreeWidgetItem(v, QStringList(i18n("Maximal Players")));
    d->mGameMinPlayers = new QTreeWidgetItem(v, QStringList(i18n("Minimal Players")));
    d->mGamePlayerCount = new QTreeWidgetItem(v, QStringList(i18n("Players")));
}

void KGameDebugDialog::initPlayerPage()
{
    d->mPlayerPage = new QFrame();
    addPage(d->mPlayerPage, i18n("Debug &Players"));
    QVBoxLayout *topLayout = new QVBoxLayout(d->mPlayerPage);
    // QT5 topLayout->setMargin( marginHint() );
    // QT5 topLayout->setSpacing( spacingHint() );
    QHBoxLayout *layout = new QHBoxLayout;
    topLayout->addLayout(layout);

    // TODO: connect to the KGame signals for joined/removed players!!!
    QVBoxLayout *listLayout = new QVBoxLayout;
    layout->addLayout(listLayout);
    QLabel *listLabel = new QLabel(i18n("Available Players"), d->mPlayerPage);
    listLayout->addWidget(listLabel);
    d->mPlayerList = new QListWidget(d->mPlayerPage);
    connect(d->mPlayerList, &QListWidget::itemActivated, this, qOverload<QListWidgetItem *>(&KGameDebugDialog::slotUpdatePlayerData));
    listLayout->addWidget(d->mPlayerList);
    d->mPlayerList->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));

    QTreeWidget *v = new QTreeWidget(d->mPlayerPage);
    layout->addWidget(v);
    QTreeWidgetItem *vheader = new QTreeWidgetItem();
    vheader->setText(0, tr("Data"));
    vheader->setText(1, tr("Value"));
    v->setHeaderItem(vheader);

    d->mPlayerProperties = new QTreeWidget(d->mPlayerPage);
    QTreeWidgetItem *mPlayerPropertiesHeader = new QTreeWidgetItem();
    mPlayerPropertiesHeader->setText(0, tr("Property"));
    mPlayerPropertiesHeader->setText(1, tr("Value"));
    mPlayerPropertiesHeader->setText(2, tr("Policy"));
    d->mPlayerProperties->setHeaderItem(mPlayerPropertiesHeader);
    layout->addWidget(d->mPlayerProperties);

    QPushButton *b = new QPushButton(i18n("Update"), d->mPlayerPage);
    connect(b, &QPushButton::pressed, this, &KGameDebugDialog::slotUpdatePlayerList);
    topLayout->addWidget(b);

    d->mPlayerAddress = new QTreeWidgetItem(v, QStringList(i18n("Player Pointer")));
    d->mPlayerId = new QTreeWidgetItem(v, QStringList(i18n("Player ID")));
    d->mPlayerName = new QTreeWidgetItem(v, QStringList(i18n("Player Name")));
    d->mPlayerGroup = new QTreeWidgetItem(v, QStringList(i18n("Player Group")));
    d->mPlayerUserId = new QTreeWidgetItem(v, QStringList(i18n("Player User ID")));
    d->mPlayerMyTurn = new QTreeWidgetItem(v, QStringList(i18n("My Turn")));
    d->mPlayerAsyncInput = new QTreeWidgetItem(v, QStringList(i18n("Async Input")));
    d->mPlayerKGameAddress = new QTreeWidgetItem(v, QStringList(i18n("KGame Address")));
    d->mPlayerVirtual = new QTreeWidgetItem(v, QStringList(i18n("Player is Virtual")));
    d->mPlayerActive = new QTreeWidgetItem(v, QStringList(i18n("Player is Active")));
    d->mPlayerRtti = new QTreeWidgetItem(v, QStringList(i18n("RTTI")));
    d->mPlayerNetworkPriority = new QTreeWidgetItem(v, QStringList(i18n("Network Priority")));
}

void KGameDebugDialog::initMessagePage()
{
    d->mMessagePage = new QFrame();
    addPage(d->mMessagePage, i18n("Debug &Messages"));
    QGridLayout *layout = new QGridLayout(d->mMessagePage);
    // QT5 layout->setMargin(marginHint());
    // QT5 layout->setSpacing(spacingHint());
    d->mMessageList = new QTreeWidget(d->mMessagePage);
    layout->addWidget(d->mMessageList, 0, 0, 10, 4);

    QTreeWidgetItem *mMessageListHeader = new QTreeWidgetItem();
    mMessageListHeader->setText(0, tr("Time"));
    mMessageListHeader->setText(1, tr("ID"));
    mMessageListHeader->setText(2, tr("Receiver"));
    mMessageListHeader->setText(2, tr("Sender"));
    mMessageListHeader->setText(2, tr("ID - Text"));
    d->mMessageList->setHeaderItem(mMessageListHeader);

    QPushButton *hide = new QPushButton(i18n("&>>"), d->mMessagePage);
    connect(hide, &QPushButton::pressed, this, &KGameDebugDialog::slotHideId);
    layout->addWidget(hide, 4, 4);

    QPushButton *show = new QPushButton(i18n("&<<"), d->mMessagePage);
    connect(show, &QPushButton::pressed, this, &KGameDebugDialog::slotShowId);
    layout->addWidget(show, 6, 4);

    QLabel *l = new QLabel(i18n("Do not show IDs:"), d->mMessagePage);
    layout->addWidget(l, 0, 5, 1, 2);
    d->mHideIdList = new QListWidget(d->mMessagePage);
    layout->addWidget(d->mHideIdList, 1, 5, 8, 2);

    QPushButton *clear = new QPushButton(d->mMessagePage);
    KGuiItem::assign(clear, KStandardGuiItem::clear());
    connect(clear, &QPushButton::pressed, this, &KGameDebugDialog::slotClearMessages);
    layout->addWidget(clear, 10, 0, 1, 7);
    // TODO: "show all but..." and "show nothing but..."
}

void KGameDebugDialog::clearPlayerData()
{
    d->mPlayerAddress->setText(1, QLatin1String(""));
    d->mPlayerId->setText(1, QLatin1String(""));
    d->mPlayerName->setText(1, QLatin1String(""));
    d->mPlayerGroup->setText(1, QLatin1String(""));
    d->mPlayerUserId->setText(1, QLatin1String(""));
    d->mPlayerMyTurn->setText(1, QLatin1String(""));
    d->mPlayerAsyncInput->setText(1, QLatin1String(""));
    d->mPlayerKGameAddress->setText(1, QLatin1String(""));
    d->mPlayerVirtual->setText(1, QLatin1String(""));
    d->mPlayerActive->setText(1, QLatin1String(""));
    d->mPlayerRtti->setText(1, QLatin1String(""));
    d->mPlayerNetworkPriority->setText(1, QLatin1String(""));

    d->mPlayerProperties->clear();
}

void KGameDebugDialog::clearGameData()
{
    d->mGameAddress->setText(1, QLatin1String(""));
    d->mGameId->setText(1, QLatin1String(""));
    d->mGameCookie->setText(1, QLatin1String(""));
    d->mGameMaster->setText(1, QLatin1String(""));
    d->mGameAdmin->setText(1, QLatin1String(""));
    d->mGameOffering->setText(1, QLatin1String(""));
    d->mGameStatus->setText(1, QLatin1String(""));
    d->mGameRunning->setText(1, QLatin1String(""));
    d->mGameMaxPlayers->setText(1, QLatin1String(""));
    d->mGameMinPlayers->setText(1, QLatin1String(""));

    d->mGameProperties->clear();
}

void KGameDebugDialog::slotUpdatePlayerData()
{
    if (!d->mGame || d->mPlayerList->currentRow() == -1) {
        return;
    }
    slotUpdatePlayerData(d->mPlayerList->item(d->mPlayerList->currentRow()));
}

void KGameDebugDialog::slotUpdatePlayerList()
{
    QListWidgetItem *i = d->mPlayerList->item(0);
    for (; d->mPlayerList->count() > 0; i = d->mPlayerList->item(0)) {
        removePlayer(i);
    }

    for (QList<KPlayer *>::const_iterator it = d->mGame->playerList()->begin(); it != d->mGame->playerList()->end(); ++it) {
        addPlayer(*it);
    }
}

void KGameDebugDialog::slotUpdateGameData()
{
    if (!d->mGame) {
        d->mGameAddress->setText(1, i18n("NULL pointer"));
        return;
    }

    clearGameData();

    QString buf = QString::asprintf("%p", (void *)d->mGame);
    d->mGameAddress->setText(1, buf);
    d->mGameId->setText(1, QString::number(d->mGame->gameId()));
    d->mGameCookie->setText(1, QString::number(d->mGame->cookie()));
    d->mGameMaster->setText(1, d->mGame->isMaster() ? i18n("True") : i18n("False"));
    d->mGameAdmin->setText(1, d->mGame->isAdmin() ? i18n("True") : i18n("False"));
    d->mGameOffering->setText(1, d->mGame->isOfferingConnections() ? i18n("True") : i18n("False"));
    d->mGameStatus->setText(1, QString::number(d->mGame->gameStatus()));
    d->mGameRunning->setText(1, d->mGame->isRunning() ? i18n("True") : i18n("False"));
    d->mGameMaxPlayers->setText(1, QString::number(d->mGame->maxPlayers()));
    d->mGameMinPlayers->setText(1, QString::number(d->mGame->minPlayers()));
    d->mGamePlayerCount->setText(1, QString::number(d->mGame->playerCount()));

    // TODO ios

    KGamePropertyHandler *handler = d->mGame->dataHandler();
    for (KGamePropertyBase *property : std::as_const(handler->dict())) {
        QString policy;
        switch (property->policy()) {
        case KGamePropertyBase::PolicyClean:
            policy = i18n("Clean");
            break;
        case KGamePropertyBase::PolicyDirty:
            policy = i18n("Dirty");
            break;
        case KGamePropertyBase::PolicyLocal:
            policy = i18n("Local");
            break;
        case KGamePropertyBase::PolicyUndefined:
        default:
            policy = i18n("Undefined");
            break;
        }
        QStringList items;
        items << handler->propertyName(property->id()) << handler->propertyValue(property) << policy;
        new QTreeWidgetItem(d->mGameProperties, items);
        //	qCDebug(KFOURINLINE_LOG) << ": checking for all game properties: found property name" << name;
    }
}

void KGameDebugDialog::slotUpdatePlayerData(QListWidgetItem *item)
{
    if (!item || !d->mGame) {
        return;
    }

    KPlayer *p = d->mGame->findPlayer(item->text().toInt());

    if (!p) {
        qCCritical(KFOURINLINE_LOG) << ": cannot find player";
        return;
    }

    clearPlayerData();

    QString buf = QString::asprintf("%p", (void *)p);
    d->mPlayerAddress->setText(1, buf);
    d->mPlayerId->setText(1, QString::number(p->id()));
    d->mPlayerName->setText(1, p->name());
    d->mPlayerGroup->setText(1, p->group());
    d->mPlayerUserId->setText(1, QString::number(p->userId()));
    d->mPlayerMyTurn->setText(1, p->myTurn() ? i18n("True") : i18n("False"));
    d->mPlayerAsyncInput->setText(1, p->asyncInput() ? i18n("True") : i18n("False"));
    buf = QString::asprintf("%p", (void *)p->game());
    d->mPlayerKGameAddress->setText(1, buf);
    d->mPlayerVirtual->setText(1, p->isVirtual() ? i18n("True") : i18n("False"));
    d->mPlayerActive->setText(1, p->isActive() ? i18n("True") : i18n("False"));
    d->mPlayerRtti->setText(1, QString::number(p->rtti()));
    d->mPlayerNetworkPriority->setText(1, QString::number(p->networkPriority()));

    // TODO ios

    // Properties
    KGamePropertyHandler *handler = p->dataHandler();
    for (KGamePropertyBase *property : std::as_const(handler->dict())) {
        QString policy;
        switch (property->policy()) {
        case KGamePropertyBase::PolicyClean:
            policy = i18n("Clean");
            break;
        case KGamePropertyBase::PolicyDirty:
            policy = i18n("Dirty");
            break;
        case KGamePropertyBase::PolicyLocal:
            policy = i18n("Local");
            break;
        case KGamePropertyBase::PolicyUndefined:
        default:
            policy = i18n("Undefined");
            break;
        }
        QStringList items;
        items << handler->propertyName(property->id()) << handler->propertyValue(property) << policy;
        new QTreeWidgetItem(d->mPlayerProperties, items);
    }
}

void KGameDebugDialog::clearPages()
{
    clearPlayerData();
    clearGameData();
    d->mPlayerList->clear();
    slotClearMessages();
}

void KGameDebugDialog::setKGame(const KGame *g)
{
    slotUnsetKGame();
    d->mGame = g;
    if (g) {
        // TODO: connect to the KGame signals for joined/removed players!!!
        connect(d->mGame, &QObject::destroyed, this, &KGameDebugDialog::slotUnsetKGame);
        //	connect();

        for (QList<KPlayer *>::const_iterator it = d->mGame->playerList()->begin(); it != d->mGame->playerList()->end(); ++it) {
            addPlayer(*it);
        }

        slotUpdateGameData();

        connect(d->mGame, &KGame::signalMessageUpdate, this, &KGameDebugDialog::slotMessageUpdate);
    }
}

void KGameDebugDialog::slotUnsetKGame()
{
    if (d->mGame) {
        disconnect(d->mGame, nullptr, this, nullptr);
    }
    d->mGame = nullptr;
    clearPages();
}

void KGameDebugDialog::addPlayer(KPlayer *p)
{
    if (!p) {
        qCCritical(KFOURINLINE_LOG) << "trying to add NULL player";
        return;
    }

    (void)new QListWidgetItem(QString::number(p->id()), d->mPlayerList);
    // TODO connect to signals, like deleted/removed, ...
}

void KGameDebugDialog::removePlayer(QListWidgetItem *i)
{
    if (!i || !d->mGame) {
        return;
    }
    KPlayer *p = d->mGame->findPlayer(i->text().toInt());
    if (!p) {
        return;
    }
    disconnect(p, nullptr, this, nullptr);
    if (i->isSelected()) {
        clearPlayerData();
    }
    delete i;
}

void KGameDebugDialog::slotMessageUpdate(int msgid, quint32 receiver, quint32 sender)
{
    if (!showId(msgid)) {
        return;
    }
    QString msgidText = KGameMessage::messageId2Text(msgid);
    if (msgidText.isNull()) {
        if (msgid > KGameMessage::IdUser) {
            Q_EMIT signalRequestIdName(msgid - KGameMessage::IdUser, true, msgidText);
        } else {
            Q_EMIT signalRequestIdName(msgid, false, msgidText);
        }
        if (msgidText.isNull()) {
            msgidText = i18n("Unknown");
        }
    }
    const QStringList items {
        QTime::currentTime().toString(),
        QString::number(msgid),
        QString::number(receiver),
        QString::number(sender),
        msgidText,
    };
    new QTreeWidgetItem(d->mMessageList, items);
}

void KGameDebugDialog::slotClearMessages()
{
    d->mMessageList->clear();
}

void KGameDebugDialog::slotShowId()
{
    /* QListBoxItem* i = d->mHideIdList->firstItem();
     for (; i; i = i->next()) {
        if (i->selected()) {
            d->mHideIdList->removeItem(i->);
        }
     }*/
    if (!d->mHideIdList->currentItem()) {
        return;
    }
    d->mHideIdList->takeItem(d->mHideIdList->currentRow());
}

void KGameDebugDialog::slotHideId()
{
    if (!d->mMessageList->currentItem()) {
        return;
    }
    int msgid = d->mMessageList->currentItem()->text(1).toInt();
    if (!showId(msgid)) {
        return;
    }
    (void)new QListWidgetItem(QString::number(msgid), d->mHideIdList);
}

bool KGameDebugDialog::showId(int msgid)
{
    for (int j = 0; j < d->mHideIdList->count(); ++j) {
        QListWidgetItem *i = d->mHideIdList->item(j);
        if (i->text().toInt() == msgid) {
            return false;
        }
    }
    return true;
}

#include "moc_kgamedebugdialog.cpp"
