/* Copyright 2013-2021 MultiMC Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <QObject>
#include <QString>
#include <QList>
#include <QJsonObject>
#include <QPair>
#include <QMap>

#include <memory>
#include "AuthSession.h"
#include "Usable.h"

#include "multimc_logic_export.h"

class Task;
class YggdrasilTask;
class MojangAccount;

typedef std::shared_ptr<MojangAccount> MojangAccountPtr;
Q_DECLARE_METATYPE(MojangAccountPtr)

/**
 * A profile within someone's Mojang account.
 *
 * Currently, the profile system has not been implemented by Mojang yet,
 * but we might as well add some things for it in MultiMC right now so
 * we don't have to rip the code to pieces to add it later.
 */
struct AccountProfile
{
    QString id;
    QString name;
    bool legacy;
};

enum AccountStatus
{
    NotVerified,
    Verified
};

/**
 * Object that stores information about a certain Mojang account.
 *
 * Said information may include things such as that account's username, client token, and access
 * token if the user chose to stay logged in.
 */
class MULTIMC_LOGIC_EXPORT MojangAccount :
    public QObject,
    public Usable,
    public std::enable_shared_from_this<MojangAccount>
{
    Q_OBJECT
public: /* construction */
    //! Do not copy accounts. ever.
    explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;

    //! Default constructor
    explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};

    //! Creates an empty account for the specified user name.
    static MojangAccountPtr createFromUsername(const QString &username);

    //! Loads a MojangAccount from the given JSON object.
    static MojangAccountPtr loadFromJson(const QJsonObject &json);

    //! Saves a MojangAccount to a JSON object and returns it.
    QJsonObject saveToJson() const;

public: /* manipulation */
        /**
     * Sets the currently selected profile to the profile with the given ID string.
     * If profileId is not in the list of available profiles, the function will simply return
     * false.
     */
    bool setCurrentProfile(const QString &profileId);

    /**
     * Attempt to login. Empty password means we use the token.
     * If the attempt fails because we already are performing some task, it returns false.
     */
    std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString());
    void invalidateClientToken();

public: /* queries */
    const QString &username() const
    {
        return m_username;
    }

    const QString &clientToken() const
    {
        return m_clientToken;
    }

    const QString &accessToken() const
    {
        return m_accessToken;
    }

    const QList<AccountProfile> &profiles() const
    {
        return m_profiles;
    }

    const User &user()
    {
        return m_user;
    }

    //! Returns the currently selected profile (if none, returns nullptr)
    const AccountProfile *currentProfile() const;

    //! Returns whether the account is NotVerified, Verified or Online
    AccountStatus accountStatus() const;

signals:
    /**
     * This signal is emitted when the account changes
     */
    void changed();

    // TODO: better signalling for the various possible state changes - especially errors

protected: /* variables */
    QString m_username;

    // Used to identify the client - the user can have multiple clients for the same account
    // Think: different launchers, all connecting to the same account/profile
    QString m_clientToken;

    // Blank if not logged in.
    QString m_accessToken;

    // Index of the selected profile within the list of available
    // profiles. -1 if nothing is selected.
    int m_currentProfile = -1;

    // List of available profiles.
    QList<AccountProfile> m_profiles;

    // the user structure, whatever it is.
    User m_user;

    // current task we are executing here
    std::shared_ptr<YggdrasilTask> m_currentTask;

protected: /* methods */

    void incrementUses() override;
    void decrementUses() override;

private
slots:
    void authSucceeded();
    void authFailed(QString reason);

private:
    void fillSession(AuthSessionPtr session);

public:
    friend class YggdrasilTask;
    friend class AuthenticateTask;
    friend class ValidateTask;
    friend class RefreshTask;
};
