/*
    Copyright © 2014-2015 by The qTox Project Contributors

    This file is part of qTox, a Qt-based graphical interface for Tox.

    qTox is libre software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    qTox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with qTox.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "core/core.h"
#include "nexus.h"
#include "ui_profileform.h"
#include "profileform.h"
#include "widget/form/settingswidget.h"
#include "widget/maskablepixmapwidget.h"
#include "widget/form/setpassworddialog.h"
#include "widget/contentlayout.h"
#include "widget/tool/croppinglabel.h"
#include "widget/widget.h"
#include "widget/gui.h"
#include "widget/style.h"
#include "widget/translator.h"
#include "persistence/profilelocker.h"
#include "persistence/profile.h"
#include "persistence/settings.h"
#include "net/toxme.h"
#include <QLabel>
#include <QLineEdit>
#include <QGroupBox>
#include <QApplication>
#include <QClipboard>
#include <QInputDialog>
#include <QFileDialog>
#include <QBuffer>
#include <QMessageBox>
#include <QComboBox>
#include <QWindow>
#include <QMenu>
#include <QMouseEvent>

ProfileForm::ProfileForm(QWidget *parent) :
    QWidget{parent}, qr{nullptr}
{
    bodyUI = new Ui::IdentitySettings;
    bodyUI->setupUi(this);
    core = Core::getInstance();

    head = new QWidget(this);
    QHBoxLayout* headLayout = new QHBoxLayout();
    head->setLayout(headLayout);

    QLabel* imgLabel = new QLabel();
    headLayout->addWidget(imgLabel);

    nameLabel = new QLabel();
    QFont bold;
    bold.setBold(true);
    nameLabel->setFont(bold);
    headLayout->addWidget(nameLabel);
    headLayout->addStretch(1);

    imgLabel->setPixmap(QPixmap(":/img/settings/identity.png").scaledToHeight(40, Qt::SmoothTransformation));

    // tox
    toxId = new ClickableTE();
    toxId->setReadOnly(true);
    toxId->setFrame(false);
    toxId->setFont(Style::getFont(Style::Small));
    toxId->setToolTip(bodyUI->toxId->toolTip());

    QVBoxLayout *toxIdGroup = qobject_cast<QVBoxLayout*>(bodyUI->toxGroup->layout());
    delete toxIdGroup->replaceWidget(bodyUI->toxId, toxId);     // Original toxId is in heap, delete it
    bodyUI->toxId->hide();

    /* Toxme section init */
    bodyUI->toxmeServersList->addItem("toxme.io");
    QString toxmeInfo = Settings::getInstance().getToxmeInfo();
    if (toxmeInfo.isEmpty()) // User not registered
        showRegisterToxme();
    else
        showExistingToxme();

    bodyUI->qrLabel->setWordWrap(true);

    QRegExp re("[^@ ]+");
    QRegExpValidator* validator = new QRegExpValidator(re, this);
    bodyUI->toxmeUsername->setValidator(validator);

    profilePicture = new MaskablePixmapWidget(this, QSize(64, 64), ":/img/avatar_mask.svg");
    profilePicture->setPixmap(QPixmap(":/img/contact_dark.svg"));
    profilePicture->setContextMenuPolicy(Qt::CustomContextMenu);
    profilePicture->setClickable(true);
    profilePicture->installEventFilter(this);
    profilePicture->setAccessibleName("Profile avatar");
    profilePicture->setAccessibleDescription("Set a profile avatar shown to all contacts");
    connect(profilePicture, SIGNAL(clicked()), this, SLOT(onAvatarClicked()));
    connect(profilePicture, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showProfilePictureContextMenu(const QPoint&)));
    QHBoxLayout *publicGrouplayout = qobject_cast<QHBoxLayout*>(bodyUI->publicGroup->layout());
    publicGrouplayout->insertWidget(0, profilePicture);
    publicGrouplayout->insertSpacing(1, 7);

    timer.setInterval(750);
    timer.setSingleShot(true);
    connect(&timer, &QTimer::timeout, this, [=]() {bodyUI->toxIdLabel->setText(bodyUI->toxIdLabel->text().replace(" ✔", "")); hasCheck = false;});

    connect(bodyUI->toxIdLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
    connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
    connect(core, &Core::idSet, this, &ProfileForm::setToxId);
    connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited()));
    connect(bodyUI->statusMessage, SIGNAL(editingFinished()), this, SLOT(onStatusMessageEdited()));
    connect(bodyUI->renameButton, &QPushButton::clicked, this, &ProfileForm::onRenameClicked);
    connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked);
    connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked);
    connect(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked);
    connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::onDeletePassClicked);
    connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::onChangePassClicked);
    connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::setPasswordButtonsText);
    connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::setPasswordButtonsText);
    connect(bodyUI->saveQr, &QPushButton::clicked, this, &ProfileForm::onSaveQrClicked);
    connect(bodyUI->copyQr, &QPushButton::clicked, this, &ProfileForm::onCopyQrClicked);
    connect(bodyUI->toxmeRegisterButton, &QPushButton::clicked, this, &ProfileForm::onRegisterButtonClicked);
    connect(bodyUI->toxmeUpdateButton, &QPushButton::clicked, this, &ProfileForm::onRegisterButtonClicked);

    connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->setText(val); });
    connect(core, &Core::statusMessageSet, this, [=](const QString& val) { bodyUI->statusMessage->setText(val); });

    for (QComboBox* cb : findChildren<QComboBox*>())
    {
            cb->installEventFilter(this);
            cb->setFocusPolicy(Qt::StrongFocus);
    }

    retranslateUi();
    Translator::registerHandler(std::bind(&ProfileForm::retranslateUi, this), this);
}

void ProfileForm::prFileLabelUpdate()
{
    Nexus& nexus = Nexus::getInstance();
    bodyUI->prFileLabel->setText(tr("Current profile: ") + nexus.getProfile()->getName() + ".tox");
}

ProfileForm::~ProfileForm()
{
    Translator::unregister(this);
    delete qr;
    delete bodyUI;
    head->deleteLater();
}

bool ProfileForm::isShown() const
{
    if (head->isVisible())
    {
        window()->windowHandle()->alert(0);
        return true;
    }

    return false;
}

void ProfileForm::show(ContentLayout* contentLayout)
{
    contentLayout->mainHead->layout()->addWidget(head);
    contentLayout->mainContent->layout()->addWidget(this);
    head->show();
    QWidget::show();
    prFileLabelUpdate();
    bool portable = Settings::getInstance().getMakeToxPortable();
    QString defaultPath = QDir(Settings::getInstance().getSettingsDirPath()).path().trimmed();
    QString appPath = QApplication::applicationDirPath();
    QString dirPath = portable ? appPath : defaultPath;

    QString dirPrLink = tr("Current profile location: %1")
            .arg(QString("<a href=\"file://%1\">%1</a>").arg(dirPath));

    bodyUI->dirPrLink->setText(dirPrLink);
    bodyUI->dirPrLink->setOpenExternalLinks(true);
    bodyUI->dirPrLink->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse);
    bodyUI->dirPrLink->setMaximumSize(bodyUI->dirPrLink->sizeHint());
    bodyUI->userName->setFocus();
    bodyUI->userName->selectAll();
}

bool ProfileForm::eventFilter(QObject *object, QEvent *event)
{
    if (object == static_cast<QObject*>(profilePicture) && event->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        if (mouseEvent->button() == Qt::RightButton)
            return true;
    }
    return false;
}

void ProfileForm::showProfilePictureContextMenu(const QPoint &point)
{
    QPoint pos = profilePicture->mapToGlobal(point);

    QMenu contextMenu;
    QAction *removeAction = contextMenu.addAction(style()->standardIcon(QStyle::SP_DialogCancelButton), tr("Remove"));
    QAction *selectedItem = contextMenu.exec(pos);

    if (selectedItem == removeAction)
        Nexus::getProfile()->removeAvatar();
}

void ProfileForm::copyIdClicked()
{
    toxId->selectAll();
    QString txt = toxId->text();
    txt.replace('\n',"");
    QApplication::clipboard()->setText(txt, QClipboard::Clipboard);
    if (QApplication::clipboard()->supportsSelection())
      QApplication::clipboard()->setText(txt, QClipboard::Selection);
    toxId->setCursorPosition(0);

    if (!hasCheck)
    {
        bodyUI->toxIdLabel->setText(bodyUI->toxIdLabel->text() + " ✔");
        hasCheck = true;
    }
    timer.start();
}

void ProfileForm::onUserNameEdited()
{
    Core::getInstance()->setUsername(bodyUI->userName->text());
}

void ProfileForm::onStatusMessageEdited()
{
    Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text());
}

void ProfileForm::onSelfAvatarLoaded(const QPixmap& pic)
{
    profilePicture->setPixmap(pic);
}

void ProfileForm::setToxId(const QString& id)
{
    toxId->setText(id);
    toxId->setCursorPosition(0);

    delete qr;
    qr = new QRWidget();
    qr->setQRData("tox:"+id);
    bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150)));
}

void ProfileForm::onAvatarClicked()
{
    auto picToPng = [](QPixmap pic)
    {
        QByteArray bytes;
        QBuffer buffer(&bytes);
        buffer.open(QIODevice::WriteOnly);
        pic.save(&buffer, "PNG");
        buffer.close();
        return bytes;
    };

    QString filename = QFileDialog::getOpenFileName(this,
                                                    tr("Choose a profile picture"),
                                                    QDir::homePath(),
                                                    Nexus::getSupportedImageFilter(),
                                                    0,
                                                    QFileDialog::DontUseNativeDialog);
    if (filename.isEmpty())
        return;

    QFile file(filename);
    file.open(QIODevice::ReadOnly);
    if (!file.isOpen())
    {
        GUI::showError(tr("Error"), tr("Unable to open this file."));
        return;
    }

    QPixmap pic;
    if (!pic.loadFromData(file.readAll()))
    {
        GUI::showError(tr("Error"), tr("Unable to read this image."));
        return;
    }

    // Limit the avatar size to 64kB
    // We do a first rescale to 256x256 in case the image was huge, then keep tryng from here
    QByteArray bytes{picToPng(pic)};
    if (bytes.size() > 65535)
    {
        pic = pic.scaled(256,256, Qt::KeepAspectRatio, Qt::SmoothTransformation);
        bytes = picToPng(pic);
    }
    if (bytes.size() > 65535)
        bytes = picToPng(pic.scaled(128,128, Qt::KeepAspectRatio, Qt::SmoothTransformation));
    if (bytes.size() > 65535)
        bytes = picToPng(pic.scaled(64,64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
    if (bytes.size() > 65535)
        bytes = picToPng(pic.scaled(32,32, Qt::KeepAspectRatio, Qt::SmoothTransformation));

    // If this happens, you're really doing it on purpose.
    if (bytes.size() > 65535)
    {
        QMessageBox::critical(this,
                              tr("Error"),
                              tr("The supplied image is too large.\nPlease use another image."));
        return;
    }

    Nexus::getCore()->setAvatar(bytes);
}

void ProfileForm::onRenameClicked()
{
    Nexus& nexus = Nexus::getInstance();
    QString cur = nexus.getProfile()->getName();
    QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur);
    do
    {
        QString name = QInputDialog::getText(this, title, title+":");
        if (name.isEmpty()) break;
        name = Core::sanitize(name);

        if (Profile::exists(name))
            GUI::showError(tr("Profile already exists", "rename failure title"),
                           tr("A profile named \"%1\" already exists.", "rename confirm text").arg(name));
        else if (!nexus.getProfile()->rename(name))
            GUI::showError(tr("Failed to rename", "rename failed title"),
                             tr("Couldn't rename the profile to \"%1\"").arg(cur));
        else
        {
            prFileLabelUpdate();
            break;
        }
    } while (true);
}

void ProfileForm::onExportClicked()
{
    QString current = Nexus::getProfile()->getName() + Core::TOX_EXT;
    QString path = QFileDialog::getSaveFileName(this,
                                                tr("Export profile", "save dialog title"),
                                                QDir::home().filePath(current),
                                                tr("Tox save file (*.tox)", "save dialog filter"),
                                                0,
                                                QFileDialog::DontUseNativeDialog);
    if (!path.isEmpty())
    {
        if (!Nexus::tryRemoveFile(path))
        {
            GUI::showWarning(tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
            return;
        }
        if (!QFile::copy(Settings::getInstance().getSettingsDirPath()+current, path))
            GUI::showWarning(tr("Failed to copy file"), tr("The file you chose could not be written to."));
    }
}

void ProfileForm::onDeleteClicked()
{
    if (GUI::askQuestion(
                tr("Really delete profile?", "deletion confirmation title"),
                tr("Are you sure you want to delete this profile?", "deletion confirmation text")))
    {
        Nexus& nexus = Nexus::getInstance();

        QVector<QString> manualDeleteFiles = nexus.getProfile()->remove();

        if (!manualDeleteFiles.empty())
        {
            QString message = tr("The following files could not be deleted:", "deletion failed text part 1") + "\n\n";

            for (auto& file : manualDeleteFiles)
            {
                message = message + file + "\n";
            }

            message = message + "\n" + tr("Please manually remove them.", "deletion failed text part 2");

            GUI::showError(tr("Files could not be deleted!", "deletion failed title"), message);
        }

        nexus.showLoginLater();
    }
}

void ProfileForm::onLogoutClicked()
{
    Nexus& nexus = Nexus::getInstance();
    Settings::getInstance().saveGlobal();
    nexus.showLoginLater();
}

void ProfileForm::setPasswordButtonsText()
{
    if (Nexus::getProfile()->isEncrypted())
    {
        bodyUI->changePassButton->setText(tr("Change password", "button text"));
        bodyUI->deletePassButton->setVisible(true);
    }
    else
    {
        bodyUI->changePassButton->setText(tr("Set profile password", "button text"));
        bodyUI->deletePassButton->setVisible(false);
    }
}

void ProfileForm::onCopyQrClicked()
{
    QApplication::clipboard()->setImage(*qr->getImage());
}

void ProfileForm::onSaveQrClicked()
{
    QString current = Nexus::getProfile()->getName() + ".png";
    QString path = QFileDialog::getSaveFileName(this,
                                                tr("Save", "save qr image"),
                                                QDir::home().filePath(current),
                                                tr("Save QrCode (*.png)", "save dialog filter"),
                                                0,
                                                QFileDialog::DontUseNativeDialog);
    if (!path.isEmpty())
    {
        if (!Nexus::tryRemoveFile(path))
        {
            GUI::showWarning(tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
            return;
        }
        if (!qr->saveImage(path))
            GUI::showWarning(tr("Failed to copy file"), tr("The file you chose could not be written to."));
    }
}

void ProfileForm::onDeletePassClicked()
{
    Profile* pro = Nexus::getProfile();
    if (!pro->isEncrypted())
    {
        GUI::showInfo(tr("Nothing to remove"), tr("Your profile does not have a password!"));
        return;
    }

    if (!GUI::askQuestion(tr("Really delete password?","deletion confirmation title"),
                      tr("Are you sure you want to delete your password?","deletion confirmation text")))
        return;

    Nexus::getProfile()->setPassword(QString());
}

void ProfileForm::onChangePassClicked()
{
    SetPasswordDialog* dialog = new SetPasswordDialog(tr("Please enter a new password."), QString(), 0);
    int r = dialog->exec();
    if (r == QDialog::Rejected)
        return;

    QString newPass = dialog->getPassword();
    Nexus::getProfile()->setPassword(newPass);
}

void ProfileForm::retranslateUi()
{
    bodyUI->retranslateUi(this);
    nameLabel->setText(tr("User Profile"));
    setPasswordButtonsText();
    // We have to add the toxId tooltip here and not in the .ui or Qt won't know how to translate it dynamically
    toxId->setToolTip(tr("This bunch of characters tells other Tox clients how to contact you.\nShare it with your friends to communicate."));
}

void ProfileForm::showRegisterToxme()
{
    bodyUI->toxmeUsername->setText("");
    bodyUI->toxmeBio->setText("");
    bodyUI->toxmePrivacy->setChecked(false);

    bodyUI->toxmeRegisterButton->show();
    bodyUI->toxmeUpdateButton->hide();
    bodyUI->toxmePassword->hide();
    bodyUI->toxmePasswordLabel->hide();
}

void ProfileForm::showExistingToxme()
{
    QStringList info = Settings::getInstance().getToxmeInfo().split("@");
    bodyUI->toxmeUsername->setText(info[0]);
    bodyUI->toxmeServersList->addItem(info[1]);

    QString bio = Settings::getInstance().getToxmeBio();
    bodyUI->toxmeBio->setText(bio);

    bool priv = Settings::getInstance().getToxmePriv();
    bodyUI->toxmePrivacy->setChecked(priv);

    QString pass = Settings::getInstance().getToxmePass();
    bodyUI->toxmePassword->setText(pass);
    bodyUI->toxmePassword->show();
    bodyUI->toxmePasswordLabel->show();

    bodyUI->toxmeRegisterButton->hide();
    bodyUI->toxmeUpdateButton->show();
}

void ProfileForm::onRegisterButtonClicked()
{
    QString name =  bodyUI->toxmeUsername->text();
    if (name.isEmpty())
        return;

    bodyUI->toxmeRegisterButton->setEnabled(false);
    bodyUI->toxmeUpdateButton->setEnabled(false);
    bodyUI->toxmeRegisterButton->setText(tr("Register (processing)"));
    bodyUI->toxmeUpdateButton->setText(tr("Update (processing)"));

    QString id = toxId->text();
    QString bio = bodyUI->toxmeBio->text();
    QString server = bodyUI->toxmeServersList->currentText();
    bool privacy = bodyUI->toxmePrivacy->isChecked();

    Core* oldCore = Core::getInstance();

    Toxme::ExecCode code = Toxme::ExecCode::Ok;
    QString response = Toxme::createAddress(code, server, ToxId(id), name, privacy, bio);

    Core* newCore = Core::getInstance();
    // Make sure the user didn't logout (or logout and login)
    // before the request is finished, else qTox will crash.
    if (oldCore == newCore)
    {
        switch (code) {
        case Toxme::Updated:
            GUI::showInfo(tr("Done!"), tr("Account %1@%2 updated successfully").arg(name, server));
            Settings::getInstance().setToxme(name, server, bio, privacy);
            showExistingToxme();
            break;
        case Toxme::Ok:
            GUI::showInfo(tr("Done!"), tr("Successfully added %1@%2 to the database. Save your password").arg(name, server));
            Settings::getInstance().setToxme(name, server, bio, privacy, response);
            showExistingToxme();
            break;
        default:
            QString errorMessage = Toxme::getErrorMessage(code);
            qWarning() << errorMessage;
            QString translated = Toxme::translateErrorMessage(code);
            GUI::showWarning(tr("Toxme error"),  translated);
        }

        bodyUI->toxmeRegisterButton->setEnabled(true);
        bodyUI->toxmeUpdateButton->setEnabled(true);
        bodyUI->toxmeRegisterButton->setText(tr("Register"));
        bodyUI->toxmeUpdateButton->setText(tr("Update"));
    }
}
