/*
 * Fooyin
 * Copyright © 2023, Luke Taylor <luket@pm.me>
 *
 * Fooyin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fooyin 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fooyin.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "tageditoritem.h"

#include "tageditorsettings.h"

#include <utils/stringcollator.h>

#include <numeric>

using namespace Qt::StringLiterals;

constexpr auto CharLimit         = 2000;
constexpr auto PreviewValueLimit = 50;

namespace {
bool canAppendValue(int currentCharCount, const QString& value)
{
    return currentCharCount == 0 || currentCharCount + value.length() <= CharLimit;
}

QString normaliseSingleValue(const QString& value)
{
    QString normalised = value;

    normalised.replace(u"\r\n"_s, u"\n"_s);
    normalised.replace(u'\r', u'\n');

    return normalised;
}

bool containsValue(const QStringList& values, const QString& value)
{
    return std::ranges::find(values, value) != values.cend();
}

bool appendPreviewValue(QStringList& previewValues, QString& cachedValue, int& currentCharCount, const QString& value)
{
    if(containsValue(previewValues, value)) {
        return false;
    }

    if(previewValues.size() >= PreviewValueLimit || !canAppendValue(currentCharCount, value)) {
        return false;
    }

    previewValues.append(value);
    currentCharCount += value.length();
    cachedValue.clear();

    return true;
}
} // namespace

namespace Fooyin::TagEditor {
TagEditorItem::TagEditorItem()
    : TagEditorItem{{}, nullptr}
{ }

TagEditorItem::TagEditorItem(TagEditorField field, TagEditorItem* parent)
    : TreeStatusItem{parent}
    , m_field{std::move(field)}
    , m_titleChanged{false}
    , m_valueChanged{false}
    , m_trackCount{0}
    , m_multipleValues{false}
    , m_splitTrackValues{false}
    , m_valueCharCount{0}
{ }

TagEditorField TagEditorItem::field() const
{
    return m_field;
}

QString TagEditorItem::title() const
{
    return m_field.name;
}

QString TagEditorItem::changedTitle() const
{
    return m_changedTitle;
}

bool TagEditorItem::titleChanged() const
{
    return m_titleChanged;
}

QString TagEditorItem::displayValue() const
{
    if(m_value.isEmpty()) {
        QStringList nonEmptyValues{m_values};
        nonEmptyValues.removeAll(QString{});
        m_value = joinMultiValueText(nonEmptyValues, m_multiValueSeparators);
    }

    if(m_trackCount > 1 && m_multipleValues) {
        return u"<<multiple values>> "_s + value();
    }

    return m_value;
}

QString TagEditorItem::value() const
{
    if(m_value.isEmpty()) {
        QStringList nonEmptyValues{m_values};
        nonEmptyValues.removeAll(QString{});
        m_value = joinMultiValueText(nonEmptyValues, m_multiValueSeparators);
    }

    return m_value;
}

QString TagEditorItem::changedDisplayValue() const
{
    if(m_trackCount > 1 && m_multipleValues) {
        return u"<<multiple values>> "_s + changedValue();
    }

    return changedValue();
}

QString TagEditorItem::changedValue() const
{
    if(m_changedValue.isEmpty()) {
        QStringList nonEmptyValues{m_changedValues};
        nonEmptyValues.removeAll(QString{});
        m_changedValue = joinMultiValueText(nonEmptyValues, m_multiValueSeparators);
    }

    return m_changedValue;
}

bool TagEditorItem::valueChanged() const
{
    return m_valueChanged;
}

bool TagEditorItem::isSetField() const
{
    return m_field.id >= 0;
}

bool TagEditorItem::isDefault() const
{
    return m_field.id >= 0 || m_field.isDefault;
}

int TagEditorItem::trackCount() const
{
    return m_trackCount;
}

bool TagEditorItem::splitTrackValues() const
{
    return m_splitTrackValues;
}

bool TagEditorItem::multipleValues() const
{
    return m_multipleValues;
}

void TagEditorItem::addTrack(int count)
{
    m_trackCount += count;
}

void TagEditorItem::addTrackValue(const QString& value)
{
    m_trackCount++;

    if(containsValue(m_values, value)) {
        return;
    }

    const bool hadPreviousValue = !m_values.empty() || m_multipleValues;
    const bool appended         = appendPreviewValue(m_values, m_value, m_valueCharCount, value);

    if(m_trackCount > 1 && (hadPreviousValue || !appended)) {
        m_multipleValues = true;
    }
}

void TagEditorItem::addTrackValue(const QStringList& values)
{
    m_trackCount++;

    if(values.empty()) {
        if(m_trackCount > 1 && (!m_values.empty() || m_multipleValues)) {
            m_multipleValues = true;
        }
        return;
    }

    for(const auto& trackValue : values) {
        if(containsValue(m_values, trackValue)) {
            continue;
        }

        const bool hadPreviousValue = m_trackCount > 1 || !m_values.empty() || m_multipleValues;
        const bool appended         = appendPreviewValue(m_values, m_value, m_valueCharCount, trackValue);

        if(m_trackCount > 1 && (hadPreviousValue || !appended)) {
            m_multipleValues = true;
        }
    }
}

bool TagEditorItem::setValue(int newValue)
{
    return setValue(QString::number(newValue));
}

bool TagEditorItem::setValue(const QString& newValue, const QStringList& separators)
{
    if(!separators.empty()) {
        setMultiValueSeparators(separators);
    }

    const bool isMultiValueEdit = m_field.multivalue || m_splitTrackValues;

    QStringList values;

    if(isMultiValueEdit) {
        values = splitMultiValueText(newValue, m_multiValueSeparators);
    }
    else {
        values.append(newValue);
    }

    const QString currentValue = value();
    const bool sameValue = isMultiValueEdit ? (currentValue == newValue)
                                            : (normaliseSingleValue(currentValue) == normaliseSingleValue(newValue));

    if(sameValue) {
        const bool appliesPreviewToMixedTracks = status() == None && m_trackCount > 1 && m_multipleValues;

        if(status() == None && !appliesPreviewToMixedTracks
           && (!isMultiValueEdit || m_values == values
               || (m_values.size() == 1 && m_values.front().isEmpty() && values.empty()))) {
            return false;
        }
        if(status() == Changed) {
            m_valueChanged = false;
            m_changedValues.clear();
            m_changedValue.clear();
            setStatus(m_titleChanged ? Changed : None);
            return true;
        }
    }

    m_changedValue   = isMultiValueEdit ? newValue : QString{};
    m_changedValues  = values;
    m_valueChanged   = true;
    m_multipleValues = false;

    if(status() != Added) {
        setStatus(Changed);
    }

    return true;
}

bool TagEditorItem::setTitle(const QString& title)
{
    if(m_field.name == title) {
        if(status() == None) {
            return false;
        }
        if(status() == Changed) {
            m_titleChanged = false;
            m_changedTitle.clear();
            setStatus(m_valueChanged ? Changed : None);
            return true;
        }
    }

    m_changedTitle = title;
    m_titleChanged = true;

    if(status() != Added) {
        setStatus(Changed);
    }

    return true;
}

void TagEditorItem::setFieldMultiValue(bool multivalue)
{
    m_field.multivalue = multivalue;
}

void TagEditorItem::setMultipleValues(bool multiple)
{
    m_multipleValues = multiple;
}

void TagEditorItem::setSplitTrackValues(bool enabled)
{
    m_splitTrackValues = enabled;
}

void TagEditorItem::setMultiValueSeparators(const QStringList& separators)
{
    m_multiValueSeparators = separators.empty() ? normaliseMultiValueSeparators(QString{}) : separators;
    m_value.clear();
    m_changedValue.clear();
}

void TagEditorItem::sortCustomTags()
{
    if(m_children.empty()) {
        return;
    }

    auto sortedChildren{m_children};

    StringCollator collator;

    auto defaultEnd
        = std::ranges::find_if(sortedChildren, [](const TagEditorItem* item) { return item && !item->isDefault(); });

    std::ranges::sort(defaultEnd, sortedChildren.end(),
                      [&collator](const TagEditorItem* lhs, const TagEditorItem* rhs) {
                          return collator.compare(lhs->title(), rhs->title()) < 0;
                      });

    m_children = sortedChildren;
}

void TagEditorItem::applyChanges(const TagEditorField& field)
{
    m_field = field;

    if(m_titleChanged) {
        m_field.name = m_changedTitle;
    }
    if(m_valueChanged) {
        m_values         = m_changedValues;
        m_valueCharCount = std::accumulate(m_values.cbegin(), m_values.cend(), 0,
                                           [](int sum, const QString& str) { return sum + str.length(); });
        m_value          = m_changedValue;
    }

    reset();
}

void TagEditorItem::reset()
{
    m_titleChanged     = false;
    m_valueChanged     = false;
    m_splitTrackValues = false;
    m_changedTitle.clear();
    m_changedValue.clear();
    m_changedValues.clear();
    setStatus(None);
}
} // namespace Fooyin::TagEditor
