/***************************************************************************
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include <QContextMenuEvent>
#include <QMenu>


#include <cstring>

#include <Base/Console.h>
#include <Base/Tools.h>
#include <Base/Color.h>

#include "PrefWidgets.h"


using Base::Console;
using namespace Gui;

/** Constructs a preference widget.
 */
PrefWidget::PrefWidget()
    : WindowParameter("")
{}

/**
 * Destroys the widget and detaches it from its parameter group.
 */
PrefWidget::~PrefWidget()
{
    if (getWindowParameter().isValid()) {
        getWindowParameter()->Detach(this);
    }
}

/** Sets the preference name to \a name. */
void PrefWidget::setEntryName(const QByteArray& name)
{
    m_sPrefName = name;
}

/** Sets the preference name to \a name. */
void PrefWidget::setPrefEntry(const QByteArray& name)
{
    setEntryName(name);
}

/** Returns the widget's preference name. */
QByteArray PrefWidget::entryName() const
{
    return m_sPrefName;
}

/** Sets the preference path to \a path. */
void PrefWidget::setParamGrpPath(const QByteArray& path)
{
#ifdef FC_DEBUG
    if (getWindowParameter().isValid()) {
        if (paramGrpPath() != path) {
            Base::Console().warning("Widget already attached\n");
        }
    }
#endif

    if (paramGrpPath() != path) {
        if (setGroupName(path)) {
            m_sPrefGrp = path;
            assert(getWindowParameter().isValid());
            getWindowParameter()->Attach(this);
        }
    }
}

/** Sets the preference path to \a path. */
void PrefWidget::setPrefPath(const QByteArray& name)
{
    setParamGrpPath(name);
}

/** Returns the widget's preferences path. */
QByteArray PrefWidget::paramGrpPath() const
{
    return m_sPrefGrp;
}

/**
 * This method is called if one or more values in the parameter settings are changed
 * where getParamGrp() points to.
 * Note: This method is called for each parameter inside the parameter group. So
 * you have to filter out the appropriate parameter with the name \a sReason.
 * \a rCaller calls this method.
 */
void PrefWidget::OnChange(Base::Subject<const char*>& rCaller, const char* sReason)
{
    Q_UNUSED(rCaller);
    if (std::strcmp(sReason, m_sPrefName) == 0) {
        restorePreferences();
    }
}

/**
 * Saves the current preferences of the widget.
 * All preference widget attached to the same parameter group are notified.
 */
void PrefWidget::onSave()
{
    savePreferences();
    if (getWindowParameter().isValid()) {
        getWindowParameter()->Notify(entryName());
    }
#ifdef FC_DEBUG
    else {
        qFatal("No parameter group specified!");
    }
#endif
}

/**
 * Restores the preferences of the widget.
 */
void PrefWidget::onRestore()
{
#ifdef FC_DEBUG
    if (getWindowParameter().isNull()) {
        qWarning("No parameter group specified!");
    }
#endif
    restorePreferences();
    m_Restored = true;
}

void PrefWidget::failedToSave(const QString& name) const
{
    QByteArray objname = name.toLatin1();
    if (objname.isEmpty()) {
        objname = "Undefined";
    }
    Console().warning("Cannot save %s (%s)\n", typeid(*this).name(), objname.constData());
}

void PrefWidget::failedToRestore(const QString& name) const
{
    QByteArray objname = name.toLatin1();
    if (objname.isEmpty()) {
        objname = "Undefined";
    }
    Console().warning("Cannot restore %s (%s)\n", typeid(*this).name(), objname.constData());
}

// --------------------------------------------------------------------

PrefSpinBox::PrefSpinBox(QWidget* parent)
    : QSpinBox(parent)
    , PrefWidget()
{
    setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

void PrefSpinBox::wheelEvent(QWheelEvent* event)
{
    if (hasFocus()) {
        QSpinBox::wheelEvent(event);
    }
    else {
        event->ignore();
    }
}

PrefSpinBox::~PrefSpinBox() = default;

void PrefSpinBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    int nVal = getWindowParameter()->GetInt(entryName(), QSpinBox::value());
    setValue(nVal);
}

void PrefSpinBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetInt(entryName(), (int)value());
}

// --------------------------------------------------------------------

PrefDoubleSpinBox::PrefDoubleSpinBox(QWidget* parent)
    : QDoubleSpinBox(parent)
    , PrefWidget()
{
    setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

void PrefDoubleSpinBox::wheelEvent(QWheelEvent* event)
{
    if (hasFocus()) {
        QDoubleSpinBox::wheelEvent(event);
    }
    else {
        event->ignore();
    }
}

PrefDoubleSpinBox::~PrefDoubleSpinBox() = default;

void PrefDoubleSpinBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    double fVal = (double)getWindowParameter()->GetFloat(entryName(), value());
    setValue(fVal);
}

void PrefDoubleSpinBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetFloat(entryName(), value());
}

// --------------------------------------------------------------------

PrefLineEdit::PrefLineEdit(QWidget* parent)
    : QLineEdit(parent)
    , PrefWidget()
{}

PrefLineEdit::~PrefLineEdit() = default;

void PrefLineEdit::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    QString text = this->text();
    text = QString::fromUtf8(getWindowParameter()->GetASCII(entryName(), text.toUtf8()).c_str());
    setText(text);
}

void PrefLineEdit::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetASCII(entryName(), text().toUtf8());
}

// --------------------------------------------------------------------

PrefTextEdit::PrefTextEdit(QWidget* parent)
    : QTextEdit(parent)
    , PrefWidget()
{}

PrefTextEdit::~PrefTextEdit() = default;

void PrefTextEdit::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    QString text = this->toPlainText();
    text = QString::fromUtf8(getWindowParameter()->GetASCII(entryName(), text.toUtf8()).c_str());
    setText(text);
}

void PrefTextEdit::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    QString text = this->toPlainText();
    getWindowParameter()->SetASCII(entryName(), text.toUtf8());
}

// --------------------------------------------------------------------

PrefFileChooser::PrefFileChooser(QWidget* parent)
    : FileChooser(parent)
    , PrefWidget()
{}

PrefFileChooser::~PrefFileChooser() = default;

void PrefFileChooser::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    QString txt = QString::fromUtf8(
        getWindowParameter()->GetASCII(entryName(), fileName().toUtf8()).c_str()
    );
    setFileName(txt);
}

void PrefFileChooser::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetASCII(entryName(), fileName().toUtf8());
}

// --------------------------------------------------------------------

PrefComboBox::PrefComboBox(QWidget* parent)
    : QComboBox(parent)
    , PrefWidget()
{
    setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

void PrefComboBox::wheelEvent(QWheelEvent* event)
{
    if (hasFocus()) {
        QComboBox::wheelEvent(event);
    }
    else {
        event->ignore();
    }
}

PrefComboBox::~PrefComboBox() = default;

QMetaType::Type PrefComboBox::getParamType() const
{
    return static_cast<QMetaType::Type>(property("prefType").userType());
}

void PrefComboBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }
    if (!m_Restored) {
        m_Default = currentData();
        m_DefaultText = currentText();
        m_DefaultIndex = currentIndex();
    }
    int index = -1;
    switch (static_cast<int>(getParamType())) {
        case QMetaType::Int:
        case QMetaType::LongLong:
            index = findData(
                static_cast<int>(getWindowParameter()->GetInt(entryName(), m_Default.toInt()))
            );
            break;
        case QMetaType::UInt:
        case QMetaType::ULongLong:
            index = findData(
                static_cast<uint>(getWindowParameter()->GetUnsigned(entryName(), m_Default.toUInt()))
            );
            break;
        case QMetaType::Bool:
            index = findData(getWindowParameter()->GetBool(entryName(), m_Default.toBool()));
            break;
        case QMetaType::Double:
            index = findData(getWindowParameter()->GetFloat(entryName(), m_Default.toDouble()));
            break;
        case QMetaType::QString:
            index = findText(
                QString::fromUtf8(
                    getWindowParameter()->GetASCII(entryName(), m_DefaultText.toUtf8().constData()).c_str()
                )
            );
            break;
        case QMetaType::QByteArray:
            index = findData(QByteArray(
                getWindowParameter()->GetASCII(entryName(), m_Default.toByteArray().constData()).c_str()
            ));
            break;
        default:
            index = getWindowParameter()->GetInt(entryName(), m_DefaultIndex);
            break;
    }
    if (index >= 0 && index < count()) {
        setCurrentIndex(index);
    }
}

void PrefComboBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    switch (static_cast<int>(getParamType())) {
        case QMetaType::Int:
        case QMetaType::LongLong:
            getWindowParameter()->SetInt(entryName(), currentData().toInt());
            break;
        case QMetaType::UInt:
        case QMetaType::ULongLong:
            getWindowParameter()->SetUnsigned(entryName(), currentData().toUInt());
            break;
        case QMetaType::Bool:
            getWindowParameter()->SetBool(entryName(), currentData().toBool());
            break;
        case QMetaType::Double:
            getWindowParameter()->SetFloat(entryName(), currentData().toDouble());
            break;
        case QMetaType::QString:
            getWindowParameter()->SetASCII(entryName(), currentText().toUtf8().constData());
            break;
        case QMetaType::QByteArray:
            getWindowParameter()->SetASCII(entryName(), currentData().toByteArray().constData());
            break;
        default:
            getWindowParameter()->SetInt(entryName(), currentIndex());
            break;
    }
}

// --------------------------------------------------------------------

PrefCheckBox::PrefCheckBox(QWidget* parent)
    : QCheckBox(parent)
    , PrefWidget()
{}

PrefCheckBox::~PrefCheckBox() = default;

void PrefCheckBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    bool enable = getWindowParameter()->GetBool(entryName(), isChecked());
    setChecked(enable);
}

void PrefCheckBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetBool(entryName(), isChecked());
}

// --------------------------------------------------------------------

PrefRadioButton::PrefRadioButton(QWidget* parent)
    : QRadioButton(parent)
    , PrefWidget()
{}

PrefRadioButton::~PrefRadioButton() = default;

void PrefRadioButton::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    bool enable = getWindowParameter()->GetBool(entryName(), isChecked());
    setChecked(enable);
}

void PrefRadioButton::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetBool(entryName(), isChecked());
}

// --------------------------------------------------------------------

PrefSlider::PrefSlider(QWidget* parent)
    : QSlider(parent)
    , PrefWidget()
{}

PrefSlider::~PrefSlider() = default;

void PrefSlider::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    int nVal = getWindowParameter()->GetInt(entryName(), QSlider::value());
    setValue(nVal);
}

void PrefSlider::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetInt(entryName(), (int)value());
}

// --------------------------------------------------------------------

PrefColorButton::PrefColorButton(QWidget* parent)
    : ColorButton(parent)
    , PrefWidget()
{}

PrefColorButton::~PrefColorButton() = default;

void PrefColorButton::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    if (!m_Restored) {
        m_Default = color();
    }

    unsigned int icol = Base::Color::asPackedRGBA<QColor>(m_Default);

    unsigned long lcol = static_cast<unsigned long>(icol);
    lcol = getWindowParameter()->GetUnsigned(entryName(), lcol);
    icol = static_cast<unsigned int>(lcol);
    QColor value = Base::Color::fromPackedRGBA<QColor>(icol);
    if (!this->allowTransparency()) {
        value.setAlpha(0xff);
    }
    setColor(value);
}

void PrefColorButton::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    QColor col = color();
    // (r,g,b,a) with a = 255 (opaque)
    unsigned int icol = Base::Color::asPackedRGBA<QColor>(col);
    unsigned long lcol = static_cast<unsigned long>(icol);
    getWindowParameter()->SetUnsigned(entryName(), lcol);
}

// --------------------------------------------------------------------

PrefUnitSpinBox::PrefUnitSpinBox(QWidget* parent)
    : QuantitySpinBox(parent)
    , PrefWidget()
{
    setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

void PrefUnitSpinBox::wheelEvent(QWheelEvent* event)
{
    if (hasFocus()) {
        QuantitySpinBox::wheelEvent(event);
    }
    else {
        event->ignore();
    }
}

PrefUnitSpinBox::~PrefUnitSpinBox() = default;

void PrefUnitSpinBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    double fVal = (double)getWindowParameter()->GetFloat(entryName(), rawValue());
    setValue(fVal);
}

void PrefUnitSpinBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    double q = rawValue();
    getWindowParameter()->SetFloat(entryName(), q);
}

// --------------------------------------------------------------------

namespace Gui
{
class HistoryList
{
    QStringList list;
    int max_size = 5;

public:
    const QStringList& asStringList() const
    {
        return list;
    }
    int maximumSize() const
    {
        return max_size;
    }
    void setMaximumSize(int num)
    {
        max_size = num;
        while (list.size() > num) {
            list.pop_front();
        }
    }
    void clear()
    {
        list.clear();
    }
    void append(const QString& value)
    {
        if (!list.isEmpty() && list.back() == value) {
            return;
        }
        const auto it = std::ranges::find(list, value);
        if (it != list.end()) {
            list.erase(it);
        }
        else if (list.size() == max_size) {
            list.pop_front();
        }
        list.push_back(value);
    }
};

class PrefQuantitySpinBoxPrivate
{
public:
    HistoryList history;
    bool isSaving = false;

    QByteArray getHistoryGroupName(QByteArray name) const
    {
        return name + "_History";
    }

    void restoreHistory(ParameterGrp::handle hGrp)
    {
        std::vector<std::string> hist = hGrp->GetASCIIs("Hist");
        for (const auto& it : hist) {
            history.append(QString::fromStdString(it));
        }
    }
    void clearHistory(ParameterGrp::handle hGrp)
    {
        std::vector<std::string> hist = hGrp->GetASCIIs("Hist");
        for (const auto& it : hist) {
            hGrp->RemoveASCII(it.c_str());
        }
    }
    void saveHistory(ParameterGrp::handle hGrp)
    {
        clearHistory(hGrp);

        const QStringList& list = history.asStringList();
        for (int i = 0; i < list.size(); i++) {
            QByteArray key("Hist");
            key.append(QByteArray::number(i));
            hGrp->SetASCII(key, list[i].toUtf8());
        }
    }
};
}  // namespace Gui

PrefQuantitySpinBox::PrefQuantitySpinBox(QWidget* parent)
    : QuantitySpinBox(parent)
    , d_ptr(new PrefQuantitySpinBoxPrivate())
{
    setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

PrefQuantitySpinBox::~PrefQuantitySpinBox() = default;

void PrefQuantitySpinBox::contextMenuEvent(QContextMenuEvent* event)
{
    Q_D(PrefQuantitySpinBox);

    QMenu* editMenu = lineEdit()->createStandardContextMenu();
    editMenu->setTitle(tr("Edit"));
    std::unique_ptr<QMenu> menu(new QMenu(QStringLiteral("PrefQuantitySpinBox")));

    menu->addMenu(editMenu);
    menu->addSeparator();

    // data structure to remember actions for values
    QStringList history = d->history.asStringList();
    for (const auto& it : history) {
        QAction* action = menu->addAction(it);
        action->setProperty("history_value", it);
    }

    // add the save value portion of the menu
    menu->addSeparator();
    QAction* saveValueAction = menu->addAction(tr("Save Value"));
    QAction* clearListAction = menu->addAction(tr("Clear List"));
    clearListAction->setDisabled(history.empty());

    // call the menu
    QAction* userAction = menu->exec(event->globalPos());

    // look what the user has chosen
    if (userAction == saveValueAction) {
        pushToHistory(this->text());
    }
    else if (userAction == clearListAction) {
        d->history.clear();
    }
    else if (userAction) {
        QVariant prop = userAction->property("history_value");
        if (prop.isValid()) {
            lineEdit()->setText(prop.toString());
        }
    }
}

void PrefQuantitySpinBox::wheelEvent(QWheelEvent* event)
{
    if (hasFocus()) {
        QuantitySpinBox::wheelEvent(event);
    }
    else {
        event->ignore();
    }
}

void PrefQuantitySpinBox::restorePreferences()
{
    Q_D(PrefQuantitySpinBox);

    // Do not restore values while saving them
    if (d->isSaving) {
        return;
    }

    if (getWindowParameter().isNull() || entryName().isEmpty()) {
        failedToRestore(objectName());
        return;
    }

    QString text = this->text();
    text = QString::fromUtf8(getWindowParameter()->GetASCII(entryName(), text.toUtf8()).c_str());
    lineEdit()->setText(text);

    // Restore history
    auto hGrp = getWindowParameter()->GetGroup(d->getHistoryGroupName(entryName()));
    d->restoreHistory(hGrp);
}

void PrefQuantitySpinBox::savePreferences()
{
    Q_D(PrefQuantitySpinBox);
    if (getWindowParameter().isNull() || entryName().isEmpty()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetASCII(entryName(), text().toUtf8());

    // Save history
    auto hGrp = getWindowParameter()->GetGroup(d->getHistoryGroupName(entryName()));
    d->saveHistory(hGrp);
}

void PrefQuantitySpinBox::pushToHistory(const QString& value)
{
    Q_D(PrefQuantitySpinBox);
    d->history.append(value.isEmpty() ? this->text() : value);

    Base::StateLocker lock(d->isSaving);
    onSave();
}

QStringList PrefQuantitySpinBox::getHistory() const
{
    Q_D(const PrefQuantitySpinBox);
    return d->history.asStringList();
}

void PrefQuantitySpinBox::setToLastUsedValue()
{
    QStringList hist = getHistory();
    if (!hist.empty()) {
        lineEdit()->setText(hist.front());
    }
}

int PrefQuantitySpinBox::historySize() const
{
    Q_D(const PrefQuantitySpinBox);
    return d->history.maximumSize();
}

void PrefQuantitySpinBox::setHistorySize(int i)
{
    Q_D(PrefQuantitySpinBox);
    d->history.setMaximumSize(i);
}

// --------------------------------------------------------------------

PrefFontBox::PrefFontBox(QWidget* parent)
    : QFontComboBox(parent)
    , PrefWidget()
{}

PrefFontBox::~PrefFontBox() = default;

void PrefFontBox::restorePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToRestore(objectName());
        return;
    }

    QFont currFont = currentFont();  // QFont from selector widget
    QString currName = currFont.family();

    std::string prefName
        = getWindowParameter()->GetASCII(entryName(), currName.toUtf8());  // font name from cfg file

    currFont.setFamily(QString::fromStdString(prefName));
    setCurrentFont(currFont);  // set selector widget to name from cfg file
}

void PrefFontBox::savePreferences()
{
    if (getWindowParameter().isNull()) {
        failedToSave(objectName());
        return;
    }

    QFont currFont = currentFont();
    QString currName = currFont.family();
    getWindowParameter()->SetASCII(entryName(), currName.toUtf8());
}

// --------------------------------------------------------------------

PrefCheckableGroupBox::PrefCheckableGroupBox(QWidget* parent)
    : QGroupBox(parent)
    , PrefWidget()
{}

PrefCheckableGroupBox::~PrefCheckableGroupBox() = default;

void PrefCheckableGroupBox::restorePreferences()
{
    if (getWindowParameter().isNull() || entryName().isEmpty()) {
        failedToRestore(objectName());
        return;
    }

    // Default value is the current state of the checkbox (usually from .ui on first load)
    bool defaultValueInUi = isChecked();
    bool actualValue = getWindowParameter()->GetBool(entryName(), defaultValueInUi);
    setChecked(actualValue);
}

void PrefCheckableGroupBox::savePreferences()
{
    if (getWindowParameter().isNull() || entryName().isEmpty()) {
        failedToSave(objectName());
        return;
    }

    getWindowParameter()->SetBool(entryName(), isChecked());
}

#include "moc_PrefWidgets.cpp"
