/*
 * Copyright (C) 2018-2021 QuasarApp.
 * Distributed under the lgplv3 software license, see the accompanying
 * Everyone is permitted to copy and distribute verbatim copies
 * of this license document, but changing it is not allowed.
*/

#include "hanoitowers.h"
#include <cmath>
#include <QDataStream>
#include <QDir>
#include <qmlnotifyservice.h>
#include "gamestate.h"
#include "hanoiimageprovider.h"
#include <QQmlApplicationEngine>
#include <lvmainmodel.h>
#include <recordlistmodel.h>
#include <QQmlContext>
#include <QBuffer>
#include <QCoreApplication>
#include <worldupdate.h>
#include "dataconverter.h"
#include "localuser.h"
#include "recordsproxymodel.h"

#define DEFAULT_USER_ID "DefaultUser"
#define DEFAULT_USER_NAME "User"

#define FIRST_RUN_KEY "isFirstStart"
#define ANIMATION_KEY "animation"
#define RANDOM_COLOR_KEY "randomColor"
#define CURRENT_PROFILE_KEY "currentProfile"
#define FOG "fog"
#define FOG_ANIMATION "fogAnimation"
#define SHOW_CREDITS "showCredits"
#define LAUNCH_COUNT "launchCount"

HanoiTowers::HanoiTowers(QQmlApplicationEngine *engine):
    QObject(),
    _profile()
{
    _settings = QuasarAppUtils::Settings::instance();

    _client = new HanoiClient();
    _loginModel = new LoginView::LVMainModel("userLogin", this);
    _createNewOfflineUser = new LoginView::LVMainModel("createUser", this);

    _recordsTable   = new RecordListModel(this);
    _recordsTableProxy = new RecordsProxyModel(this);
    _world          = new RecordListModel(this);
    _worldProxy     = new RecordsProxyModel(this);

    _worldProxy->setSourceModel(_world);
    _worldProxy->setDynamicSortFilter(true);
    _worldProxy->setSortRole(RecordListModel::RecordListModelRoles::Record);
    _worldProxy->sort(0, Qt::SortOrder::DescendingOrder);

    _recordsTableProxy->setSourceModel(_recordsTable);
    _recordsTableProxy->setDynamicSortFilter(true);
    _recordsTableProxy->setSortRole(RecordListModel::RecordListModelRoles::Record);
    _recordsTableProxy->sort(0, Qt::SortOrder::DescendingOrder);

    _imageProvider = new HanoiImageProvider(_client->getUsersCache());
    _dataConverter = new DataConverter;

    _loginModel->setComponents(LoginView::Nickname |
                               LoginView::Title |
                               LoginView::Password |
                               LoginView::SigupPage |
                               LoginView::LoginPage |
                               LoginView::TermOfUse);

    _loginModel->setCurrentPage(LoginView::ViewComponents::LoginPage);
    _loginModel->init(engine);

    _createNewOfflineUser->setComponents(LoginView::Nickname |
                                         LoginView::SigupPage |
                                         LoginView::Title);
    _createNewOfflineUser->setAcceptButtonText(tr("Create"));
    _createNewOfflineUser->init(engine);

    _recordsTable->setSource(_client->localUsersPreview());

    engine->addImageProvider("HanoiImages", _imageProvider);


    connect(_loginModel, &LoginView::LVMainModel::sigLoginRequest,
            this, &HanoiTowers::handleOnlineRequest);

    connect(_loginModel, &LoginView::LVMainModel::sigRegisterRequest,
            this, &HanoiTowers::handleOnlineRegisterRequest);

    connect(_createNewOfflineUser , &LoginView::LVMainModel::sigRegisterRequest,
            this, &HanoiTowers::handleCreateNewProfile);

    connect(_client, &HanoiClient::requestError,
            this, &HanoiTowers::handleOnlineRequestError);

    connect(_client, &HanoiClient::userDataChanged,
            this, &HanoiTowers::handleAcceptUserData);

    connect(_client, &HanoiClient::statusChanged,
            this, &HanoiTowers::setOnlineStatus);

    connect(_client, &HanoiClient::worldChanged,
            this, &HanoiTowers::handleWorldChanged);

    connect(_client, &HanoiClient::worldInited,
            this, &HanoiTowers::handleWorldInited);

    connect(_client, &HanoiClient::sigBestuserIdChanged,
            this, &HanoiTowers::handleBestUserIdChanged);

    connect(&_profile, &LocalUser::nameChanged,
            this, &HanoiTowers::handleChangeName);

    setProfile(_settings->getStrValue(CURRENT_PROFILE_KEY, DEFAULT_USER_ID));
    init();
}

void HanoiTowers::init() {
    _settingsData.animation = _settings->getValue(ANIMATION_KEY, true).toBool();
    _settingsData.randomColor = _settings->getValue(RANDOM_COLOR_KEY, false).toBool();
    _settingsData.fog = _settings->getValue(FOG, true).toBool();
    _settingsData.fogAnimation = _settings->getValue(FOG_ANIMATION, true).toBool();
    _settingsData.showCredits = _settings->getValue(SHOW_CREDITS, true).toBool();
    _settingsData._launchCount = _settings->getValue(LAUNCH_COUNT, 0).toUInt();

    setLaunchCount(_settingsData._launchCount + 1);

}

void HanoiTowers::loadOldSaves() {
    QFile f(MAIN_SETINGS_FILE);
    if(f.open(QIODevice::ReadOnly)){
        QDataStream stream(&f);

        unsigned short lvl;
        bool isFirstStart, _animation, _randomColor;
        stream >> lvl;
        stream >> isFirstStart;
        stream >> _animation;
        stream >> _randomColor;

        setAnimation(_animation);
        setRandomColor(_randomColor);
        setShowHelp(isFirstStart);
        if (lvl < 99)
            gameState()->saveLvl(lvl);


        f.close();
        QFile::remove(MAIN_SETINGS_FILE);

        updateProfile();
    }
}

void HanoiTowers::onlineRequest(const QString &userId) {

    if (_client->isLogined()) {
        return;
    }

    if (_profile.token().isValid() && _client->login(userId)) {
        return;
    }

    LoginView::UserData data;
    data.setNickname(userId);
    _loginModel->setData(data);

    if (!_client->isConnected() && !_client->connectToServer()) {

        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Connect error"),
                    tr("Failed to connect to server"
                       " please check network connection befor login"), "",
                    QmlNotificationService::NotificationData::Error);

        return;
    }

    emit showOnlinePage();
}

void HanoiTowers::updateProfile() {
    if (!_profile.isValid())
        return;

    if (!_client->updateProfile(_profile)) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Update Profile error"),
                    tr("Failed to update yuo user data"
                       " please check network connection befor update prifile"), "",
                    QmlNotificationService::NotificationData::Error);
    } else {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Update Profile"),
                    tr("User data updated successful"), "",
                    QmlNotificationService::NotificationData::Normal);
    }
}

int HanoiTowers::onlineStatus() const {
    return static_cast<int>(_onlineStatus);
}

QString HanoiTowers::onlineStatusColor() const {
    switch (_onlineStatus) {
    case OnlineStatus::Connected:
    case OnlineStatus::Connecting: {
        return "#ffaa7f";
    }
    case OnlineStatus::Loginning:
    case OnlineStatus::Logined: {
        return "#00ff7f";
    }
    default: return "#787878";
    }
}

QObject *HanoiTowers::bestUser() {
    return &_bestUser;
}

QObject *HanoiTowers::selectedUser() {
    return &_selectedUser;
}

bool HanoiTowers::isAndroid() const{
#ifdef Q_OS_ANDROID
    return true;
#else
    return false;
#endif
}

bool HanoiTowers::isAdMod() const {
#ifdef HANOI_ADMOD
    return true;
#else
    return false;
#endif
}

void HanoiTowers::handleChangeName(const QString &) {
    emit profileChanged();
}

void HanoiTowers::handleBestUserIdChanged(const QString & userId) {
    _bestUser.setId(userId);
}

void HanoiTowers::handleCreateNewProfile(const LoginView::UserData & data) {
    createProfile(data.nickname(), data.nickname());
}

void HanoiTowers::handleOnlineRequest(const LoginView::UserData & user) {

    if (!_client->login(user.nickname(), user.rawPassword())) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("login error"),
                    tr("Failed to login into online account,"
                       " please check your password and username"), "",
                    QmlNotificationService::NotificationData::Error);

    }
}

void HanoiTowers::handleOnlineRegisterRequest(const LoginView::UserData &user) {
    if (!_client->signup(user.nickname(), user.rawPassword())) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Register online error"),
                    tr("Failed to register this account,"
                       " if this account was created by you, try to restore it."), "",
                    QmlNotificationService::NotificationData::Error);
    }
}

void HanoiTowers::handleOnlineRequestError(QH::ErrorCodes::Code code, const QString & err) {

    auto errMessage = [](const QString &err) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Server error"),
                    err, "",
                    QmlNotificationService::NotificationData::Error);
    };

    if (code == QH::ErrorCodes::UserNotExits) {
        _loginModel->setCurrentPage(LoginView::ViewComponents::SigupPage);
        errMessage(tr("User with this id is not registered. If it you then use please the sigup form."));
        emit showOnlinePage();

        return;
    }

    if (code == QH::ErrorCodes::UserExits) {
        _loginModel->setCurrentPage(LoginView::ViewComponents::LoginPage);
        errMessage(tr("User with this id already registered. If it you then use please the login form."));
        emit showOnlinePage();

        return;
    }

    QmlNotificationService::NotificationService::getService()->setNotify(
                tr("Server error"),
                err, "",
                QmlNotificationService::NotificationData::Error);
}

void HanoiTowers::handleAcceptUserData(QSharedPointer<LocalUser> data) {
    if (_profile.getId() == data->getId()) {

        _profile.copyFrom(data.data());
        _settings->setValue(CURRENT_PROFILE_KEY, _profile.getId());

        loadOldSaves();

        emit profileChanged();

    }

    if (_bestUser.getId() == data->getId()) {
        _bestUser.copyFrom(data.data());
        emit bestUserChanged(&_bestUser);

    }

    if (_selectedUser.getId() == data->getId()) {
        _selectedUser.copyFrom(data.data());
        emit selectedUserChanged(&_selectedUser);

    }
}

bool HanoiTowers::randomColor() const {
    return _settings->getValue(RANDOM_COLOR_KEY, false).toBool();
}

void HanoiTowers::setRandomColor(bool random) {

    if (_settingsData.randomColor != random) {

        _settings->setValue(RANDOM_COLOR_KEY, random);
        _settingsData.randomColor = random;
        emit randomColorChanged();
    }
}

bool HanoiTowers::animation() const{
    return _settings->getValue(ANIMATION_KEY, true).toBool();
}

void HanoiTowers::setAnimation(bool value) {
    if (_settingsData.animation != value) {

        _settings->setValue(ANIMATION_KEY, value);
        _settingsData.animation = value;
        emit animationChanged();
    }
}

unsigned short HanoiTowers::getMinSteps(const unsigned short lvl) const{
    return static_cast<unsigned short>(pow(2, lvl)) - 1;
}

bool HanoiTowers::isFirst()const{
    return _settings->getValue(FIRST_RUN_KEY, true).toBool();
}

void HanoiTowers::setShowHelp(bool state) {
    _settings->setValue(FIRST_RUN_KEY, state);
}

void HanoiTowers::setNewAvatar(QString pathToAvatar) {
    if (pathToAvatar.contains("file://")) {
        pathToAvatar = pathToAvatar.right(pathToAvatar.size() - 7);
    }

    QImage img(pathToAvatar);
    int maxSize = std::max(img.size().width(), img.size().height());
    bool widthIsLarge = maxSize == img.size().width();
    if (maxSize > 200) {
        if (widthIsLarge) {
            img = img.scaledToWidth(200,
                                    Qt::TransformationMode::SmoothTransformation);

        } else {
            img = img.scaledToHeight(200,
                                    Qt::TransformationMode::SmoothTransformation);
        }
    }

    QByteArray arr;
    QBuffer buf(&arr);
    buf.open(QIODevice::WriteOnly);
    img.save(&buf, "PNG");
    buf.close();

    _client->setNewAvatar(_profile.getId().toString(), arr);

}

void HanoiTowers::selectUserFromWorldTable(const QString &userId) {
    _selectedUser.setId(userId);
    _client->getUserData(userId);
}

bool HanoiTowers::fog() const {
    return _settings->getValue(FOG, true).toBool();
}

bool HanoiTowers::fogAnimation() const {
    return _settings->getValue(FOG_ANIMATION, true).toBool();
}

HanoiTowers::~HanoiTowers() {
    updateProfile();

    QCoreApplication::processEvents();

    _imageProvider->stop();
    _client->softDelete();


    delete _dataConverter;
}

QString HanoiTowers::profile() const {
    return _profile.name();
}

QObject* HanoiTowers::profileList() {
    return _recordsTableProxy;
}

QObject *HanoiTowers::worldList() {
    return _worldProxy;
}

bool HanoiTowers::createProfile(const QString& userId, const QString &userName) {

    LocalUser user;
    user.setName(userName);
    user.setUserId(userId);

    if (!_client->addProfile(user)) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Create user error"),
                    tr("Failed to create a new user, The name %0 alredy used.").arg(userId), "",
                    QmlNotificationService::NotificationData::Error);
        return false;

    }

    _recordsTable->updateAddSourceItem(_dataConverter->toUserPreview(user));

    return true;
}

QObject *HanoiTowers::profileObject() {
    return &_profile;
}

GameState *HanoiTowers::gameState() {
    if (auto obj = dynamic_cast<LocalUser*>(profileObject())) {
        return obj->gameState();
    }

    return nullptr;
}

QObject *HanoiTowers::client() {
    return _client;
}

void HanoiTowers::removeUser(const QString &userId) {

    if (_client->isLogined() && !_client->removeUser()) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Remove online error"), tr("current profile not online!"), "",
                    QmlNotificationService::NotificationData::Warning);
    }

    if (!_client->removeLocalUser(userId)) {
        QmlNotificationService::NotificationService::getService()->setNotify(
                    tr("Remove Local user"), tr("Failed to remove the local user data"), "",
                    QmlNotificationService::NotificationData::Warning);
        return;
    }

    _recordsTable->removeSourceItem(userId);

    auto userID = _recordsTable->data(_recordsTable->index(0,0), RecordListModel::UserId).toString();

    if (userID.isEmpty()) {
        userID = DEFAULT_USER_ID;
    }

    setProfile(userID);

}

void HanoiTowers::setProfile(QString userId) {
    if (!_client)
        return;

    updateProfile();

    _profile.setId(userId);
    if (_client->setProfile(userId)) {
        emit profileChanged();

    } else {
        createProfile(userId, userId);
        _client->setProfile(userId);
    }
}

void HanoiTowers::setReward(int revard) {

    if (_profile.record() < revard) {
        _profile.setRecord(revard);
        updateProfile();
        _recordsTable->updateAddSourceItem(_dataConverter->toUserPreview(_profile));

    }
}

void HanoiTowers::setFog(bool fog) {
    if (_settingsData.fog == fog)
        return;

    _settingsData.fog = fog;
    _settings->setValue(FOG, fog);

    if (!_settingsData.fog)
        setFogAnimation(_settingsData.fog);

    emit fogChanged(_settingsData.fog);
}

void HanoiTowers::setFogAnimation(bool fogAnimation) {
    if (_settingsData.fogAnimation == fogAnimation ||
            !_settingsData.fog)
        return;

    _settingsData.fogAnimation = fogAnimation;
    _settings->setValue(FOG_ANIMATION, fogAnimation);

    emit fogAnimationChanged(_settingsData.fogAnimation);
}

void HanoiTowers::setGameState(GameState *gameState) {
    if (_profile.gameState() == gameState)
        return;

    _profile.setGameState(*gameState);
    emit profileChanged();
}

void HanoiTowers::setOnlineStatus(QH::ClientStatus onlineStatus) {
    if (_onlineStatus == static_cast<OnlineStatus>(onlineStatus))
        return;

    if (_profile.isOnline() && onlineStatus == QH::ClientStatus::Connected) {
        if (!_client->login(DataConverter::toUserMember(_profile))) {
            QuasarAppUtils::Params::log("Failed to login.", QuasarAppUtils::Error);
        }
    }

    if (_profile.isOnline() && onlineStatus == QH::ClientStatus::Logined) {
        if (!_client->getUserData(_profile.getId().toString())) {
            QuasarAppUtils::Params::log("Failed to send a user data request.", QuasarAppUtils::Error);
        }

        if (!_client->subscribeToWorld()) {
            QuasarAppUtils::Params::log("Failed to subscribe to world.", QuasarAppUtils::Error);
        }
    }

    _onlineStatus = static_cast<OnlineStatus>(onlineStatus);
    emit onlineStatusChanged(static_cast<int>(_onlineStatus));
}

void HanoiTowers::handleWorldChanged(QSharedPointer<WorldUpdate> delta) {
    for (const auto &val: qAsConst(delta->getDataAddUpdate())) {
        _world->updateAddSourceItem(val);
    }

    for (const auto &val: qAsConst(delta->getDataRemove())) {
        _world->removeSourceItem(val);
    }
}

void HanoiTowers::handleWorldInited(QHash<QString, UserPreview> initWorldList) {
    _world->setSource({initWorldList.begin(), initWorldList.end()});
}

bool HanoiTowers::showCredits() const {
    return _settingsData.showCredits;
}

void HanoiTowers::setShowCredits(bool newShowCredits) {
    if (_settingsData.showCredits != newShowCredits) {

        _settings->setValue(SHOW_CREDITS, newShowCredits);
        _settingsData.showCredits = newShowCredits;
        emit showCreditsChanged();
    }
}

unsigned int HanoiTowers::launchCount() const {
    return _settingsData._launchCount;
}

void HanoiTowers::setLaunchCount(unsigned int newLaunchCount) {
    if (_settingsData._launchCount == newLaunchCount)
        return;

    _settingsData._launchCount = newLaunchCount;
    _settings->setValue(LAUNCH_COUNT, newLaunchCount);

    emit launchCountChanged();
}