/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2020, Agnieszka Cicha-Cisek
 * Copyright (c) 2020, Patryk Cisek
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef NITROKEYPROVIDER_H
#define NITROKEYPROVIDER_H

#include <memory>

#include <QMetaType>
#include <QList>
#include <QThreadPool>
#include <QTimer>

#include "gettotpcoderesponse.h"
#include "nitrokeybase.h"
#include "randompasswordgenerator.h"
#include "totpslot.h"


/**
 * @brief A Qt QObject async interface between Nitrokey and application.
 * @param nitrokey Reference to actual Nitrokey interface object.
 *
 * NitrokeyBase is an interface for communicating with the key
 * synchronously. Some operations might take considerable time,
 * so invoking them synchronously would freeze GUI for the time of
 * execution. This class provides a proxy Qt-aware interface, that
 * will run operations in a separate thread and communicate results
 * using Qt's Signals & Slots mechanism.
 */
class NitrokeyProvider : public QObject
{
    Q_OBJECT
public:

    /**
     * @brief Constructs an instance of provider.
     * @param nitrokey An interface to the key.
     */
    explicit NitrokeyProvider(std::shared_ptr<NitrokeyBase> nitrokey);

    /**
     * @brief Triggers asynchronuous attempt to connect to the key.
     *
     * This function only triggers async attempt to connect to the key,
     * but exits immediatelly. So won't block main GUI thread. In
     * the background, using QtConcurrent, it'll keep trying to connect
     * to the key. After connection is established, connected() signal
     * will be emitted.
     */
    void connect();

    /**
     * @brief A syncronuous function returning true, we're currently connected
     * to the USB key. Since this method is not an async call, no signal is emitted
     * as a result of executing it.
     *
     * @return True, if connected to the key. False, otherwise.
     */
    bool syncIsConnected();

    /**
     * @brief Triggers asynchronuous attempt to read TOTP slots from the key.
     *
     * This function only triggers async operation, but exits immediatelly.
     * After slots have been read in a separate thread, a signal:
     * gotSlots(QList<TOTPSlot>) will be emitted.
     */
    void getSlots();

    /**
     * @brief Triggers asynchronuous attemp to authenticate admin.
     * @param adminPin Admin PIN (factory default is: *12345678*).
     * @param tempPassword temporary password for the current connection
     * session.
     */
    void adminAuth(const QString &adminPin,
                   const RandomPasswordGenerator::KeyPassword &tempPassword);

    /**
     * @brief Triggers asynchronuous attemp to authenticate regular user.
     * @param userPin User PIN (factory default is: *123456*).
     * @param tempPassword temporary password for the current connection
     * session.
     */
    void userAuth(const QString &userPin,
                  const RandomPasswordGenerator::KeyPassword &tempPassword);

    /**
     * @brief Writes new TOTP slot to the USB key.
     * @param slot TOTP slot on the USB key (basically its name and index in
     * the key).
     * @param timeWindow Time window in seconds for TOTP codes to expire.
     * Most probably it's gonna be 30 seconds to stay consistant and compatible
     * with GoogleAuthenticator.
     * @param hexSecret TOTP secret HEX-encoded.
     * @param temporaryPassword Temporary session password obtained using
     * Authentication class for Admin auth using Admin PIN.
     */
    void writeTotpSlot(const TOTPSlot &slot,
                       std::uint16_t timeWindow,
                       const QString &hexSecret,
                       const RandomPasswordGenerator::KeyPassword temporaryPassword);

    /**
     * @brief Erases existing TOTP slot on the USB key.
     * @param slot Slot to be erased.
     * @param temporaryPassword Temporary password for the Admin session.
     */
    void eraseTotpSlot(const TOTPSlot &slot,
                       const RandomPasswordGenerator::KeyPassword temporaryPassword);

    /**
     * @brief Requests Nitrokey to generate TOTP code for the given slot.
     * @param slot Slot for which the code is to be read.
     * @param unixTimestamp A UNIX timestamp (seconds since Jan 1st, 1970)
     * for which the code is to be generated
     * @param temporaryPassword A temporary User or Admin (both would work)
     * pasword.
     *
     * This method will request Nitrokey to generate TOTP code for the
     * given slot. One provides a timestamp -- this is used here instead of
     * getting current timestamp to allow user to generate TOTP codes ahead of
     * time and e.g. caching them.
     */
    void getTOTPCode(const TOTPSlot &slot,
                     const unsigned int unixTimestamp,
                     const RandomPasswordGenerator::KeyPassword &temporaryPassword);

signals:
    /**
     * @brief Emitted after successfull connection to the key has been
     * established.
     */
    void connected() const;

    /**
     * @brief Emitted whenever we get disconnected from the key.
     */
    void disconnected() const;

    /**
     * @brief Emitted when async operation of getting slots from the key
     * completes.
     * @param readSlots Slots that got read from the USB key.
     */
    void gotSlots(QList<TOTPSlot>readSlots) const;

    /**
     * @brief Emitted upon successfull authentication of an admin.
     */
    void adminAuthenticated() const;

    /**
     * @brief Emitted upon unsuccessfull Admin authentication.
     */
    void adminAuthenticationFailure() const;

    /**
     * @brief Emitted upon successfull authentication of a regular user.
     */
    void userAuthenticated() const;

    /**
     * @brief Emitted upon unsuccessfull regular user authentication.
     */
    void userAuthenticationFailure() const;

    /**
     * @brief Emitted upon successfull TOTP slot write.
     * @param slot TOTP slot, that got written.
     */
    void totpSlotWritten(const TOTPSlot slot) const;

    /**
     * @brief Emitted upon successfull erasure of the TOTP slot.
     * @param slot Slot that got erased.
     */
    void totpSlotErased(const TOTPSlot slot) const;

    /**
     * @brief Emitted whenever Nitrokey finishes generating TOTP code.
     * @param totpCodeResponse Response with TOTP code and a TOPT slot
     * number on the USB key, for which the code's been generated.
     */
    void gotTOTPCode(const GetTOTPCodeResponse &totpCodeResponse);

private:
    void asyncConnect();
    void asyncGetSlots();
    void asyncAdminAuth(const QString &adminPin,
                        const RandomPasswordGenerator::KeyPassword &tempPassword);
    void asyncUserAuth(const QString &userPin,
                       const RandomPasswordGenerator::KeyPassword &tempPassword);
    void asyncWriteTotpSlot(const TOTPSlot &slot,
                            std::uint16_t timeWindow,
                            const QString &hexSecret,
                            const RandomPasswordGenerator::KeyPassword temporaryPassword);
    void asyncEraseTotpSlot(const TOTPSlot &slot,
                            const RandomPasswordGenerator::KeyPassword temporaryPassword);
    void asyncGetTOTPCode(const TOTPSlot &slot,
                          const unsigned int unixTimestamp,
                          const RandomPasswordGenerator::KeyPassword &temporaryPassword);

    std::string keyPassToString(const RandomPasswordGenerator::KeyPassword &password) const;

    std::shared_ptr<NitrokeyBase> nitrokey;
    QThreadPool threadPool;
};

Q_DECLARE_METATYPE(GetTOTPCodeResponse);

#endif // NITROKEYPROVIDER_H
