//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Type/SetWithModel.h
//! @brief     Defines and implements templated class SetWithModel.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifdef SWIG
#error no need to expose this header to Swig
#endif // SWIG
#ifndef BORNAGAIN_GUI_MODEL_TYPE_SETWITHMODEL_H
#define BORNAGAIN_GUI_MODEL_TYPE_SETWITHMODEL_H

#include "Base/Type/OwningVector.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Type/ModelForSet.h"
#include <QObject>
#include <QStringList>
#include <iostream>
#include <vector>

//! Non-templated base class for SetWithModel.
//!
//! Needed to support Qt signals (the Q_OBJECT macro does not work in templated classes).
//! The virtual functions setCurrentIndex(i) and model() are used in class SetView.

class AbstractSetModel : public QObject {
    Q_OBJECT
public:
    explicit AbstractSetModel();
    virtual ~AbstractSetModel();

    virtual size_t currentIndex() const = 0;
    virtual void setCurrentIndex(size_t i) = 0;
    virtual QAbstractListModel* model() = 0;

signals:
    void setChanged() const;
    void indexSet() const;
    void currentModified() const;
};

//! A set of NamedItem%s that has a current item and a QListModel.

template <typename T> class SetWithModel : public AbstractSetModel {
public:
    SetWithModel()
        : m_qmodel(std::make_unique<ModelForSet<T>>(this))
    {
    }
    virtual ~SetWithModel() = default;

    void clear()
    {
        m_qmodel->beginResetModel();
        m_vec.clear();
        m_idx = -1;
        m_qmodel->endResetModel();
        emit AbstractSetModel::setChanged();
    }
    void delete_current()
    {
        ASSERT(m_idx != size_t(-1));
        m_qmodel->beginRemoveRows({}, m_idx, m_idx);
        m_vec.delete_at(m_idx);
        update_current();
        m_qmodel->endRemoveRows();
        emit AbstractSetModel::setChanged();
    }
    void add_item(T* t)
    {
        m_qmodel->beginInsertRows({}, size(), size());
        t->renumber(itemNames());
        m_vec.push_back(t);
        m_idx = m_vec.size() - 1;
        m_qmodel->endInsertRows();
        emit AbstractSetModel::setChanged();
    }
    void add_items(const std::vector<T*>& v)
    {
        if (v.empty())
            return;
        m_qmodel->beginInsertRows({}, size(), size() + v.size() - 1);
        for (T* t : v) {
            t->renumber(itemNames());
            m_vec.push_back(t);
        }
        m_idx = m_vec.size() - 1;
        m_qmodel->endInsertRows();
        emit AbstractSetModel::setChanged();
    }
    void move_current_to(size_t i)
    {
        if (i >= size())
            return;
        QModelIndex parent = m_qmodel->index(m_idx).parent();
        m_qmodel->beginMoveRows(parent, m_idx, m_idx, parent, (i <= m_idx) ? i : i + 1);
        m_vec.insert_at(i, m_vec.release_at(m_idx));
        m_idx = i;
        m_qmodel->endMoveRows();
        emit AbstractSetModel::setChanged();
    }

    void setCurrentIndex(size_t i) override
    {
        if (!(i < m_vec.size() || i == size_t(-1))) {
            std::cerr << "setCurrentIndex i=" << i << " vs vec#=" << m_vec.size() << std::endl;
            ASSERT_NEVER;
        }
        m_qmodel->beginResetModel();
        if (i != m_idx)
            m_idx = i;
        m_qmodel->endResetModel();
        emit indexSet();
        emit AbstractSetModel::setChanged();
    }

    void setCurrentName(const QString& s)
    {
        if (s != currentItem()->name()) {
            currentItem()->setName(s);
            currentDataChanged();
        }
    }
    void setCurrentDescription(const QString& s)
    {
        if (s != currentItem()->description()) {
            currentItem()->setDescription(s);
            currentDataChanged();
        }
    }

    size_t currentIndex() const override { return m_idx; }
    const T* currentItem() const { return m_idx < m_vec.size() ? m_vec.at(m_idx) : (T*)nullptr; }
    T* currentItem() { return m_idx < m_vec.size() ? m_vec.at(m_idx) : (T*)nullptr; }

    const T* at(size_t i) const { return m_vec.at(i); }
    T* at(size_t i) { return m_vec.at(i); }
    const T* back() const { return m_vec.back(); }
    T* back() { return m_vec.back(); }

    size_t size() const { return m_vec.size(); }
    bool empty() const { return m_vec.empty(); }
    int index_of(const T* t) const { return m_vec.index_of(t); }
    const T* at_or(size_t i, T* defolt) const { return m_vec.at_or(i, defolt); }

    QStringList itemNames() const
    {
        QStringList result;
        for (const auto* t : m_vec)
            result << t->name();
        return result;
    }

    using Iterator = typename std::vector<T*>::iterator; // "typename" can be dropped under C++20
    using ConstIterator = typename std::vector<T*>::const_iterator;

    ConstIterator begin() const { return m_vec.begin(); }
    ConstIterator end() const { return m_vec.end(); }
    Iterator begin() { return m_vec.begin(); }
    Iterator end() { return m_vec.end(); }

    QAbstractListModel* model() override { return m_qmodel.get(); }

private:
    //! To be called when m_idx may have fallen out of range.
    void update_current()
    {
        if (m_idx == m_vec.size())
            m_idx = m_vec.size() - 1;
    }
    //! To be called when name or description of the current item have changed.
    void currentDataChanged()
    {
        QModelIndex qi = m_qmodel->index(m_idx, 0);
        m_qmodel->dataChanged(qi, qi, QVector<int>({Qt::DisplayRole, Qt::EditRole}));
    }

    OwningVector<T> m_vec;
    size_t m_idx = -1; //!< current index, or -1 for empty set
    std::unique_ptr<ModelForSet<T>> m_qmodel;
};

#endif // BORNAGAIN_GUI_MODEL_TYPE_SETWITHMODEL_H
