// SPDX-FileCopyrightText: 2024 Manuel Alcaraz Zambrano <manuel@alcarazzam.dev>
// SPDX-License-Identifier: GPL-3.0-or-later

#include "controller.h"
#include "accountmanager.h"
#include "standing.h"
#include "tournament/pairing.h"
#include "tournament/state.h"
#include "tournament/sync/syncengine.h"
#include "utils.h"

#include <KSandbox>
#include <QCoroFuture>
#include <QDesktopServices>
#include <QRandomGenerator>
#include <QtConcurrentRun>

using namespace Qt::Literals::StringLiterals;

Controller::Controller(QObject *parent)
    : QObject(parent)
    , m_playersModel(new PlayersModel(this))
    , m_pairingModel(new PairingModel(this))
    , m_standingsModel(new StandingsModel(this))
    , m_accountManager(std::make_unique<AccountManager>())
{
    connect(m_playersModel, &PlayersModel::playerChanged, this, [this](Player *player, PlayersModel::Columns field) {
        Q_UNUSED(field);
        m_tournament->savePlayer(player);
    });

    connect(m_pairingModel, &PairingModel::pairingChanged, this, [this]() {
        setAreStandingsValid(false);
        Q_EMIT hasCurrentRoundFinishedChanged();
    });

    connect(m_accountManager.get(), &AccountManager::openUrl, this, [](const QUrl &url) {
        QDesktopServices::openUrl(url);
    });
}

Event *Controller::getEvent() const
{
    return m_event.get();
}

void Controller::setEvent(std::unique_ptr<Event> event)
{
    if (m_event == event) {
        return;
    }
    m_event = std::move(event);
    Q_EMIT eventChanged();
}

Tournament *Controller::tournament() const
{
    return m_tournament;
}

void Controller::setTournament(Tournament *tournament)
{
    if (m_tournament == tournament) {
        return;
    }
    m_tournament = tournament;

    m_playersModel->setTournament(m_tournament);
    m_playersModel->setPlayers(m_tournament->players());
    m_pairingModel->setTournament(m_tournament);
    m_pairingModel->setPairings(m_tournament->pairings(1));
    m_standingsModel->setTournament(m_tournament);

    setHasOpenTournament(true);
    setCurrentRound(1);
    setAreStandingsValid(false);

    Q_EMIT tournamentChanged();
}

bool Controller::hasOpenTournament() const
{
    return m_hasOpenTournament;
}

void Controller::setHasOpenTournament(bool hasOpenTournament)
{
    if (m_hasOpenTournament == hasOpenTournament) {
        return;
    }
    m_hasOpenTournament = hasOpenTournament;
    Q_EMIT hasOpenTournamentChanged();
}

Player *Controller::currentPlayer() const
{
    return m_currentPlayer;
}

void Controller::setCurrentPlayer(Player *currentPlayer)
{
    if (m_currentPlayer == currentPlayer) {
        return;
    }
    m_currentPlayer = currentPlayer;
    Q_EMIT currentPlayerChanged();
}

int Controller::currentRound() const
{
    return m_currentRound;
}

bool Controller::hasCurrentRoundFinished()
{
    return m_tournament->currentRound() == 0 || m_tournament->isRoundFinished(m_tournament->currentRound());
}

void Controller::setCurrentRound(int currentRound)
{
    Q_ASSERT(currentRound >= 1);

    m_currentRound = currentRound;
    m_pairingModel->setPairings(m_tournament->pairings(currentRound));
    Q_EMIT currentRoundChanged();
}

bool Controller::areStandingsValid() const
{
    return m_areStandingsValid;
}

QString Controller::resultToString(Pairing::PartialResult whiteResult, Pairing::PartialResult blackResult)
{
    return Pairing::resultToString(whiteResult, blackResult);
}

void Controller::setAreStandingsValid(bool valid)
{
    if (m_areStandingsValid == valid) {
        return;
    }
    m_areStandingsValid = valid;
    Q_EMIT areStandingsValidChanged();
}

void Controller::importTrf(const QUrl &fileUrl)
{
    auto event = std::make_unique<Event>();

    if (auto ok = event->create(); !ok) {
        setError(ok.error());
        return;
    }

    auto tournament = event->importTournament(fileUrl.toLocalFile());

    if (!tournament) {
        setError(tournament.error());
        return;
    }

    setEvent(std::move(event));
    setTournament(*tournament);
    setCurrentView(Controller::View::Players);
}

void Controller::exportTrf(const QUrl &fileUrl)
{
    if (!m_tournament->exportTrf(fileUrl.toLocalFile())) {
        setError(i18n("Couldn't export tournament."));
    }
}

QCoro::Task<void> Controller::pairRound(bool sort, uint color)
{
    if (m_tournament->currentRound() == 0 && sort) {
        sortPlayers();
    }

    if (m_tournament->currentRound() == 0) {
        Tournament::InitialColor initialColor;
        if (color == 2) {
            initialColor = Tournament::InitialColor(QRandomGenerator::global()->bounded(2));
        } else {
            initialColor = Tournament::InitialColor(color);
        }
        m_tournament->setInitialColor(initialColor);
    }
    const auto pairings = co_await m_tournament->pairNextRound();

    if (!pairings.has_value()) {
        setError(pairings.error());
        co_return;
    }

    setCurrentRound(m_tournament->currentRound());
    m_pairingModel->setPairings(m_tournament->pairings(m_tournament->currentRound()));

    setAreStandingsValid(false);
    Q_EMIT hasCurrentRoundFinishedChanged();
}

void Controller::removePairings(bool keepByes)
{
    if (const auto ok = m_tournament->removePairings(m_currentRound, keepByes); !ok) {
        setError(ok.error());
        return;
    }

    setCurrentRound(std::max(1, m_tournament->currentRound()));
    setAreStandingsValid(false);
}

void Controller::reloadPairings(int round)
{
    Q_ASSERT(round >= 1);

    if (m_currentRound == round) {
        m_pairingModel->setPairings(m_tournament->pairings(m_currentRound));
    }
}

QCoro::QmlTask Controller::reloadStandings(int maxRound)
{
    qDebug() << "reloading standings";
    return updateStandings(maxRound);
}

QCoro::Task<> Controller::updateStandings(int maxRound)
{
    const auto standings = co_await QtConcurrent::run([this, maxRound]() -> QList<Standing> {
        const State state = m_tournament->state(maxRound);
        return m_tournament->standings(state);
    });

    m_standingsModel->setStandings(standings);

    setAreStandingsValid(true);
}

void Controller::sortPlayers()
{
    m_tournament->sortPlayers();
    m_playersModel->setPlayers(m_tournament->players());
}

void Controller::newTournament(const QUrl &fileUrl, const QString &name, int numberOfRounds)
{
    auto fileName = Utils::maybeAddExtension(fileUrl, u".chessament"_s);
    auto event = std::make_unique<Event>();

    if (const auto ok = event->create(fileName.toLocalFile()); !ok) {
        setError(ok.error());
        return;
    }

    auto tournament = event->createTournament();
    if (!tournament) {
        setError(tournament.error());
        return;
    }

    (*tournament)->setName(name);
    (*tournament)->setNumberOfRounds(numberOfRounds);

    setEvent(std::move(event));
    setTournament(*tournament);
    setCurrentView(Controller::View::Players);
}

void Controller::openEvent(const QUrl &fileUrl)
{
    auto event = std::make_unique<Event>();

    if (const auto ok = event->open(fileUrl.toLocalFile()); !ok) {
        setError(ok.error());
        return;
    }

    setEvent(std::move(event));
    setTournament(m_event->tournament(0));
    setCurrentView(Controller::View::Players);
}

void Controller::saveEventAs(const QUrl &fileUrl)
{
    auto fileName = Utils::maybeAddExtension(fileUrl, u".chessament"_s);

    if (const auto ok = m_event->saveAs(fileName.toLocalFile()); !ok) {
        setError(ok.error());
        return;
    }

    openEvent(fileName);
}

PlayersModel *Controller::playersModel() const
{
    return m_playersModel;
}

PairingModel *Controller::pairingModel() const
{
    return m_pairingModel;
}

StandingsModel *Controller::standingsModel() const
{
    return m_standingsModel;
}

AccountManager *Controller::accountManager() const
{
    return m_accountManager.get();
}

void Controller::uploadTournament()
{
    auto account = m_accountManager->first();

    Q_ASSERT(account);

    auto engine = new SyncEngine{account, m_tournament};
    engine->start();
}

Controller::View Controller::currentView() const
{
    return m_currentView;
}

void Controller::setCurrentView(Controller::View currentView)
{
    if (m_currentView == currentView) {
        return;
    }
    m_currentView = currentView;
    Q_EMIT currentViewChanged();
}

QString Controller::error() const
{
    return m_error;
}

void Controller::setError(const QString &error)
{
    m_error = error;
    Q_EMIT errorChanged();
}

#include "moc_controller.cpp"
