#include "../mainwindow.h"
#include "../cheats.h"
#include "createroom.h"
#include "waitroom.h"
#include "../version.h"
#include "../interface/core_commands.h"
#include "netplay_common.h"
#include <QGridLayout>
#include <QLabel>
#include <QCheckBox>
#include <QLineEdit>
#include <QFileDialog>
#include <QMessageBox>
#include <QJsonObject>
#include <QJsonDocument>
#include <QInputDialog>
#include <QLabel>

CreateRoom::CreateRoom(QWidget *parent)
    : QDialog(parent)
{
    QGridLayout *layout = new QGridLayout(this);

    QRegularExpression rx("[a-zA-Z0-9]+");
    QValidator *validator = new QRegularExpressionValidator(rx, this);

    QLabel *nameLabel = new QLabel("Room Name", this);
    layout->addWidget(nameLabel, 0, 0);
    nameEdit = new QLineEdit(this);
    nameEdit->setValidator(validator);
    nameEdit->setMaxLength(30);
    layout->addWidget(nameEdit, 0, 1);

    QLabel *passwordLabel = new QLabel("Password (Optional)", this);
    layout->addWidget(passwordLabel, 1, 0);
    passwordEdit = new QLineEdit(this);
    layout->addWidget(passwordEdit, 1, 1);

    QLabel *romLabel = new QLabel("ROM", this);
    layout->addWidget(romLabel, 2, 0);
    romButton = new QPushButton("ROM Path", this);
    connect(romButton, &QPushButton::released, this, &CreateRoom::handleRomButton);
    layout->addWidget(romButton, 2, 1);

    QLabel *playerNameLabel = new QLabel("Player Name", this);
    layout->addWidget(playerNameLabel, 3, 0);
    playerNameEdit = new QLineEdit(this);
    playerNameEdit->setValidator(validator);
    playerNameEdit->setMaxLength(30);
    if (w->getSettings()->contains("netplay_name"))
        playerNameEdit->setText(w->getSettings()->value("netplay_name").toString());
    layout->addWidget(playerNameEdit, 3, 1);

    QLabel *serverLabel = new QLabel("Server", this);
    layout->addWidget(serverLabel, 6, 0);
    serverChooser = new QComboBox(this);
    serverChooser->setSizeAdjustPolicy(QComboBox::AdjustToContents);
    layout->addWidget(serverChooser, 6, 1);
    connect(serverChooser, &QComboBox::currentIndexChanged, this, &CreateRoom::handleServerChanged);

    QLabel *pingLabel = new QLabel("Your ping:", this);
    layout->addWidget(pingLabel, 7, 0);
    pingValue = new QLabel(this);
    pingValue->setText("(Calculating)");
    layout->addWidget(pingValue, 7, 1);

    QFrame *lineH1 = new QFrame(this);
    lineH1->setFrameShape(QFrame::HLine);
    lineH1->setFrameShadow(QFrame::Sunken);
    layout->addWidget(lineH1, 8, 0, 1, 2);

    createButton = new QPushButton("Create Game", this);
    connect(createButton, &QPushButton::released, this, &CreateRoom::handleCreateButton);
    layout->addWidget(createButton, 9, 0, 1, 2);

    setLayout(layout);

    connect(&manager, &QNetworkAccessManager::finished, this,
            &CreateRoom::downloadFinished);

    connect(this, &CreateRoom::finished, this, &CreateRoom::onFinished);

    QNetworkRequest request(QUrl(QStringLiteral("https://m64p.s3.amazonaws.com/servers.json")));
    manager.get(request);

    broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0);
    connect(&broadcastSocket, &QUdpSocket::readyRead, this, &CreateRoom::processBroadcast);
    QByteArray multirequest;
    multirequest.append(1);
    broadcastSocket.writeDatagram(multirequest, QHostAddress::Broadcast, 45000);

    launched = 0;
}

void CreateRoom::processBroadcast()
{
    while (broadcastSocket.hasPendingDatagrams())
    {
        QNetworkDatagram datagram = broadcastSocket.receiveDatagram();
        QByteArray incomingData = datagram.data();
        QJsonDocument json_doc = QJsonDocument::fromJson(incomingData);
        QJsonObject json = json_doc.object();
        QStringList servers = json.keys();
        for (int i = 0; i < servers.size(); ++i)
            serverChooser->addItem(servers.at(i), json.value(servers.at(i)).toString());
    }
}

void CreateRoom::onFinished(int)
{
    broadcastSocket.close();
    (*CoreDoCommand)(M64CMD_ROM_CLOSE, 0, NULL);
    if (!launched && webSocket)
    {
        webSocket->close();
        webSocket->deleteLater();
    }
}

void CreateRoom::handleRomButton()
{
    filename = QFileDialog::getOpenFileName(this,
                                            tr("Open ROM"), w->getSettings()->value("ROMdir").toString(), tr("ROM Files (*.n64 *.N64 *.z64 *.Z64 *.v64 *.V64 *.rom *.ROM *.zip *.ZIP *.7z)"));
    if (!filename.isNull())
    {
        romButton->setText(filename);
    }
}

void CreateRoom::handleCreateButton()
{
    QMessageBox msgBox;
    if (serverChooser->currentData() == "Custom" && customServerHost.isEmpty())
    {
        msgBox.setText("Custom Server Address is invalid");
        msgBox.exec();
        return;
    }
    if (loadROM(filename) == M64ERR_SUCCESS)
    {
        createButton->setEnabled(false);
        (*CoreDoCommand)(M64CMD_ROM_GET_SETTINGS, sizeof(rom_settings), &rom_settings);

        connectionTimer = new QTimer(this);
        connectionTimer->setSingleShot(true);
        connectionTimer->start(5000);
        connect(connectionTimer, &QTimer::timeout, this, &CreateRoom::connectionFailed);
        connect(webSocket, &QWebSocket::disconnected, connectionTimer, &QTimer::stop);
        connect(webSocket, &QObject::destroyed, connectionTimer, &QTimer::stop);

        if (webSocket->isValid())
        {
            createRoom();
        }
        else
        {
            connect(webSocket, &QWebSocket::connected, this, &CreateRoom::createRoom);
        }
    }
    else
    {
        msgBox.setText("Could not open ROM");
        msgBox.exec();
    }
}

void CreateRoom::createRoom()
{
    connectionTimer->stop();
    QJsonObject json;
    QJsonObject room;
    room.insert("room_name", nameEdit->text());
    room.insert("password", passwordEdit->text());
    room.insert("MD5", QString(rom_settings.MD5));
    room.insert("game_name", QString(rom_settings.goodname));
    json.insert("type", "request_create_room");
    json.insert("player_name", playerNameEdit->text());
    json.insert("client_sha", QStringLiteral(GUI_VERSION));
    json.insert("netplay_version", NETPLAY_VER);

    QString gameName = getCheatGameName();
    QJsonObject gameData = loadCheatData(gameName);
    QJsonObject cheats = getCheatsFromSettings(gameName, gameData);
    if (!cheats.isEmpty())
    {
        QJsonDocument cheatsDoc(cheats);
        QJsonObject features;
        features.insert("cheats", QString(cheatsDoc.toJson(QJsonDocument::Compact)));
        room.insert("features", features);
    }

    json.insert("room", room);
    addAuthData(webSocket, &json);

    QJsonDocument json_doc(json);
    webSocket->sendTextMessage(json_doc.toJson());
}

void CreateRoom::processTextMessage(QString message)
{
    QMessageBox msgBox;
    msgBox.setTextFormat(Qt::RichText);
    msgBox.setTextInteractionFlags(Qt::TextBrowserInteraction);
    QJsonDocument json_doc = QJsonDocument::fromJson(message.toUtf8());
    QJsonObject json = json_doc.object();
    if (json.value("type").toString() == "reply_create_room")
    {
        if (json.value("accept").toInt() == 0)
        {
            json.remove("type");
            launched = 1;
            WaitRoom *waitRoom = new WaitRoom(filename, json, webSocket, parentWidget());
            waitRoom->show();
            accept();
        }
        else
        {
            (*CoreDoCommand)(M64CMD_ROM_CLOSE, 0, NULL);
            msgBox.setText(json.value("message").toString());
            msgBox.exec();
            createButton->setEnabled(true);
        }
    }
}

void CreateRoom::downloadFinished(QNetworkReply *reply)
{
    if (!reply->error())
    {
        QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll());
        QJsonObject json = json_doc.object();
        QStringList servers = json.keys();
        for (int i = 0; i < servers.size(); ++i)
            serverChooser->addItem(servers.at(i), json.value(servers.at(i)).toString());
        serverChooser->addItem(QString("Custom"), QString("Custom"));
    }

    reply->deleteLater();
}

void CreateRoom::handleServerChanged(int index)
{
    if (serverChooser->itemData(index) == "Custom")
    {
        bool ok;
        QString host = QInputDialog::getText(this, "Custom Netplay Server", "IP Address / Host:", QLineEdit::Normal, "", &ok);

        if (ok && !host.isEmpty())
        {
            customServerHost = host;
        }
    }

    if (webSocket)
    {
        webSocket->close();
        webSocket->deleteLater();
    }

    pingValue->setText("(Calculating)");

    webSocket = new QWebSocket;
    connect(webSocket, &QWebSocket::pong, this, &CreateRoom::updatePing);
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &CreateRoom::sendPing);
    connect(webSocket, &QWebSocket::disconnected, timer, &QTimer::stop);
    connect(webSocket, &QObject::destroyed, timer, &QTimer::stop);

    connect(webSocket, &QWebSocket::textMessageReceived, this, &CreateRoom::processTextMessage);

    timer->start(2500);
    QString serverAddress = serverChooser->itemData(index) == "Custom" ? customServerHost.prepend("ws://") : serverChooser->itemData(index).toString();
    QUrl serverUrl = QUrl(serverAddress);
    if (serverChooser->itemData(index) == "Custom" && serverUrl.port() < 0)
        // Be forgiving of custom server addresses that forget the port
        serverUrl.setPort(45000);

    webSocket->open(serverUrl);
}

void CreateRoom::connectionFailed()
{
    (*CoreDoCommand)(M64CMD_ROM_CLOSE, 0, NULL);
    QMessageBox msgBox;
    msgBox.setText("Could not connect to netplay server.");
    msgBox.exec();
    // Allow them to try again
    createButton->setEnabled(true);
    // Trigger input dialog for custom IP address, if using custom IP
    handleServerChanged(serverChooser->currentIndex());
}

void CreateRoom::updatePing(quint64 elapsedTime, const QByteArray &)
{
    pingValue->setText(QString::number(elapsedTime) + " ms");
}

void CreateRoom::sendPing()
{
    webSocket->ping();
}
