/*********
*
* In the name of the Father, and of the Son, and of the Holy Spirit.
*
* This file is part of BibleTime's source code, http://www.bibletime.info/
*
* Copyright 1999-2020 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License
* version 2.0.
*
**********/

#include "crangechooserdialog.h"

#include <QDialogButtonBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QListWidgetItem>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include "../../backend/config/btconfig.h"
#include "../../backend/managers/cswordbackend.h"
#include "../../util/btassert.h"
#include "../../util/btconnect.h"
#include "../messagedialog.h"

// Sword includes:
#include <versekey.h>
#include <listkey.h>

namespace Search {

CRangeChooserDialog::CRangeChooserDialog(const QStringList& scopeModules, QWidget *parentDialog)
    : QDialog(parentDialog),
      m_scopeModules(scopeModules)
{
    initView();
    initConnections();

    retranslateUi();

    // Add the existing scopes
    BtConfig::StringMap map = btConfig().getSearchScopesForCurrentLocale(scopeModules);
    BtConfig::StringMap::Iterator it;
    for (it = map.begin(); it != map.end(); ++it) {
        new RangeItem(it.key(), it.value(), m_rangeList);
    }
    resetEditControls();
}

void CRangeChooserDialog::initView() {
    m_rangeList = new QListWidget(this);
    m_rangeListLabel = new QLabel(this);
    m_rangeListLabel->setBuddy(m_rangeList);

    m_newRangeButton = new QPushButton(this);
    m_deleteRangeButton = new QPushButton(this);

    m_nameEdit = new QLineEdit(this);
    m_nameEditLabel = new QLabel(this);
    m_nameEditLabel->setBuddy(m_nameEdit);

    m_rangeEdit = new QTextEdit(this);
    m_rangeEditLabel = new QLabel(this);
    m_rangeEditLabel->setBuddy(m_rangeEdit);

    m_resultList = new QListWidget(this);
    m_resultListLabel = new QLabel(this);
    m_resultListLabel->setBuddy(m_resultList);

    QFrame *hSeparatorLine = new QFrame(this);
    hSeparatorLine->setFrameShape(QFrame::HLine);
    hSeparatorLine->setFrameShadow(QFrame::Sunken);

    m_buttonBox = new QDialogButtonBox(this);
    m_buttonBox->setOrientation(Qt::Horizontal);
    m_buttonBox->setStandardButtons(QDialogButtonBox::Ok
                                    | QDialogButtonBox::Cancel
                                    | QDialogButtonBox::RestoreDefaults);
    message::prepareDialogBox(m_buttonBox);

    QHBoxLayout *rangeButtonsLayout = new QHBoxLayout();
    rangeButtonsLayout->addWidget(m_newRangeButton);
    rangeButtonsLayout->addWidget(m_deleteRangeButton);

    QVBoxLayout* rangeListLayout = new QVBoxLayout;
    rangeListLayout->addWidget(m_rangeListLabel);
    rangeListLayout->addWidget(m_rangeList);
    rangeListLayout->addLayout(rangeButtonsLayout);

    QHBoxLayout* nameEditLayout = new QHBoxLayout();
    nameEditLayout->addWidget(m_nameEditLabel);
    nameEditLayout->addWidget(m_nameEdit);

    QVBoxLayout* rangeEditLayout = new QVBoxLayout();
    rangeEditLayout->addLayout(nameEditLayout);
    rangeEditLayout->addWidget(m_rangeEditLabel);
    rangeEditLayout->addWidget(m_rangeEdit);
    rangeEditLayout->addWidget(m_resultListLabel);
    rangeEditLayout->addWidget(m_resultList);

    QHBoxLayout *topLayout = new QHBoxLayout;
    topLayout->addLayout(rangeListLayout);
    topLayout->addLayout(rangeEditLayout);

    QVBoxLayout *vboxLayout = new QVBoxLayout(this);
    vboxLayout->addLayout(topLayout);
    vboxLayout->addWidget(hSeparatorLine);
    vboxLayout->addWidget(m_buttonBox);
}

void CRangeChooserDialog::initConnections() {
    BT_CONNECT(m_rangeList, SIGNAL(currentItemChanged(QListWidgetItem *,
                                                      QListWidgetItem *)),
               this,        SLOT(selectedRangeChanged(QListWidgetItem *,
                                                      QListWidgetItem *)));
    BT_CONNECT(m_nameEdit, SIGNAL(textEdited(QString)),
               this,       SLOT(nameEditTextChanged(QString)));
    BT_CONNECT(m_rangeEdit, SIGNAL(textChanged()),
               this,        SLOT(updateResultList()));

    // Connect buttons:
    BT_CONNECT(m_buttonBox, SIGNAL(accepted()),
               this,        SLOT(accept()));
    BT_CONNECT(m_buttonBox, SIGNAL(rejected()),
               this,        SLOT(reject()));
    BT_CONNECT(m_newRangeButton, SIGNAL(clicked()),
               this,             SLOT(addNewRange()));
    BT_CONNECT(m_deleteRangeButton, SIGNAL(clicked()),
               this,                SLOT(deleteCurrentRange()));
    QPushButton * defaultsButton = m_buttonBox->button(QDialogButtonBox::RestoreDefaults);
    BT_CONNECT(defaultsButton, SIGNAL(clicked()),
               this,           SLOT(restoreDefaults()));
}

void CRangeChooserDialog::retranslateUi() {
    setWindowTitle(tr("Setup Search Scopes"));

    m_rangeListLabel->setText(tr("S&earch range:"));
    m_rangeList->setToolTip(tr("Select a scope from the list to edit the search"
                               "ranges"));

    m_newRangeButton->setText(tr("&Add new scope"));
    m_newRangeButton->setToolTip(tr("Add a new search scope. First enter an "
                                    "appropriate name, then edit the search "
                                    "ranges."));

    m_deleteRangeButton->setText(tr("Delete current &scope"));
    m_deleteRangeButton->setToolTip(tr("Delete the selected search scope"));

    m_nameEditLabel->setText(tr("&Name:"));
    m_nameEdit->setToolTip(tr("Change the name of the selected search scope"));

    m_rangeEditLabel->setText(tr("Edi&t current range:"));
    m_rangeEdit->setToolTip(tr("Change the search ranges of the selected search"
                               "scope item. Have a look at the predefined "
                               "search scopes to see how search ranges are "
                               "constructed."));

    m_resultListLabel->setText(tr("Parsed search range:"));
    m_resultList->setToolTip(tr("The search ranges which will be used for the "
                                "search, parsed to the canonical form"));
}

void CRangeChooserDialog::saveCurrentToRange(RangeItem * i) {
    if (!m_nameEdit->text().isEmpty())
        i->setCaption(m_nameEdit->text());

    i->setRange(m_rangeEdit->toPlainText());
}

void CRangeChooserDialog::addNewRange() {
    static const QString nullStr;
    RangeItem * const i = new RangeItem(tr("New range"), nullStr, m_rangeList);
    m_rangeList->setCurrentItem(i);
    resetEditControls();
}

void CRangeChooserDialog::selectedRangeChanged(QListWidgetItem * current,
                                               QListWidgetItem * previous)
{
    Q_UNUSED(current);
    if (previous) {
        BT_ASSERT(dynamic_cast<RangeItem *>(previous));
        saveCurrentToRange(static_cast<RangeItem*>(previous));
    }

    resetEditControls();
}

void CRangeChooserDialog::resetEditControls() {
    const QListWidgetItem * const item = m_rangeList->currentItem();
    BT_ASSERT(!item || dynamic_cast<RangeItem const *>(item));
    const RangeItem * rangeItem = static_cast<const RangeItem *>(item);

    m_nameEdit->setEnabled(item != nullptr);
    m_rangeEdit->setEnabled(item != nullptr);
    m_resultList->setEnabled(item != nullptr);
    m_deleteRangeButton->setEnabled(item != nullptr);
    m_nameEdit->setText(item != nullptr ? rangeItem->caption() : "");
    m_rangeEdit->setText(item != nullptr ? rangeItem->range() : "");

    if (item != nullptr)
        m_nameEdit->setFocus();

    nameEditTextChanged(item != nullptr ? rangeItem->caption() : "");
}

void CRangeChooserDialog::updateResultList() {
    using VK = sword::VerseKey;

    m_resultList->clear();

    QString const range =
            m_rangeEdit->toPlainText().replace(QRegExp("\\s{0,}-\\s{0,}"), "-");

    Q_FOREACH(const QString & moduleName, m_scopeModules) {
        auto module = CSwordBackend::instance()->findModuleByName(moduleName);
        if (!module)
            continue;
        VK vk = module->module().getKey();
        sword::ListKey verses = vk.parseVerseList(range.toUtf8().constData(),
                                                  "Genesis 1:1", true);
        if (verses.getCount() > 0) {
            for (int i = 0; i < verses.getCount(); i++) {
                new QListWidgetItem(QString::fromUtf8(verses.getElement(i)->getRangeText()),
                                    m_resultList);
            }
            break;
        }
    }
}

void CRangeChooserDialog::deleteCurrentRange() {
    BT_ASSERT(dynamic_cast<RangeItem *>(m_rangeList->currentItem()));
    QListWidgetItem *i = m_rangeList->currentItem();
    m_rangeList->removeItemWidget(i);
    delete i;

    resetEditControls();
}

void CRangeChooserDialog::accept() {
    // Update the active item:
    QListWidgetItem *currentItem = m_rangeList->currentItem();
    if (currentItem != nullptr) {
        BT_ASSERT(dynamic_cast<RangeItem *>(currentItem));
        saveCurrentToRange(static_cast<RangeItem*>(currentItem));
    }

    // Save the new sorted map of search scopes:
    m_rangeList->sortItems();
    BtConfig::StringMap map;
    for (int i = 0; i < m_rangeList->count(); i++) {
        BT_ASSERT(dynamic_cast<RangeItem *>(m_rangeList->item(i)));
        const RangeItem * item = static_cast<RangeItem*>(m_rangeList->item(i));
        map[item->caption()] = item->range();
    }
    btConfig().setSearchScopesWithCurrentLocale(m_scopeModules, map);

    QDialog::accept();
}

void CRangeChooserDialog::restoreDefaults() {
    using SMCI = BtConfig::StringMap::ConstIterator;

    m_rangeList->clear();
    btConfig().deleteSearchScopesWithCurrentLocale();
    const BtConfig::StringMap map = btConfig().getSearchScopesForCurrentLocale(m_scopeModules);
    for (SMCI it = map.begin(); it != map.end(); ++it) {
        new RangeItem(it.key(), it.value(), m_rangeList);
    };
    m_rangeList->setCurrentItem(nullptr);
    resetEditControls();
}

void CRangeChooserDialog::nameEditTextChanged(const QString &newText) {
    const bool e = !newText.isEmpty() || m_rangeList->count() <= 0;
    m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(e);
}

} //end of namespace Search
