//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Descriptor/ComboProperty.cpp
//! @brief     Implements class ComboProperty.
//!
//! @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)
//
//  ************************************************************************************************

#include "GUI/Model/Descriptor/ComboProperty.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Util/UtilXML.h"

namespace {

const QString value_separator = ";";
const QString selection_separator = ",";

} // namespace


ComboProperty::ComboProperty() = default;

ComboProperty::ComboProperty(QStringList values)
    : m_values(std::move(values))
{
}

ComboProperty ComboProperty::fromList(const QStringList& values, const QString& current_value)
{
    ComboProperty result(values);

    if (!current_value.isEmpty())
        result.setCurrentValue(current_value);

    return result;
}

ComboProperty ComboProperty::fromStdVec(const std::vector<std::string>& values,
                                        const std::string& current_value)
{
    QStringList q_list;

    for (const std::string& val : values)
        q_list << QString::fromStdString(val);

    QString q_value = QString::fromStdString(current_value);

    return fromList(q_list, q_value);
}

QString ComboProperty::currentValue() const
{
    return currentIndex() < 0 ? QString() : m_values.at(currentIndex());
}

void ComboProperty::setCurrentValue(const QString& name)
{
    ASSERT(m_values.contains(name));
    setCurrentIndex(m_values.indexOf(name));
}
//! Sets new list of values. Current value will be preserved, if exists in a new list.

void ComboProperty::setValues(const QStringList& values)
{
    ASSERT(!values.empty());
    QString current = currentValue();
    m_values = values;
    setCurrentIndex(m_values.contains(current) ? m_values.indexOf(current) : 0);
}

void ComboProperty::setToolTips(const QStringList& tooltips)
{
    m_tooltips = tooltips;
}

int ComboProperty::currentIndex() const
{
    return m_selected_indices.empty() ? -1 : m_selected_indices.at(0);
}

void ComboProperty::setCurrentIndex(int index)
{
    ASSERT(index >= 0 && index < m_values.size());
    m_selected_indices.clear();
    m_selected_indices.push_back(index);
}

ComboProperty& ComboProperty::operator<<(const QString& str)
{
    m_values.append(str);
    if (!m_values.empty())
        setCurrentIndex(0);
    return *this;
}

ComboProperty& ComboProperty::operator<<(const QStringList& str)
{
    m_values.append(str);
    if (!m_values.empty())
        setCurrentIndex(0);
    return *this;
}

bool ComboProperty::operator==(const ComboProperty& other) const
{
    if (m_selected_indices != other.m_selected_indices)
        return false;
    if (m_values != other.m_values)
        return false;
    return true;
}

bool ComboProperty::operator!=(const ComboProperty& other) const
{
    return !(*this == other);
}

bool ComboProperty::operator<(const ComboProperty& other) const
{
    return m_selected_indices.size() < other.m_selected_indices.size()
           && m_values.size() < other.m_values.size();
}

//! Returns a single string containing values delimited with ';'.

QString ComboProperty::stringOfValues() const
{
    return m_values.join(value_separator);
}

//! Sets values from the string containing delimeter ';'.

void ComboProperty::setStringOfValues(const QString& values)
{
    QString current = currentValue();
    m_values = values.split(value_separator);
    setCurrentIndex(m_values.contains(current) ? m_values.indexOf(current) : 0);
}

//! Constructs variant enclosing given ComboProperty.

QVariant ComboProperty::variant() const
{
    QVariant result;
    result.setValue(*this);
    return result;
}

//! Returns vector of selected indices.
//! Returns list of string with selected values;

QStringList ComboProperty::selectedValues() const
{
    QStringList result;
    for (auto index : m_selected_indices)
        result.append(m_values.at(index));
    return result;
}

//! Sets given index selection flag.
//! If false, index will be excluded from selection.

void ComboProperty::setSelected(int index, bool value)
{
    if (index < 0 || index >= m_values.size())
        return;

    if (value) {
        if (!m_selected_indices.contains(index))
            m_selected_indices.push_back(index);
    } else {
        m_selected_indices.removeAll(index);
    }
    std::sort(m_selected_indices.begin(), m_selected_indices.end());
}

void ComboProperty::setSelected(const QString& name, bool value)
{
    setSelected(m_values.indexOf(name), value);
}

//! Return string with coma separated list of selected indices.

QString ComboProperty::stringOfSelections() const
{
    QStringList text;
    for (auto index : m_selected_indices)
        text.append(QString::number(index));
    return text.join(selection_separator);
}

//! Sets selected indices from string.

void ComboProperty::setStringOfSelections(const QString& values)
{
    m_selected_indices.clear();
    if (values.isEmpty())
        return;

    for (const auto& str : values.split(selection_separator)) {
        bool success(false);
        int num = str.toInt(&success);
        if (success)
            setSelected(num, true);
    }
}

//! Returns the label to show

QString ComboProperty::label() const
{
    if (m_selected_indices.size() > 1)
        return "Multiple";
    if (m_selected_indices.size() == 1)
        return currentValue();
    return "None";
}

void ComboProperty::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::index, currentIndex());
    // The next line allows to see the entry name in XML. It may be skipped while reading.
    XML::writeAttribute(w, XML::Attrib::name, currentValue());
    XML::writeAttribute(w, XML::Attrib::version, uint(1));
}

void ComboProperty::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    int index = -1;
    XML::readAttribute(r, XML::Attrib::index, &index);
    ASSERT(index < m_values.size());
    // 'ComboProperty' can be in unselected state
    if (index >= 0)
        setCurrentIndex(index);
}
