/*
 * 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 KEYTOTPMODEL_H
#define KEYTOTPMODEL_H

#include <memory>

#include <QAbstractListModel>
#include <QErrorMessage>
#include <QSize>

#include "connectionstate.h"
#include "nitrokeyprovider.h"
#include "newtotpslotprovider.h"
#include "totpuriparser.h"

class AuthenticateDialog;
class Authentication;
class NewTOTPSlotDialog;
class TOTPSlot;

/**
 * @brief A model class (in Qt's Model/View/Controller implementation) representing
 * data for the main view in the main window of an app.
 *
 * We're using Qt's Model/View/Controller framework for representing both, data and view
 * in this app. See: <a href=https://doc.qt.io/qt-5/model-view-programming.html>Qt's
 * documentation</a> for more details.
 */
class KeyTOTPModel : public QAbstractListModel
{
    Q_OBJECT

    enum class PostAdminAuthAction
    {
        ADD_SLOT,
        DELETE_SLOT
    };

    enum class PostUserAuthAction
    {
        GET_TOTP_CODE
    };

    /**
     * @brief Async interface for interacting with
     * Nitrokey.
     */
    std::shared_ptr<NitrokeyProvider> keyProvider;
    bool connected;

    ConnectionState connectionState;

    /**
     * @brief List of slots obtained using key provider.
     */
    QList<TOTPSlot> obtainedSlots;

    /// Currently selected slot.
    QModelIndex currentSelected;

    /// Previously selected slot.
    QModelIndex previousSelected;

    /**
     * @brief A TOTP code for the selected slot obtained using
     * key provider.
     */
    std::unique_ptr<GetTOTPCodeResponse> codeForSelectedSlot;

    QSize itemSize;

    std::unique_ptr<TotpUriParser> parserForNewSlot;

    PostAdminAuthAction postAdminAuthAction;
    PostUserAuthAction postUserAuthAction;
    QErrorMessage errorMessage;
    std::shared_ptr<AuthenticateDialog> adminAuthDialog;
    std::shared_ptr<Authentication> adminAuth;
    std::shared_ptr<AuthenticateDialog> userAuthDialog;
    std::shared_ptr<Authentication> userAuth;
    std::shared_ptr<NewTOTPSlotDialog> newTOTPSlotDialog;
    NewTOTPSlotProvider newTOTPSlotProvider;
    QString newTOTPSlotName;
    QTimer tickTimer;
    bool removalConfirmed;
    static constexpr unsigned int AUTHENTICATOR_PERIOD_SECONDS = 30;

private slots:
    void onGotSlots(const QList<TOTPSlot> &);
    void onAdminAuthenticated();
    void onAdminAuthenticationError();
    void onAdminAuthenticationCanceled();
    void onUserAuthenticated();
    void onUserAuthenticationError();
    void onUserAuthenticationCanceled();
    void onAddNewSlot();
    void onNewSlotWritten(const TOTPSlot);
    void onSelectedSlotErased(const TOTPSlot &erasedSlot);
    void onGotTOTPCode(const GetTOTPCodeResponse &totpCode);
    void on1SecTick();

public:
    /**
     * @brief Constructs instance of KeyTOTPModel.
     * @param keyProvider An instance of a interface class that is an async interface with the key.
     * @param adminAuthenticateDialog Modal dialog that asks user about Admin PIN.
     * @param adminAuthentication Authentication instance for Admin auth.
     * @param userAuthenticateDialog Modal dialog that asks user about regular user PIN.
     * @param userAuthentication Authentication instance for regular user auth.
     * @param parent A parent of the QObject instance (used internally by Qt).
     * @param newTOTPSlotDialog A modal dialog that's asking user for new Slot name.
     */
    explicit KeyTOTPModel(std::shared_ptr<NitrokeyProvider> keyProvider,
                          std::shared_ptr<AuthenticateDialog> adminAuthenticateDialog,
                          std::shared_ptr<Authentication> adminAuthentication,
                          std::shared_ptr<AuthenticateDialog> userAuthenticateDialog,
                          std::shared_ptr<Authentication> userAuthentication,
                          std::shared_ptr<NewTOTPSlotDialog> newTOTPSlotDialog,
                          QObject *parent = nullptr);

    /**
     * @brief Implementation of a
     * <a href=https://doc.qt.io/qt-5/qabstractitemmodel.html#data>data()</a>
     * method from Qt's
     * <a href=https://doc.qt.io/qt-5/qabstractitemmodel.html#data>data()</a>.
     * @param index Index of an item being requested from the model.
     * @param role <a href=https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum>Role</a>
     * of an item being requested.
     * @return An instance of <a href=https://doc.qt.io/qt-5/qicon.html>QIcon</a>
     * being wrapped inside <a href=https://doc.qt.io/qt-5/qvariant.html>QVariant</a>.
     */
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    /**
     * @brief rowCount Implementation of a
     * <a href=https://doc.qt.io/qt-5/qabstractitemmodel.html#rowCount>rowCount()</a>
     * method from Qt's
     * <a href=https://doc.qt.io/qt-5/qabstractitemmodel.html#data>data()</a>.
     * @param parent A parent index is not used in our case, since there's no hierarchical
     * structure of data. It's a simple list of items, so it doesn't have to be a valid
     * index (can be default-constructed: **QModelIndex()**).
     * @return Number of items in the model (number of TOTP slots used in the key).
     */
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    /**
     * @brief Changes the size of the items (instances of QIcon) being generated.
     * @param newItemSize A new size of items.
     */
    void setItemSize(const QSize &newItemSize) { itemSize = newItemSize; }

    /**
     * @brief Adds new TOTP slot to the key.
     * @param uriParser A parser with data from a corresponding QR-Code.
     */
    void addNewSlot(std::unique_ptr<TotpUriParser> &&uriParser);

    /// Returns TOTP code of the currently selected slot, if a slot is selected.
    /// Otherwise returns an empty string.
    QString currentlySelectedTOTPCode() const;

public slots:
    /**
     * @brief Sets currently selected (clicked in the View) slot.
     * @param current Index of currently selected slot.
     * @param previous Index of previously selected (or invalid, if nothing
     * was selected before) slot.
     *
     * This will kick-off a workflow for reading TOTP code for the current slot.
     */
    void onSlotSelected(const QModelIndex &current, const QModelIndex &previous);

    /**
     * @brief Erases selected TOTP slot.
     */
    void onEraseSelectedSlot();

signals:
    /**
     * @brief Emitted every time connection state to USB key changes.
     * @param connected True if connection state changed to connected.
     * False, if connection state changed to disconnected.
     */
    void connectionStateChanged(bool connected);

    /**
     * @brief Emitted whenever the model successfuly loads TOTP slots from the key.
     */
    void slotsLoaded();

    /**
     * @brief Emitted upon failed Admin authentication attempt.
     */
    void adminAuthenticationFailed();

    /**
     * @brief Emitted when user cancels authentication.
     */
    void adminAuthenticationCanceled();

    /**
     * @brief Emitted upon failed User authentication attempt.
     */
    void userAuthenticationFailed();

    /**
     * @brief Emitted when user cancels authentication.
     */
    void userAuthenticationCanceled();

    /**
     * @brief Emitted whenever a new TOTP code is available and
     * whenever a valid TOTP code expires.
     * @param present True if there is a valid code to be read.
     * False, otherwise.
     */
    void totpCodePresent(bool present) const;

private:
    bool selectedSlotStillValid() const;
    unsigned int selectedSlotSecondsRemaining() const;
    QIcon iconForSelected() const;
    QIcon iconForUnSelected(const QModelIndex &index) const;
    std::size_t slotNumberToObtainedSlotsIndex(const std::uint8_t slotNumber) const;
};

#endif // KEYTOTPMODEL_H
