/*
    This file is part of the Okteta Kasten module, made within the KDE community.

    SPDX-FileCopyrightText: 2009, 2022 Friedrich W. H. Kossebau <kossebau@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

// QCA
// need to have this first, as QCA needs QT_NO_CAST_FROM_ASCII disabled when included
#include <config-qca2.hpp> // krazy:excludeall=includes
#if HAVE_QCA2
// disable QT_NO_CAST_FROM_ASCII
#ifdef QT_NO_CAST_FROM_ASCII
#undef QT_NO_CAST_FROM_ASCII
#endif
#include <QtCrypto>
#endif

#include "checksumtool.hpp"

// lib
#include "checksumcalculatejob.hpp"
#include "checksumlogging.hpp"
//
#include <bytearraychecksumalgorithmfactory.hpp>
#include <abstractbytearraychecksumalgorithm.hpp>
// Okteta Kasten gui
#include <Kasten/Okteta/ByteArrayView>
// Okteta Kasten core
#include <Kasten/Okteta/ByteArrayDocument>
// Okteta core
#include <Okteta/AbstractByteArrayModel>
#include <Okteta/ArrayChangeMetricsList>
// KF
#include <KConfigGroup>
#include <KSharedConfig>
#include <KLocalizedString>
// Qt
#include <QApplication>


namespace Kasten {

// C++11 needs a definition for static constexpr members
constexpr char ChecksumTool::ConfigGroupId[];
constexpr char ChecksumTool::AlgorithmConfigKey[];


ChecksumTool::ChecksumTool()
    : mChecksumUptodate(false)
    , mSourceByteArrayModelUptodate(false)
{
    setObjectName(QStringLiteral("Checksum"));

// TODO: find a better place to do and store the initialization
#if HAVE_QCA2
    mQcaInitializer = new QCA::Initializer(QCA::Practical, 64);
    qCDebug(LOG_OKTETA_KASTEN_CONTROLLER_CHECKSUM) << QCA::supportedFeatures();// Hash::supportedTypes();
#endif

    mAlgorithmList = ByteArrayChecksumAlgorithmFactory::createAlgorithms();

    const KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId);
    for (auto *algorithm : qAsConst(mAlgorithmList)) {
        algorithm->loadConfig(configGroup);
    }

    mAlgorithmId = 0;
    const QString algorithmId = configGroup.readEntry(AlgorithmConfigKey);
    if (!algorithmId.isEmpty()) {
        for (int i = 0; i < mAlgorithmList.size(); ++i) {
            if (mAlgorithmList[i]->id() == algorithmId) {
                mAlgorithmId = i;
                break;
            }
        }
    }
}

ChecksumTool::~ChecksumTool()
{
    qDeleteAll(mAlgorithmList);
#if HAVE_QCA2
    delete mQcaInitializer;
#endif
}

QVector<AbstractByteArrayChecksumAlgorithm*> ChecksumTool::algorithmList() const { return mAlgorithmList; }

bool ChecksumTool::isApplyable() const
{
    return (mByteArrayModel && mByteArrayView && mByteArrayView->hasSelectedData());
}

QString ChecksumTool::title() const { return i18nc("@title:window of the tool to calculate checksums", "Checksum"); }

AbstractByteArrayChecksumParameterSet* ChecksumTool::parameterSet()
{
    AbstractByteArrayChecksumAlgorithm* algorithm = mAlgorithmList.at(mAlgorithmId);

    return algorithm ? algorithm->parameterSet() : nullptr;
}

void ChecksumTool::setTargetModel(AbstractModel* model)
{
    ByteArrayView* const byteArrayView = model ? model->findBaseModel<ByteArrayView*>() : nullptr;
    if (byteArrayView == mByteArrayView) {
        return;
    }

    if (mByteArrayView) {
        mByteArrayView->disconnect(this);
    }

    mByteArrayView = byteArrayView;

    ByteArrayDocument* document =
        mByteArrayView ? qobject_cast<ByteArrayDocument*>(mByteArrayView->baseModel()) : nullptr;
    mByteArrayModel = document ? document->content() : nullptr;

    if (mByteArrayView && mByteArrayModel) {
        connect(mByteArrayView,  &ByteArrayView::selectedDataChanged,
                this, &ChecksumTool::onSelectionChanged);
    }

    // TODO: if there is no view, there is nothing calculate a checksum from
    // or this could be the view where we did the checksum from and it did not change
    checkUptoDate();
    emit uptodateChanged(mChecksumUptodate);
    emit isApplyableChanged(isApplyable());
}

void ChecksumTool::checkUptoDate()
{
    mChecksumUptodate =
        (mSourceByteArrayModel == mByteArrayModel
         && mByteArrayView && mSourceSelection == mByteArrayView->selection()
         && mSourceAlgorithmId == mAlgorithmId
         && mSourceByteArrayModelUptodate);
}

void ChecksumTool::calculateChecksum()
{
    AbstractByteArrayChecksumAlgorithm* algorithm = mAlgorithmList.at(mAlgorithmId);

    if (algorithm) {
        // forget old string source
        if (mSourceByteArrayModel) {
            mSourceByteArrayModel->disconnect(this);
        }

        QApplication::setOverrideCursor(Qt::WaitCursor);

        ChecksumCalculateJob* checksumCalculateJob =
            new ChecksumCalculateJob(&mCheckSum, algorithm, mByteArrayModel, mByteArrayView->selection());
        checksumCalculateJob->exec();

        QApplication::restoreOverrideCursor();

        // remember checksum source
        mSourceAlgorithmId = mAlgorithmId;
        mSourceByteArrayModel = mByteArrayModel;
        mSourceSelection = mByteArrayView->selection();
        connect(mSourceByteArrayModel,  &Okteta::AbstractByteArrayModel::contentsChanged,
                this, &ChecksumTool::onSourceChanged);
        connect(mSourceByteArrayModel,  &Okteta::AbstractByteArrayModel::destroyed,
                this, &ChecksumTool::onSourceDestroyed);

        mChecksumUptodate = true;
        mSourceByteArrayModelUptodate = true;
        emit checksumChanged(mCheckSum);
        emit uptodateChanged(true);
    }
}

void ChecksumTool::setAlgorithm(int algorithmId)
{
    if (mAlgorithmId == algorithmId) {
        return;
    }

    mAlgorithmId = algorithmId;
    checkUptoDate();

    AbstractByteArrayChecksumAlgorithm* algorithm = mAlgorithmList.at(mAlgorithmId);
    if (algorithm) {
        KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId);
        configGroup.writeEntry(AlgorithmConfigKey, algorithm->id());
    }

    emit algorithmChanged(mAlgorithmId);
    emit uptodateChanged(mChecksumUptodate);
    emit isApplyableChanged(isApplyable());
}

// TODO: hack!
// better would be to store the parameter set used for the source and compare if equal
// this hack does the same, except for that the source will never be up-to-date
void ChecksumTool::resetSourceTool()
{
    mSourceAlgorithmId = -1;

    AbstractByteArrayChecksumAlgorithm* algorithm = mAlgorithmList.at(mAlgorithmId);
    if (algorithm) {
        KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId);
        algorithm->saveConfig(configGroup);
    }

    checkUptoDate();
    emit uptodateChanged(mChecksumUptodate);
    emit isApplyableChanged(isApplyable());
}

void ChecksumTool::onSelectionChanged()
{
// TODO: could be quicker using the selection data
    checkUptoDate();
    emit uptodateChanged(mChecksumUptodate);
    emit isApplyableChanged(isApplyable());
}

void ChecksumTool::onSourceChanged()
{
    mChecksumUptodate = false;
    mSourceByteArrayModelUptodate = false;
    emit uptodateChanged(false);
}

void ChecksumTool::onSourceDestroyed()
{
    mSourceByteArrayModel = nullptr;
    onSourceChanged();
}

}

#include "moc_checksumtool.cpp"
