/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
 */
#ifndef OATH_H
#define OATH_H

#include <QByteArray>
#include <QCryptographicHash>
#include <QDateTime>
#include <QSharedPointer>

#include <functional>
#include <optional>

namespace oath
{
    class Encoder
    {
    public:
        Encoder(uint tokenLength, bool addChecksum = false);
        virtual ~Encoder();
        virtual QString encode(quint32) const;
        uint tokenLength(void) const;
        bool checksum(void) const;
        static quint32 reduceMod10(quint32 value, uint tokenLength);
    private:
        Q_DISABLE_COPY_MOVE(Encoder)
    private:
        static constexpr const quint32 powerTable[10] = {
            1, 10, 100, 1'000, 10'000, 100'000, 1'000'000, 10'000'000, 100'000'000, 1'000'000'000
        };
        const uint m_tokenLength;
        const bool m_addChecksum;
    };

    class Algorithm
    {
    public:
        static bool validate(const Encoder *encoder);
        static bool validate(QCryptographicHash::Algorithm algorithm, const std::optional<uint> offset);
        static std::optional<Algorithm> create(QCryptographicHash::Algorithm algorithm, const std::optional<uint> offset, const QSharedPointer<const Encoder> &encoder, bool requireSaneKeyLength = false);
        static std::optional<Algorithm> totp(QCryptographicHash::Algorithm algorithm, uint tokenLength, bool requireSaneKeyLength = false);
        static std::optional<Algorithm> hotp(const std::optional<uint> offset, uint tokenLength, bool checksum, bool requireSaneKeyLength = false);
        std::optional<QString> compute(quint64 counter, char * secretBuffer, int length) const;
    private:
        Algorithm(const QSharedPointer<const Encoder> &encoder, const std::function<quint32(const QByteArray &)> &truncation, QCryptographicHash::Algorithm algorithm, bool requireSaneKeyLength);
    private:
        const QSharedPointer<const Encoder> m_encoder;
        const std::function<quint32(const QByteArray &)> m_truncation;
        bool m_enforceKeyLength;
        const QCryptographicHash::Algorithm m_algorithm;
    };

    uint luhnChecksum(quint32 value, uint digits);
    std::optional<quint64> count(const QDateTime &epoch, uint timeStep, const std::function<qint64(void)> &clock = &QDateTime::currentMSecsSinceEpoch);
    std::optional<QDateTime> fromCounter(quint64 count, const QDateTime &epoch, uint timeStep);
}

#endif
