//    Copyright (C) 2020-2022 Jakub Melka
//
//    This file is part of PDF4QT.
//
//    PDF4QT is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    with the written consent of the copyright owner, any later version.
//
//    PDF4QT 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 Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.

#include "pdfobjecteditorwidget.h"
#include "pdfobjecteditorwidget_impl.h"
#include "pdfdocumentbuilder.h"
#include "pdfencoding.h"
#include "pdfwidgetutils.h"

#include <QTabWidget>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QSpacerItem>
#include <QLabel>
#include <QComboBox>
#include <QLineEdit>
#include <QTextBrowser>
#include <QPushButton>
#include <QDateTimeEdit>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>

#include "pdfdbgheap.h"

namespace pdf
{

PDFObjectEditorWidget::PDFObjectEditorWidget(EditObjectType type, QWidget* parent) :
    BaseClass(parent),
    m_mapper(nullptr),
    m_tabWidget(nullptr)
{
    QVBoxLayout* layout = new QVBoxLayout(this);
    m_tabWidget = new QTabWidget(this);
    layout->addWidget(m_tabWidget);

    PDFObjectEditorAbstractModel* model = nullptr;
    switch (type)
    {
        case EditObjectType::Annotation:
            model = new PDFObjectEditorAnnotationsModel(this);
            break;

        default:
            Q_ASSERT(false);
            break;
    }
    Q_ASSERT(model);

    m_mapper = new PDFObjectEditorWidgetMapper(model, this);
    m_mapper->initialize(m_tabWidget);
}

void PDFObjectEditorWidget::setObject(PDFObject object)
{
    m_mapper->setObject(object);
}

PDFObject PDFObjectEditorWidget::getObject()
{
    return m_mapper->getObject();
}

PDFObjectEditorWidgetMapper::PDFObjectEditorWidgetMapper(PDFObjectEditorAbstractModel* model, QObject* parent) :
    BaseClass(parent),
    m_model(model),
    m_tabWidget(nullptr),
    m_isCommitingDisabled(false)
{
    connect(model, &PDFObjectEditorAbstractModel::editedObjectChanged, this, &PDFObjectEditorWidgetMapper::onEditedObjectChanged);
}

void PDFObjectEditorWidgetMapper::initialize(QTabWidget* tabWidget)
{
    size_t attributeCount = m_model->getAttributeCount();

    for (size_t i = 0; i < attributeCount; ++i)
    {
        // Unmapped attributes are ignored
        if (!m_model->queryAttribute(i, PDFObjectEditorAbstractModel::Question::IsMapped))
        {
            continue;
        }

        QString categoryName = m_model->getAttributeCategory(i);
        QString subcategoryName = m_model->getAttributeSubcategory(i);

        Category* category = getOrCreateCategory(categoryName);
        Subcategory* subcategory = category->getOrCreateSubcategory(subcategoryName);
        subcategory->attributes.push_back(i);
    }

    // Create GUI
    for (Category& category : m_categories)
    {
        category.page = new QWidget(tabWidget);
        tabWidget->addTab(category.page, category.name);
        category.page->setLayout(new QVBoxLayout());

        // Create subcategory GUI
        for (Subcategory& subcategory : category.subcategories)
        {
            QGroupBox* groupBox = new QGroupBox(category.page);
            groupBox->setTitle(subcategory.name);
            category.page->layout()->addWidget(groupBox);

            QGridLayout* layout = new QGridLayout();
            groupBox->setLayout(layout);
            layout->setColumnStretch(0, 1);
            layout->setColumnStretch(1, 2);

            for (size_t attribute : subcategory.attributes)
            {
                createMappedAdapter(groupBox, layout, attribute);
            }
        }

        QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
        category.page->layout()->addItem(spacer);
    }

    m_tabWidget = tabWidget;
}

void PDFObjectEditorWidgetMapper::setObject(PDFObject object)
{
    m_model->setEditedObject(object);
}

PDFObject PDFObjectEditorWidgetMapper::getObject()
{
    return m_model->getEditedObject();
}

void PDFObjectEditorWidgetMapper::loadWidgets()
{
    PDFTemporaryValueChange guard(&m_isCommitingDisabled, true);
    for (auto& adapterItem : m_adapters)
    {
        const size_t attribute = adapterItem.first;
        PDFObjectEditorMappedWidgetAdapter* adapter = adapterItem.second;

        if (m_model->queryAttribute(attribute, PDFObjectEditorAbstractModel::Question::IsSelector))
        {
            adapter->setValue(PDFObject::createBool(m_model->getSelectorValue(attribute)));
        }
        else
        {
            PDFObject object = m_model->getValue(attribute, true);
            if (object.isNull())
            {
                object = m_model->getDefaultValue(attribute);
            }
            adapter->setValue(object);
        }

        adapter->update();
    }

    std::vector<const Category*> insertedCategories;
    insertedCategories.reserve(m_categories.size());
    for (const Category& category : m_categories)
    {
        const bool isVisible = isCategoryVisible(category);
        if (!isVisible)
        {
            m_tabWidget->removeTab(m_tabWidget->indexOf(category.page));
        }
        else
        {
            insertedCategories.push_back(&category);
        }
    }

    for (auto it = insertedCategories.cbegin(); it != insertedCategories.cend(); ++it)
    {
        const Category* category = *it;
        if (m_tabWidget->indexOf(category->page) != -1)
        {
            // Jakub Melka: category is present already in the tab widget
            continue;
        }

        // Find index onto which we will insert the tab page. We want to preserve
        // the order of categories, so we must insert tabs carefully.
        int insertPosition = -1;
        for (auto it2 = std::next(it); it2 != insertedCategories.cend(); ++it2)
        {
            insertPosition = m_tabWidget->indexOf((*it2)->page);
            if (insertPosition != -1)
            {
                break;
            }
        }

        m_tabWidget->insertTab(insertPosition, category->page,  category->name);
    }
}

void PDFObjectEditorWidgetMapper::onEditedObjectChanged()
{
    if (!m_isCommitingDisabled)
    {
        loadWidgets();
    }
}

void PDFObjectEditorWidgetMapper::onCommitRequested(size_t attribute)
{
    if (m_isCommitingDisabled)
    {
        // Jakub Melka: Commit is disabled
        return;
    }

    PDFTemporaryValueChange guard(&m_isCommitingDisabled, true);

    // We will create a new object using several steps. We merge
    // the resulting object with old one (so data, that are not
    // edited, will remain). Steps:
    //   1) If object is selector attribute, and it is turned on,
    //      set default values to selector's attributes and set
    //      selector value.
    //   2) If object is selector attributem and it is turned off,
    //      set null values to selector's attributes and set selector
    //      value.
    //   3) For ordinary attributes, commit new value of the attribute
    //      to the object.
    //   4) Iterate each attribute, and if it doesn't exist, then set
    //      it to null in the object. If it exists, and it is constant,
    //      then add constant to the object.
    //   5) Set final object as new object

    PDFObject object = m_model->getEditedObject();

    // Steps 1) and 2)
    if (m_model->queryAttribute(attribute, PDFObjectEditorAbstractModel::Question::IsSelector))
    {
        const bool isChecked = m_adapters[attribute]->getValue().getBool();
        m_model->setSelectorValue(attribute, isChecked);

        std::vector<size_t> dependentAttributes = m_model->getSelectorDependentAttributes(attribute);
        if (isChecked)
        {
            for (size_t dependentAttribute : dependentAttributes)
            {
                object = m_model->writeAttributeValueToObject(dependentAttribute, object, m_model->getDefaultValue(dependentAttribute));
            }
        }
        else
        {
            for (size_t dependentAttribute : dependentAttributes)
            {
                object = m_model->writeAttributeValueToObject(dependentAttribute, object, PDFObject());
            }
        }
    }
    else
    {
        // Step 3) - normal attribute
        object = m_model->writeAttributeValueToObject(attribute, object, m_adapters[attribute]->getValue());
    }

    // Step 4)
    for (size_t i = 0; i < m_model->getAttributeCount(); ++i)
    {
        if (!m_model->queryAttribute(i, PDFObjectEditorAbstractModel::Question::IsPersisted))
        {
            continue;
        }

        if (!m_model->queryAttribute(i, PDFObjectEditorAbstractModel::Question::HasSimilarAttribute))
        {
            object = m_model->writeAttributeValueToObject(i, object, PDFObject());
        }
        else if (m_model->getAttributeType(i) == ObjectEditorAttributeType::Constant)
        {
            object = m_model->writeAttributeValueToObject(i, object, m_model->getDefaultValue(i));
        }
    }

    // Step 5)
    m_model->setEditedObject(object);

    loadWidgets();
}

void PDFObjectEditorWidgetMapper::createMappedAdapter(QGroupBox* groupBox, QGridLayout* layout, size_t attribute)
{
    auto setAdapter = [this, attribute](PDFObjectEditorMappedWidgetAdapter* adapter)
    {
        Q_ASSERT(!m_adapters.count(attribute));
        m_adapters[attribute] = adapter;
    };

    ObjectEditorAttributeType type = m_model->getAttributeType(attribute);
    switch (type)
    {
        case ObjectEditorAttributeType::Type:
        case ObjectEditorAttributeType::ComboBox:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QComboBox* comboBox = new QComboBox(groupBox);
            layout->addWidget(label, row, 0);
            layout->addWidget(comboBox, row, 1);

            setAdapter(new PDFObjectEditorMappedComboBoxAdapter(label, comboBox, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::TextLine:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QLineEdit* lineEdit = new QLineEdit(groupBox);
            layout->addWidget(label, row, 0);
            layout->addWidget(lineEdit, row, 1);

            setAdapter(new PDFObjectEditorMappedLineEditAdapter(label, lineEdit, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::TextBrowser:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QTextBrowser* textBrowser = new QTextBrowser(groupBox);
            layout->addWidget(label, row, 0, 1, -1);
            layout->addWidget(textBrowser, row + 1, 0, 1, -1);

            setAdapter(new PDFObjectEditorMappedTextBrowserAdapter(label, textBrowser, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::Rectangle:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QPushButton* pushButton = new QPushButton(groupBox);
            pushButton->setText(tr("Rectangle"));
            pushButton->setFlat(true);

            layout->addWidget(label, row, 0);
            layout->addWidget(pushButton, row, 1);

            setAdapter(new PDFObjectEditorMappedRectangleAdapter(label, pushButton, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::DateTime:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QDateTimeEdit* dateTimeEdit = new QDateTimeEdit(groupBox);

            layout->addWidget(label, row, 0);
            layout->addWidget(dateTimeEdit, row, 1);

            setAdapter(new PDFObjectEditorMappedDateTimeAdapter(label, dateTimeEdit, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::Flags:
        {
            int row = layout->rowCount();
            int column = 0;

            std::vector<std::pair<uint32_t, QCheckBox*>> flagCheckboxes;
            const PDFObjectEditorModelAttributeEnumItems& flagItems = m_model->getAttributeEnumItems(attribute);
            flagCheckboxes.reserve(flagItems.size());

            for (const PDFObjectEditorModelAttributeEnumItem& flagItem : flagItems)
            {
                if (column == 2)
                {
                    row++;
                    column = 0;
                }

                QCheckBox* checkBox = new QCheckBox(flagItem.name, groupBox);
                layout->addWidget(checkBox, row, column);
                flagCheckboxes.emplace_back(flagItem.flags, checkBox);

                ++column;
            }

            setAdapter(new PDFObjectEditorMappedFlagsAdapter(qMove(flagCheckboxes), m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::Selector:
        case ObjectEditorAttributeType::Boolean:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QCheckBox* checkBox = new QCheckBox(groupBox);

            layout->addWidget(label, row, 0);
            layout->addWidget(checkBox, row, 1);

            setAdapter(new PDFObjectEditorMappedCheckBoxAdapter(label, checkBox, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::Color:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QComboBox* comboBox = new QComboBox(groupBox);

            layout->addWidget(label, row, 0);
            layout->addWidget(comboBox, row, 1);

            setAdapter(new PDFObjectEditorMappedColorAdapter(label, comboBox, m_model, attribute, this));
            break;
        }

        case ObjectEditorAttributeType::Double:
        {
            int row = layout->rowCount();

            QLabel* label = new QLabel(groupBox);
            QDoubleSpinBox* spinBox = new QDoubleSpinBox(groupBox);

            QVariant minimumValue = m_model->getMinimumValue(attribute);
            if (minimumValue.isValid())
            {
                spinBox->setMinimum(minimumValue.toDouble());
            }
            QVariant maximumValue = m_model->getMaximumValue(attribute);
            if (maximumValue.isValid())
            {
                spinBox->setMaximum(maximumValue.toDouble());
            }

            layout->addWidget(label, row, 0);
            layout->addWidget(spinBox, row, 1);

            setAdapter(new PDFObjectEditorMappedDoubleAdapter(label, spinBox, m_model, attribute, this));
            break;
        }

        default:
            Q_ASSERT(false);
    }

    connect(m_adapters[attribute], &PDFObjectEditorMappedWidgetAdapter::commitRequested, this, &PDFObjectEditorWidgetMapper::onCommitRequested);
}

PDFObjectEditorWidgetMapper::Category* PDFObjectEditorWidgetMapper::getOrCreateCategory(QString categoryName)
{
    auto categoryIt = std::find_if(m_categories.begin(), m_categories.end(), [&categoryName](const auto& category) { return category.name == categoryName; });
    if (categoryIt != m_categories.end())
    {
        return &*categoryIt;
    }

    Category category;
    category.name = qMove(categoryName);
    m_categories.emplace_back(qMove(category));
    return &m_categories.back();
}

bool PDFObjectEditorWidgetMapper::isCategoryVisible(const Category& category) const
{
    for (const Subcategory& subcategory : category.subcategories)
    {
        for (size_t attribute : subcategory.attributes)
        {
            if (m_model->queryAttribute(attribute, PDFObjectEditorAbstractModel::Question::HasAttribute))
            {
                return true;
            }
        }
    }

    return false;
}

PDFObjectEditorWidgetMapper::Subcategory* PDFObjectEditorWidgetMapper::Category::getOrCreateSubcategory(QString subcategoryName)
{
    auto subcategoryIt = std::find_if(subcategories.begin(), subcategories.end(), [&subcategoryName](const auto& subcategory) { return subcategory.name == subcategoryName; });
    if (subcategoryIt != subcategories.end())
    {
        return &*subcategoryIt;
    }

    Subcategory subcategory;
    subcategory.name = qMove(subcategoryName);
    subcategories.emplace_back(qMove(subcategory));
    return &subcategories.back();
}

PDFObjectEditorMappedWidgetAdapter::PDFObjectEditorMappedWidgetAdapter(PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent) :
    BaseClass(parent),
    m_model(model),
    m_attribute(attribute)
{

}

void PDFObjectEditorMappedWidgetAdapter::initLabel(QLabel* label)
{
    label->setText(m_model->getAttributeName(m_attribute));
}

PDFObjectEditorMappedComboBoxAdapter::PDFObjectEditorMappedComboBoxAdapter(QLabel* label,
                                                                           QComboBox* comboBox,
                                                                           PDFObjectEditorAbstractModel* model,
                                                                           size_t attribute,
                                                                           QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_comboBox(comboBox)
{
    initLabel(label);

    comboBox->clear();
    for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(attribute))
    {
        comboBox->addItem(item.name, item.flags);
    }

    connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedComboBoxAdapter::getValue() const
{
    QVariant currentData = m_comboBox->currentData();
    if (!currentData.isValid())
    {
        return PDFObject();
    }

    const uint32_t flags = currentData.toUInt();
    for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(m_attribute))
    {
        if (item.flags == flags)
        {
            return item.value;
        }
    }

    return PDFObject();
}

void PDFObjectEditorMappedComboBoxAdapter::setValue(PDFObject object)
{
    for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(m_attribute))
    {
        if (item.value == object)
        {
            m_comboBox->setCurrentIndex(m_comboBox->findData(int(item.flags)));
            return;
        }
    }

    m_comboBox->setCurrentIndex(-1);
}

void PDFObjectEditorMappedComboBoxAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_comboBox->setHidden(!isVisible);
    m_comboBox->setEnabled(enabled && !readonly);
}

PDFObjectEditorMappedLineEditAdapter::PDFObjectEditorMappedLineEditAdapter(QLabel* label,
                                                                           QLineEdit* lineEdit,
                                                                           PDFObjectEditorAbstractModel* model,
                                                                           size_t attribute,
                                                                           QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_lineEdit(lineEdit)
{
    initLabel(label);
    lineEdit->setClearButtonEnabled(true);

    connect(lineEdit, &QLineEdit::editingFinished, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedLineEditAdapter::getValue() const
{
    PDFObjectFactory factory;
    factory << m_lineEdit->text();
    return factory.takeObject();
}

void PDFObjectEditorMappedLineEditAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    m_lineEdit->setText(loader.readTextString(object, QString()));
}

void PDFObjectEditorMappedLineEditAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_lineEdit->setHidden(!isVisible);
    m_lineEdit->setEnabled(enabled);
    m_lineEdit->setReadOnly(readonly);
}

PDFObjectEditorMappedTextBrowserAdapter::PDFObjectEditorMappedTextBrowserAdapter(QLabel* label,
                                                                                 QTextBrowser* textBrowser,
                                                                                 PDFObjectEditorAbstractModel* model,
                                                                                 size_t attribute,
                                                                                 QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_textBrowser(textBrowser)
{
    initLabel(label);
    textBrowser->setUndoRedoEnabled(true);
    textBrowser->setTextInteractionFlags(Qt::TextEditorInteraction);

    connect(textBrowser, &QTextBrowser::textChanged, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedTextBrowserAdapter::getValue() const
{
    PDFObjectFactory factory;
    factory << m_textBrowser->toPlainText();
    return factory.takeObject();
}

void PDFObjectEditorMappedTextBrowserAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    QString text = loader.readTextString(object, QString());
    if (text != m_textBrowser->toPlainText())
    {
        m_textBrowser->setText(text);
    }
}

void PDFObjectEditorMappedTextBrowserAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_textBrowser->setHidden(!isVisible);
    m_textBrowser->setEnabled(enabled);
    m_textBrowser->setReadOnly(readonly);
}

PDFObjectEditorMappedRectangleAdapter::PDFObjectEditorMappedRectangleAdapter(QLabel* label,
                                                                             QPushButton* pushButton,
                                                                             PDFObjectEditorAbstractModel* model,
                                                                             size_t attribute,
                                                                             QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_pushButton(pushButton)
{
    initLabel(label);
}

PDFObject PDFObjectEditorMappedRectangleAdapter::getValue() const
{
    return m_rectangle;
}

void PDFObjectEditorMappedRectangleAdapter::setValue(PDFObject object)
{
    m_rectangle = qMove(object);
}

void PDFObjectEditorMappedRectangleAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_pushButton->setHidden(!isVisible);
    m_pushButton->setEnabled(enabled && !readonly);
}

PDFObjectEditorMappedDateTimeAdapter::PDFObjectEditorMappedDateTimeAdapter(QLabel* label,
                                                                           QDateTimeEdit* dateTimeEdit,
                                                                           PDFObjectEditorAbstractModel* model,
                                                                           size_t attribute,
                                                                           QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_dateTimeEdit(dateTimeEdit)
{
    initLabel(label);

    connect(dateTimeEdit, &QDateTimeEdit::editingFinished, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedDateTimeAdapter::getValue() const
{
    QDateTime dateTime = m_dateTimeEdit->dateTime();

    if (dateTime.isValid())
    {
        return PDFObject::createString(PDFEncoding::convertDateTimeToString(dateTime));
    }

    return PDFObject();
}

void PDFObjectEditorMappedDateTimeAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    QByteArray string = loader.readString(object);

    QDateTime dateTime = PDFEncoding::convertToDateTime(string);
    m_dateTimeEdit->setDateTime(dateTime);
}

void PDFObjectEditorMappedDateTimeAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_dateTimeEdit->setHidden(!isVisible);
    m_dateTimeEdit->setEnabled(enabled);
    m_dateTimeEdit->setReadOnly(readonly);
}

PDFObjectEditorMappedFlagsAdapter::PDFObjectEditorMappedFlagsAdapter(std::vector<std::pair<uint32_t, QCheckBox*>> flagCheckBoxes,
                                                                     PDFObjectEditorAbstractModel* model,
                                                                     size_t attribute,
                                                                     QObject* parent) :
    BaseClass(model, attribute, parent),
    m_flagCheckBoxes(qMove(flagCheckBoxes))
{
    for (const auto& item : m_flagCheckBoxes)
    {
        QCheckBox* checkBox = item.second;
        connect(checkBox, &QCheckBox::clicked, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
    }
}

PDFObject PDFObjectEditorMappedFlagsAdapter::getValue() const
{
    uint32_t flags = 0;

    for (const auto& item : m_flagCheckBoxes)
    {
        if (item.second->isChecked())
        {
            flags = flags | item.first;
        }
    }

    return PDFObject::createInteger(PDFInteger(flags));
}

void PDFObjectEditorMappedFlagsAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    uint32_t flags = static_cast<uint32_t>(loader.readInteger(object, 0));

    for (const auto& item : m_flagCheckBoxes)
    {
        const bool checked = flags & item.first;
        item.second->setChecked(checked);
    }
}

void PDFObjectEditorMappedFlagsAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool enableCheckbox = enabled && !readonly;
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    for (const auto& item : m_flagCheckBoxes)
    {
        item.second->setHidden(!isVisible);
        item.second->setEnabled(enableCheckbox);
    }
}

PDFObjectEditorMappedCheckBoxAdapter::PDFObjectEditorMappedCheckBoxAdapter(QLabel* label,
                                                                           QCheckBox* checkBox,
                                                                           PDFObjectEditorAbstractModel* model,
                                                                           size_t attribute,
                                                                           QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_checkBox(checkBox)
{
    initLabel(label);
    connect(checkBox, &QCheckBox::clicked, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedCheckBoxAdapter::getValue() const
{
    return PDFObject::createBool(m_checkBox->isChecked());
}

void PDFObjectEditorMappedCheckBoxAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    m_checkBox->setChecked(loader.readBoolean(object, false));
}

void PDFObjectEditorMappedCheckBoxAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_checkBox->setHidden(!isVisible);
    m_checkBox->setEnabled(enabled && !readonly);
}

PDFObjectEditorMappedDoubleAdapter::PDFObjectEditorMappedDoubleAdapter(QLabel* label,
                                                                       QDoubleSpinBox* spinBox,
                                                                       PDFObjectEditorAbstractModel* model,
                                                                       size_t attribute,
                                                                       QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_spinBox(spinBox)
{
    initLabel(label);

    connect(spinBox, &QDoubleSpinBox::editingFinished, this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedDoubleAdapter::getValue() const
{
    return PDFObject::createReal(m_spinBox->value());
}

void PDFObjectEditorMappedDoubleAdapter::setValue(PDFObject object)
{
    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    const PDFReal value = loader.readNumber(object, (m_spinBox->minimum() + m_spinBox->maximum()) * 0.5);
    m_spinBox->setValue(value);
}

void PDFObjectEditorMappedDoubleAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_spinBox->setHidden(!isVisible);
    m_spinBox->setEnabled(enabled);
    m_spinBox->setReadOnly(readonly);
}

PDFObjectEditorMappedColorAdapter::PDFObjectEditorMappedColorAdapter(QLabel* label,
                                                                     QComboBox* comboBox,
                                                                     PDFObjectEditorAbstractModel* model,
                                                                     size_t attribute,
                                                                     QObject* parent) :
    BaseClass(model, attribute, parent),
    m_label(label),
    m_comboBox(comboBox)
{
    initLabel(label);

    comboBox->clear();

    for (QString colorName : QColor::colorNames())
    {
        QColor color(colorName);
        comboBox->addItem(getIconForColor(color), colorName, color);
    }

    connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this, attribute](){ Q_EMIT commitRequested(attribute); });
}

PDFObject PDFObjectEditorMappedColorAdapter::getValue() const
{
    QColor color = m_comboBox->currentData().value<QColor>();
    if (!color.isValid())
    {
        color = Qt::black;
    }

    PDFObjectFactory factory;
    factory << color;
    return factory.takeObject();
}

void PDFObjectEditorMappedColorAdapter::setValue(PDFObject object)
{
    QColor color = Qt::black;

    PDFDocumentDataLoaderDecorator loader(m_model->getStorage());
    std::vector<PDFReal> colors = loader.readNumberArray(object, { });

    if (colors.size() == 3)
    {
        const PDFReal red = qBound(0.0, colors[0], 1.0);
        const PDFReal green = qBound(0.0, colors[1], 1.0);
        const PDFReal blue = qBound(0.0, colors[2], 1.0);
        color = QColor::fromRgbF(red, green, blue);
    }
    else if (colors.size() == 1)
    {
        const PDFReal gray = qBound(0.0, colors[0], 1.0);
        color = QColor::fromRgbF(gray, gray, gray);
    }

    int index = m_comboBox->findData(color);
    if (index != -1)
    {
        m_comboBox->setCurrentIndex(index);
    }
    else
    {
        m_comboBox->addItem(getIconForColor(color), tr("custom"), color);
        m_comboBox->setCurrentIndex(m_comboBox->count() - 1);
    }
}

void PDFObjectEditorMappedColorAdapter::update()
{
    const bool enabled = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::HasAttribute);
    const bool readonly = !m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsAttributeEditable);
    const bool isVisible = m_model->queryAttribute(m_attribute, PDFObjectEditorAbstractModel::Question::IsVisible);

    m_label->setHidden(!isVisible);
    m_comboBox->setHidden(!isVisible);
    m_comboBox->setEnabled(enabled && !readonly);
}

QIcon PDFObjectEditorMappedColorAdapter::getIconForColor(QColor color) const
{
    QIcon icon;

    QSize iconSize = PDFWidgetUtils::scaleDPI(m_comboBox, QSize(16, 16));

    QPixmap pixmap(iconSize.width(), iconSize.height());
    pixmap.fill(color);
    icon.addPixmap(pixmap);

    return icon;
}

PDFEditObjectDialog::PDFEditObjectDialog(EditObjectType type, QWidget* parent) :
    BaseClass(parent),
    m_widget(nullptr),
    m_buttonBox(nullptr)
{
    QVBoxLayout* layout = new QVBoxLayout(this);

    switch (type)
    {
        case EditObjectType::Annotation:
            setWindowTitle(tr("Edit Annotation"));
            break;

        default:
            Q_ASSERT(false);
            break;
    }

    m_widget = new PDFObjectEditorWidget(type, this);
    layout->addWidget(m_widget);

    m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
    layout->addWidget(m_buttonBox);

    connect(m_buttonBox, &QDialogButtonBox::accepted, this, &PDFEditObjectDialog::accept);
    connect(m_buttonBox, &QDialogButtonBox::rejected, this, &PDFEditObjectDialog::reject);

    setMinimumSize(PDFWidgetUtils::scaleDPI(this, QSize(480, 320)));
    pdf::PDFWidgetUtils::style(this);
}

void PDFEditObjectDialog::setObject(PDFObject object)
{
    m_widget->setObject(qMove(object));
}

PDFObject PDFEditObjectDialog::getObject()
{
    return m_widget->getObject();
}

} // namespace pdf
