/*
 * Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co., Ltd.
 *
 * Author:     gongheng<gongheng@uniontech.com>
 *
 * Maintainer: zhengyouge<zhengyouge@uniontech.com>
 *             gongheng<gongheng@uniontech.com>
 *
 * This program is free 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
 * any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "dfmvaultactivesetunlockmethodview.h"
#include "operatorcenter.h"
#include "accessibility/ac-lib-file-manager.h"
#include "controllers/vaultcontroller.h"
#include "vault/vaultconfig.h"
#include "vault/tpmoperator.h"
#include "utils/grouppolicy.h"

#include <DPasswordEdit>
#include <DLabel>
#include <DWaterProgress>
#include <DSpinner>

#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QDebug>
#include <QToolTip>
#include <QRegExp>
#include <QRegExpValidator>
#include <QLabel>
#include <QGridLayout>
#include <QComboBox>
#include <QSlider>
#include <QVBoxLayout>

DFMVaultActiveSetUnlockMethodView::DFMVaultActiveSetUnlockMethodView(QWidget *parent)
    : QWidget(parent)
    , m_pTypeCombo(nullptr)
    , m_pPassword(nullptr)
    , m_pRepeatPassword(nullptr)
    , m_pPasswordHintLabel(nullptr)
    , m_pTips(nullptr)
//    , m_pOtherMethod(nullptr)
    , m_pNext(nullptr)
{
    AC_SET_ACCESSIBLE_NAME(this, AC_VAULT_ACTIVE_SET_PASSWORD_WIDGET);

    // 设置开锁方式标签
    QLabel *pLabel = new QLabel(tr("Set Vault Password"), this);
    AC_SET_ACCESSIBLE_NAME(pLabel, AC_VAULT_ACTIVE_SET_PASSWORD_TITLE);
    QFont font = pLabel->font();
    font.setPixelSize(18);
    pLabel->setFont(font);
    pLabel->setAlignment(Qt::AlignHCenter);

    // 类型
    DLabel *pTypeLabel = new DLabel(tr("Encryption method"), this);
    AC_SET_ACCESSIBLE_NAME(pTypeLabel, AC_VAULT_ACTIVE_SET_PASSWORD_TYPE_LABEL);
    m_pTypeCombo = new QComboBox(this);
    AC_SET_ACCESSIBLE_NAME(m_pTypeCombo, AC_VAULT_ACTIVE_SET_PASSWORD_TYPE_COMBOX);
    QStringList lstItems;
    lstItems << tr("Key encryption") << tr("Transparent encryption");
    m_pTypeCombo->addItems(lstItems);
    connect(m_pTypeCombo, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotTypeChanged(int)));

    // 设置只能输入大小写字母、数字和部分符号的正则表达式
    QRegExp regx("[A-Za-z0-9,.;?@/=()<>_+*&^%$#!`~\'\"|]+");
    // 创建验证器
    QValidator *validator = new QRegExpValidator(regx, this);

    // 设置密码
    m_pPasswordLabel = new DLabel(tr("Password"), this);
    AC_SET_ACCESSIBLE_NAME(m_pPasswordLabel, AC_VAULT_ACTIVE_SET_PASSWORD_PASSWORD_LABEL);
    m_pPassword = new DPasswordEdit(this);
    AC_SET_ACCESSIBLE_NAME(m_pPassword, AC_VAULT_ACTIVE_SET_PASSWORD_PASSWORD_EDIT);
    m_pPassword->lineEdit()->setValidator(validator);   // 设置验证器
    m_pPassword->lineEdit()->setPlaceholderText(tr("≥ 8 chars, contains A-Z, a-z, 0-9, and symbols"));
    m_pPassword->lineEdit()->setAttribute(Qt::WA_InputMethodEnabled, false);
    connect(m_pPassword, &DPasswordEdit::textEdited,
            this, &DFMVaultActiveSetUnlockMethodView::slotLimiPasswordLength);
    connect(m_pPassword, &DPasswordEdit::editingFinished,
            this, &DFMVaultActiveSetUnlockMethodView::slotPasswordEditFinished);
    connect(m_pPassword, &DPasswordEdit::textChanged,
            this, &DFMVaultActiveSetUnlockMethodView::slotPasswordEditing);
    connect(m_pPassword, &DPasswordEdit::focusChanged,
            this, &DFMVaultActiveSetUnlockMethodView::slotPasswordEditFocusChanged);


    // 重复密码
    m_pRepeatPasswordLabel = new DLabel(tr("Repeat password"), this);
    AC_SET_ACCESSIBLE_NAME(m_pRepeatPasswordLabel, AC_VAULT_ACTIVE_SET_PASSWORD_REPASSWORD_LABEL);
    m_pRepeatPassword = new DPasswordEdit(this);
    AC_SET_ACCESSIBLE_NAME(m_pRepeatPassword, AC_VAULT_ACTIVE_SET_PASSWORD_REPASSWORD_EDIT);
    m_pRepeatPassword->lineEdit()->setValidator(validator);   // 设置验证器
    m_pRepeatPassword->lineEdit()->setPlaceholderText(tr("Input the password again"));
    m_pRepeatPassword->lineEdit()->setAttribute(Qt::WA_InputMethodEnabled, false);
    connect(m_pRepeatPassword, &DPasswordEdit::textEdited,
            this, &DFMVaultActiveSetUnlockMethodView::slotLimiPasswordLength);
    connect(m_pRepeatPassword, &DPasswordEdit::editingFinished,
            this, &DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditFinished);
    connect(m_pRepeatPassword, &DPasswordEdit::textChanged,
            this, &DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditing);
    connect(m_pRepeatPassword, &DPasswordEdit::focusChanged,
            this, &DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditFocusChanged);

    // 提示信息
    m_pPasswordHintLabel = new DLabel(tr("Password hint"), this);
    AC_SET_ACCESSIBLE_NAME(m_pPasswordHintLabel, AC_VAULT_ACTIVE_SET_PASSWORD_HINT_LABEL);
    m_pTips = new QLineEdit(this);
    AC_SET_ACCESSIBLE_NAME(m_pTips, AC_VAULT_ACTIVE_SET_PASSWORD_HINT_EDIT);
    m_pTips->setMaxLength(14);
    m_pTips->setPlaceholderText(tr("Optional"));

    // 透明加密描述文本
    textLay = new QVBoxLayout();
    TransparentEncryptionText = new DLabel(tr("The file vault will be automatically unlocked when accessed, "
                                              "without verifying the password. "
                                              "Files in it will be inaccessible under other user accounts. "), this);
    AC_SET_ACCESSIBLE_NAME(TransparentEncryptionText, AC_VAULT_ACTIVE_SET_PASSWORD_TRANSPARENT_LABEL);
    TransparentEncryptionText->setVisible(false);
    TransparentEncryptionText->setWordWrap(true);
    textLay->setContentsMargins(10, 0, 0, 0);
    textLay->addWidget(TransparentEncryptionText);

    useTPMCheckbox = new QCheckBox(tr("Encrypted Using TPM"), this);
    useTPMCheckbox->setCheckState(Qt::Unchecked);
    if (!TPMOperator::instance()->checkTPMAvailable()) {
        useTPMCheckbox->setEnabled(false);
    }

    errorLabel = new DLabel(this);
    QPalette pe;
    pe.setColor(QPalette::WindowText, Qt::red);
    errorLabel->setPalette(pe);
    errorLabel->setText("");

    // 下一步按钮
    m_pNext = new QPushButton(tr("Next"), this);
    AC_SET_ACCESSIBLE_NAME(m_pNext, AC_VAULT_ACTIVE_SET_PASSWORD_NEXT_BUTTON);
    m_pNext->setEnabled(false);
    connect(m_pNext, &QPushButton::clicked,
            this, &DFMVaultActiveSetUnlockMethodView::slotNextBtnClicked);

    // 布局
    play1 = new QGridLayout();
    play1->setMargin(0);

    play1->addWidget(pTypeLabel, 0, 0, 1, 1, Qt::AlignLeft);
    play1->addWidget(m_pTypeCombo, 0, 1, 1, 5);

    play1->addWidget(m_pPasswordLabel, 1, 0, 1, 1, Qt::AlignLeft);
    play1->addWidget(m_pPassword, 1, 1, 1, 5);

    play1->addWidget(m_pRepeatPasswordLabel, 2, 0, 1, 1, Qt::AlignLeft);
    play1->addWidget(m_pRepeatPassword, 2, 1, 1, 5);

    play1->addWidget(m_pPasswordHintLabel, 3, 0, 1, 1, Qt::AlignLeft);
    play1->addWidget(m_pTips, 3, 1, 1, 5);
    play1->setColumnMinimumWidth(0, 80);

    play1->addWidget(useTPMCheckbox, 4, 1, 1, 5);
    play1->addWidget(errorLabel, 5, 0, 1, 6, Qt::AlignHCenter);

    QVBoxLayout *play = new QVBoxLayout(this);
    play->setMargin(0);
    play->addWidget(pLabel);
    play->addSpacing(15);
    play->addLayout(play1);
    play->addStretch();
    play->addWidget(m_pNext);

    // 创建文件夹与目录
    if (!OperatorCenter::getInstance()->createDirAndFile())
        qWarning() << "Vault: create dir failed!";
}

DFMVaultActiveSetUnlockMethodView::~DFMVaultActiveSetUnlockMethodView()
{
    if (textLay) {
        delete textLay;
        textLay = nullptr;
    }
}

void DFMVaultActiveSetUnlockMethodView::clearText()
{
    m_pPassword->clear();
    m_pPassword->hideAlertMessage();
    // 修复BUG-51508 取消密码框警告状态
    m_pPassword->setAlert(false);
    m_pRepeatPassword->clear();
    m_pRepeatPassword->hideAlertMessage();
    // 修复BUG-51508 取消密码框警告状态
    m_pRepeatPassword->setAlert(false);
    m_pTips->clear();
    m_pNext->setEnabled(true);
}

void DFMVaultActiveSetUnlockMethodView::slotPasswordEditing()
{
    bool ok = checkPassword(m_pPassword->text());
    if (ok) {
        if (checkInputInfo()) {
            m_pNext->setEnabled(true);
        }
    }
    m_pNext->setEnabled(false);
}

void DFMVaultActiveSetUnlockMethodView::slotPasswordEditFinished()
{
    bool ok = checkPassword(m_pPassword->text());
    if (!ok) {
        m_pNext->setEnabled(false);
        // 修复BUG-51508 激活密码框警告状态
        m_pPassword->setAlert(true);
        m_pPassword->showAlertMessage(tr("≥ 8 chars, contains A-Z, a-z, 0-9, and symbols"), TIPS_TIME);
    } else {
        if (checkInputInfo()) {
            m_pNext->setEnabled(true);
        }
    }
}

void DFMVaultActiveSetUnlockMethodView::slotPasswordEditFocusChanged(bool bFocus)
{
    if (bFocus) {
        // 修复BUG-51508 取消密码框警告状态
        m_pPassword->setAlert(false);
        m_pPassword->hideAlertMessage();
    }
}

void DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditFinished()
{
    bool ok = checkRepeatPassword();
    if (!ok) {
        // 修复BUG-51508 激活密码框警告状态
        m_pRepeatPassword->setAlert(true);
        m_pRepeatPassword->showAlertMessage(tr("Passwords do not match"), TIPS_TIME);
    }
}

void DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditing()
{
    const QString &strRepeatPassword = m_pRepeatPassword->text();
    const QString &strPassword = m_pPassword->text();

    bool bSizeMatch = strRepeatPassword.size() == strPassword.size();
    if (bSizeMatch) {
        if (checkPassword(m_pPassword->text())) {
            if (checkRepeatPassword()) {
                m_pNext->setEnabled(true);
                return;
            } else {
                m_pRepeatPassword->showAlertMessage(tr("Passwords do not match"));
            }
        }
    }
    m_pNext->setEnabled(false);
}

void DFMVaultActiveSetUnlockMethodView::slotRepeatPasswordEditFocusChanged(bool bFocus)
{
    if (bFocus) {
        // 修复BUG-51508 取消密码框警告状态
        m_pRepeatPassword->setAlert(false);
        m_pRepeatPassword->hideAlertMessage();
    }
}

void DFMVaultActiveSetUnlockMethodView::slotGenerateEditChanged(const QString &str)
{
    if (!checkPassword(str)) {
        m_pNext->setEnabled(false);
    } else {
        m_pNext->setEnabled(true);
    }
}

void DFMVaultActiveSetUnlockMethodView::slotNextBtnClicked()
{
    m_pNext->setEnabled(false);

    // 设置将密码直接传递到cryfs
    VaultConfig config;
    config.set(CONFIG_NODE_NAME, CONFIG_KEY_USE_USER_PASSWORD, QVariant("1"));
    // 记录版本信息
    config.set(CONFIG_NODE_NAME, CONFIG_KEY_VERSION, QVariant(CONFIG_VAULT_VERSION_1050));

    bool tpmEnbale = useTPMCheckbox->isChecked();
    if (tpmEnbale) {
        config.set(CONFIG_NODE_NAME, CONFIG_KEY_ENABLE_TPM, CONFIG_VALUE_YES);
    } else {
        config.set(CONFIG_NODE_NAME, CONFIG_KEY_ENABLE_TPM, CONFIG_VALUE_NO);
    }

    if (m_pTypeCombo->currentIndex() == 0) {    // 密钥加密
        const QString &strPassword = m_pPassword->text();
        const QString &strPasswordHint = m_pTips->text();
        // Used to verify user input password
        if (!OperatorCenter::getInstance()->saveSaltAndCiphertext(strPassword, strPasswordHint)) {
            m_pNext->setEnabled(true);
            return;
        }

        // 记录加密方式
        config.set(CONFIG_NODE_NAME, CONFIG_KEY_ENCRYPTION_METHOD, QVariant(CONFIG_METHOD_VALUE_KEY));

        // 判断tpm是否已启用，如果是，则加密密码
        if (tpmEnbale) {
            // create random password
            QString password { "" };
            if (!TPMOperator::instance()->getRandom(TPM_KEY_PASSWORD_SIZE, password)) {
                errorLabel->setText(tr("System Encryption Failed"));
                m_pNext->setEnabled(true);
                return;
            }
            const QString &keyPin = strPassword;    // 在启用tpm下，用户输入的密码作为pin
            if (!tpmEncryptPassword(password, keyPin)) {
                errorLabel->setText(tr("System Encryption Failed"));
                m_pNext->setEnabled(true);
                return;
            }
            // 设置cryfs的密码
            OperatorCenter::getInstance()->setCryfsPassword(password);
            // Used to retrieve password
            QString pinAndPassword = keyPin + "--" + password;
            if (!OperatorCenter::getInstance()->createKeyNew(pinAndPassword)) {
                m_pNext->setEnabled(true);
                return;
            }
        } else {
            // 设置cryfs的密码
            OperatorCenter::getInstance()->setCryfsPassword(strPassword);
            // Used to retrieve password
            if (!OperatorCenter::getInstance()->createKeyNew(strPassword)) {
                m_pNext->setEnabled(true);
                return;
            }
        }
    } else if (m_pTypeCombo->currentIndex() == 1) {    // 透明加密
        // 生成随机密码
        const QString &strPassword = OperatorCenter::getInstance()->autoGeneratePassword(18);
        if (strPassword.isEmpty()) {
            m_pNext->setEnabled(true);
            return;
        }
        // 保存随机密码到Keyring
        if (!OperatorCenter::getInstance()->setPasswordToKeyring(strPassword)) {
            m_pNext->setEnabled(true);
            return;
        }

        // 记录加密方式
        config.set(CONFIG_NODE_NAME, CONFIG_KEY_ENCRYPTION_METHOD, QVariant(CONFIG_METHOD_VALUE_TRANSPARENT));

        // 判断tpm是否已启用，如果是，则加密密码
        if (tpmEnbale) {
            // create pin
            QString keyPin { "" };
            if (!TPMOperator::instance()->getRandom(TPM_KEY_PIN_SIZE, keyPin)) {
                errorLabel->setText(tr("System Encryption Failed"));
                m_pNext->setEnabled(true);
                return;
            }
            if (!tpmEncryptPassword(strPassword, keyPin)) {
                errorLabel->setText(tr("System Encryption Failed"));
                m_pNext->setEnabled(true);
                return;
            }
            // Save the pin to Keyring
            if (!OperatorCenter::getInstance()->saveInfoToKeyring(keyPin, TPM_KEY_PIN_DOMAIN)) {
                errorLabel->setText(tr("System Encryption Failed"));
                m_pNext->setEnabled(true);
                return;
            }
        }
    }

    emit sigAccepted();
}

void DFMVaultActiveSetUnlockMethodView::slotTypeChanged(int index)
{
    if (index) { // 透明加密
        play1->removeWidget(m_pPasswordLabel);
        play1->removeWidget(m_pPassword);
        play1->removeWidget(m_pRepeatPasswordLabel);
        play1->removeWidget(m_pRepeatPassword);
        play1->removeWidget(m_pPasswordHintLabel);
        play1->removeWidget(m_pTips);
        m_pPasswordLabel->setVisible(false);
        m_pPassword->setVisible(false);
        m_pRepeatPasswordLabel->setVisible(false);
        m_pRepeatPassword->setVisible(false);
        m_pPasswordHintLabel->setVisible(false);
        m_pTips->setVisible(false);

        play1->addLayout(textLay, 1, 1, 3, 5);
        TransparentEncryptionText->setVisible(true);

        useTPMCheckbox->setCheckState(Qt::Unchecked);
        useTPMCheckbox->setEnabled(false);
        errorLabel->setText("");
        m_pNext->setEnabled(true);
    } else { // 密钥加密
        play1->removeItem(textLay);
        TransparentEncryptionText->setVisible(false);

        play1->addWidget(m_pPasswordLabel, 1, 0, 1, 1, Qt::AlignLeft);
        play1->addWidget(m_pPassword, 1, 1, 1, 5);
        play1->addWidget(m_pRepeatPasswordLabel, 2, 0, 1, 1, Qt::AlignLeft);
        play1->addWidget(m_pRepeatPassword, 2, 1, 1, 5);
        play1->addWidget(m_pPasswordHintLabel, 3, 0, 1, 1, Qt::AlignLeft);
        play1->addWidget(m_pTips, 3, 1, 1, 5);
        m_pPasswordLabel->setVisible(true);
        m_pPassword->setVisible(true);
        m_pRepeatPasswordLabel->setVisible(true);
        m_pRepeatPassword->setVisible(true);
        m_pPasswordHintLabel->setVisible(true);
        m_pTips->setVisible(true);

        checkInputInfo() ? m_pNext->setEnabled(true) : m_pNext->setEnabled(false);
        if (!TPMOperator::instance()->checkTPMAvailable()) {
            useTPMCheckbox->setEnabled(false);
        } else {
            useTPMCheckbox->setEnabled(true);
        }
        errorLabel->setText("");
    }
}

void DFMVaultActiveSetUnlockMethodView::slotLimiPasswordLength(const QString &password)
{
    DPasswordEdit *pPasswordEdit = qobject_cast<DPasswordEdit *>(sender());
    if (password.length() > PASSWORD_LENGHT_MAX) {
        pPasswordEdit->setText(password.mid(0, PASSWORD_LENGHT_MAX));
    }
}

bool DFMVaultActiveSetUnlockMethodView::checkPassword(const QString &password)
{
    QString strPassword = password;

    QRegExp rx("^(?![^a-z]+$)(?![^A-Z]+$)(?!\\D+$)(?![a-zA-Z0-9]+$).{8,}$");
    QRegExpValidator v(rx);
    int pos = 0;
    QValidator::State res;
    res = v.validate(strPassword, pos);
    if (QValidator::Acceptable != res) {
        return false;
    } else {
        return true;
    }
}

bool DFMVaultActiveSetUnlockMethodView::checkRepeatPassword()
{
    const QString &strRepeatPassword = m_pRepeatPassword->text();
    const QString &strPassword = m_pPassword->text();
    if (strRepeatPassword != strPassword) {
        return false;
    } else {
        return true;
    }
}

bool DFMVaultActiveSetUnlockMethodView::checkInputInfo()
{
    if (checkPassword(m_pPassword->text()) && checkRepeatPassword()) {
        return true;
    } else {
        return false;
    }
}

bool DFMVaultActiveSetUnlockMethodView::tpmEncryptPassword(const QString &password, const QString &keyPin)
{
    if (!TPMOperator::instance()->checkTPMAvailable()) {
        qCritical() << "Vault error: TPM can not use!";
        return false;
    }

    VaultConfig config;
    // create key
    QString hashAlgo { "" };
    QString keyAlgo { "" };
    getAlgoFromGroupPolicy(hashAlgo, keyAlgo);

    QEventLoop loop;
    connect(TPMOperator::instance(), &TPMOperator::sigAsynEncryptFinished, this, [&loop](bool result){
        if (result)
            loop.exit();
        else
            loop.exit(1);
    });
    TPMOperator::instance()->asynEncrypt(hashAlgo, keyAlgo, keyPin, password);
    DSpinner spinner(this);
    spinner.setFixedSize(50, 50);
    spinner.move((width() - spinner.width()) / 2, (height() - spinner.height()) / 2);
    spinner.start();
    spinner.show();
    int re = loop.exec();
    if (re == 0)
        return true;
    else
        return false;
}

void DFMVaultActiveSetUnlockMethodView::getAlgoFromGroupPolicy(QString &hashAlgo, QString &keyAlgo)
{
    if (DTK_POLICY_SUPPORT) {
        if (GroupPolicy::instance()->containKey(GROUP_POLICY_VAULT_TPM_HASH_ALOG)) {
            hashAlgo = GroupPolicy::instance()->getValue(GROUP_POLICY_VAULT_TPM_HASH_ALOG, QVariant(CONFIG_VAULT_NO_EXIST)).toString();
            if (hashAlgo == CONFIG_VAULT_NO_EXIST || hashAlgo.isEmpty())
                hashAlgo = DEFAULT_TPM_SM3_ALGO_NAME;
        }
        if (GroupPolicy::instance()->containKey(GROUP_POLICY_VAULT_TPM_KEY_ALGO)) {
            keyAlgo = GroupPolicy::instance()->getValue(GROUP_POLICY_VAULT_TPM_KEY_ALGO, QVariant(CONFIG_VAULT_NO_EXIST)).toString();
            if (keyAlgo == CONFIG_VAULT_NO_EXIST || keyAlgo.isEmpty())
                keyAlgo = DEFAULT_TPM_SM4_ALGO_NAME;
        }
    }

    if (!TPMOperator::instance()->isSupportAlgo(hashAlgo)) {
        hashAlgo = DEFAULT_TPM_SHA256_ALGO_NAME;
        GroupPolicy::instance()->setValue(GROUP_POLICY_VAULT_TPM_HASH_ALOG, QVariant(DEFAULT_TPM_SHA256_ALGO_NAME));
    }
    if (!TPMOperator::instance()->isSupportAlgo(keyAlgo)) {
        keyAlgo = DEFAULT_TPM_AES_ALGO_NAME;
        GroupPolicy::instance()->setValue(GROUP_POLICY_VAULT_TPM_KEY_ALGO, QVariant(DEFAULT_TPM_AES_ALGO_NAME));
    }
}

void DFMVaultActiveSetUnlockMethodView::showEvent(QShowEvent *event)
{
    VaultController::ins()->setVauleCurrentPageMark(VaultPageMark::CREATEVAULTPAGE);
    QWidget::showEvent(event);
}
