/*
    languagelistmodel.h
    SPDX-FileCopyrightText: 2021 Han Young <hanyoung@protonmail.com>
    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "languagelistmodel.h"
#include "exampleutility.h"
#include "kcm_regionandlang_debug.h"
#include "kcmregionandlang.h"
#include "regionandlangsettings.h"

using namespace KCM_RegionAndLang;

LanguageListModel::LanguageListModel(QObject *parent)
    : QAbstractListModel(parent)
    , m_selectedLanguageModel(new SelectedLanguageModel(this))
{
    connect(this, &LanguageListModel::isPreviewExampleChanged, this, &LanguageListModel::exampleChanged);
    connect(m_selectedLanguageModel, &SelectedLanguageModel::exampleChanged, this, &LanguageListModel::exampleChanged);
    m_availableLanguages = KLocalizedString::availableDomainTranslations("plasmashell").values();
    m_availableLanguages.sort();
    m_availableLanguages.push_front(QStringLiteral("C"));
}

int LanguageListModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_availableLanguages.size();
}

QVariant LanguageListModel::data(const QModelIndex &index, int role) const
{
    const auto row = index.row();
    if (row < 0 || row >= m_availableLanguages.size()) {
        return {};
    }
    switch (role) {
    case NativeName:
        return languageCodeToName(m_availableLanguages.at(row));
    case LanguageCode:
        return m_availableLanguages.at(row);
    case Flag: {
        QString flagCode;
        const QStringList split = QLocale(m_availableLanguages.at(row)).name().split(QLatin1Char('_'));
        if (split.size() > 1) {
            flagCode = split[1].toLower();
        }
        return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/locale/countries/%1/flag.png").arg(flagCode));
    }
    }
    Q_UNREACHABLE();
    return {};
}

QHash<int, QByteArray> LanguageListModel::roleNames() const
{
    return {{NativeName, QByteArrayLiteral("nativeName")}, {LanguageCode, QByteArrayLiteral("languageCode")}, {Flag, QByteArrayLiteral("flag")}};
}

QString LanguageListModel::languageCodeToName(const QString &languageCode)
{
    const QLocale locale(languageCode);
    const QString languageName = locale.nativeLanguageName();

    if (languageName.isEmpty()) {
        return languageCode;
    }

    if (languageCode.contains(QLatin1Char('@'))) {
        return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode);
    }

    // KDE languageCode got translated by QLocale to a locale code we also have on
    // the list. Currently this only happens with pt that gets translated to pt_BR.
    if (languageCode == QStringLiteral("pt_BR")) {
        return i18nc("%1 is português in system locale name, Brazil is to distinguish European português and Brazilian português", "%1 (Brazil)", languageName);
    }

    return languageName;
}

int LanguageListModel::currentIndex() const
{
    return m_index;
}

void LanguageListModel::setCurrentIndex(int index)
{
    if (index == m_index || index < 0 || index >= m_availableLanguages.size()) {
        return;
    }

    m_index = index;
    Q_EMIT exampleChanged();
}

QString LanguageListModel::exampleHelper(const std::function<QString(const QLocale &)> &func) const
{
    if (!m_settings) {
        return {};
    }

    QString result = func(QLocale(m_settings->langWithFallback()));
    if (m_isPreviewExample) {
        if (m_index < 0) {
            result = func(QLocale(m_settings->langWithFallback()));
        } else {
            result = func(QLocale(m_availableLanguages[m_index]));
        }
    }
    return result;
}

QString LanguageListModel::numberExample() const
{
    return exampleHelper(Utility::numericExample);
}

QString LanguageListModel::currencyExample() const
{
    return exampleHelper(Utility::monetaryExample);
}

QString LanguageListModel::timeExample() const
{
    return exampleHelper(Utility::timeExample);
}

QString LanguageListModel::paperSizeExample() const
{
    return exampleHelper(Utility::paperSizeExample);
}

#ifdef LC_ADDRESS
QString LanguageListModel::addressExample() const
{
    return exampleHelper(Utility::addressExample);
}

QString LanguageListModel::nameStyleExample() const
{
    return exampleHelper(Utility::nameStyleExample);
}

QString LanguageListModel::phoneNumbersExample() const
{
    return exampleHelper(Utility::phoneNumbersExample);
}
#endif

QString LanguageListModel::metric() const
{
    return exampleHelper(Utility::measurementExample);
}

void LanguageListModel::setRegionAndLangSettings(QObject *settings)
{
    if (auto *regionandlangsettings = dynamic_cast<RegionAndLangSettings *>(settings)) {
        m_settings = regionandlangsettings;
        m_selectedLanguageModel->setRegionAndLangSettings(regionandlangsettings);
        Q_EMIT exampleChanged();
    }
}

bool LanguageListModel::isPreviewExample() const
{
    return m_isPreviewExample;
}

void LanguageListModel::setIsPreviewExample(bool preview)
{
    m_isPreviewExample = preview;
}

void SelectedLanguageModel::setRegionAndLangSettings(RegionAndLangSettings *settings)
{
    m_settings = settings;

    beginResetModel();
    if (m_settings->language().isEmpty() && m_settings->isDefaultSetting(SettingType::Lang)) {
        // no language but have lang
        m_selectedLanguages = {m_settings->lang()};
        m_selectedLanguages.first().remove(QStringLiteral(".UTF-8"));
    } else if (!m_settings->language().isEmpty()) {
        // have language, ignore lang
        m_selectedLanguages = m_settings->language().split(QLatin1Char(':'));
    } else {
        // have nothing, figure out from env
        QString lang = envLang();
        QString language = envLanguage();
        if (!language.isEmpty()) {
            QStringList langlist = language.split(QLatin1Char(':'));
            for (QString &lang : langlist) {
                lang = lang.split(QLatin1Char('.'))[0];
            }
            m_selectedLanguages = langlist;
        } else if (!lang.isEmpty()) {
            lang.remove(QStringLiteral(".UTF-8"));
            m_selectedLanguages = {lang};
        }
        m_hasImplicitLang = true;
        Q_EMIT hasImplicitLangChanged();
    }
    endResetModel();

    // check for invalid lang
    if (const QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front()); lang.isEmpty()) {
        m_unsupportedLanguage = m_selectedLanguages.front();
        Q_EMIT unsupportedLanguageChanged();
    } else if (!m_unsupportedLanguage.isEmpty()) {
        m_unsupportedLanguage.clear();
        Q_EMIT unsupportedLanguageChanged();
    }
}

SelectedLanguageModel *LanguageListModel::selectedLanguageModel() const
{
    return m_selectedLanguageModel;
}

int SelectedLanguageModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_selectedLanguages.size();
}

QVariant SelectedLanguageModel::data(const QModelIndex &index, int role) const
{
    Q_UNUSED(role)
    const auto row = index.row();
    if (row < 0 || row > m_selectedLanguages.size()) {
        return {};
    }
    // "add Language" Item
    if (row == m_selectedLanguages.size()) {
        return {};
    }

    return LanguageListModel::languageCodeToName(m_selectedLanguages.at(row));
}

bool SelectedLanguageModel::shouldWarnMultipleLang() const
{
    if (m_selectedLanguages.size() >= 2) {
        if (m_selectedLanguages.front() == QStringLiteral("en_US")) {
            return true;
        }
    }
    return false;
}

void SelectedLanguageModel::move(int from, int to)
{
    if (from == to || from < 0 || from >= m_selectedLanguages.size() || to < 0 || to >= m_selectedLanguages.size()) {
        return;
    }

    if (m_hasImplicitLang) {
        m_hasImplicitLang = false;
        Q_EMIT hasImplicitLangChanged();
    }

    beginResetModel();
    m_selectedLanguages.move(from, to);
    endResetModel();
    saveLanguages();
    Q_EMIT shouldWarnMultipleLangChanged();
    Q_EMIT exampleChanged();
}

void SelectedLanguageModel::remove(int index)
{
    if (index < 0 || index >= m_selectedLanguages.size()) {
        return;
    }
    beginRemoveRows(QModelIndex(), index, index);
    m_selectedLanguages.removeAt(index);
    endRemoveRows();
    saveLanguages();
    Q_EMIT shouldWarnMultipleLangChanged();
    Q_EMIT exampleChanged();
}

void SelectedLanguageModel::addLanguage(const QString &lang)
{
    if (lang.isEmpty() || m_selectedLanguages.indexOf(lang) != -1) {
        return;
    }

    // fix Kirigami.SwipeListItem doesn't connect to Actions' visible property.
    // Reset model enforce a refresh of delegate
    beginResetModel();
    if (m_hasImplicitLang) {
        m_hasImplicitLang = false;
        Q_EMIT hasImplicitLangChanged();
    }
    m_selectedLanguages.push_back(lang);
    endResetModel();
    saveLanguages();
    Q_EMIT shouldWarnMultipleLangChanged();
    Q_EMIT exampleChanged();
}

void SelectedLanguageModel::replaceLanguage(int index, const QString &lang)
{
    if (index < 0 || index >= m_selectedLanguages.size() || lang.isEmpty()) {
        return;
    }

    int existLangIndex = m_selectedLanguages.indexOf(lang);
    // return if no change, but allow change implicit lang to explicit
    if (existLangIndex == index && !m_hasImplicitLang) {
        return;
    }

    beginResetModel();
    m_selectedLanguages[index] = lang;
    if (!m_hasImplicitLang) {
        // delete duplicate lang
        if (existLangIndex != -1) {
            m_selectedLanguages.removeAt(existLangIndex);
        }
    } else {
        m_hasImplicitLang = false;
        Q_EMIT hasImplicitLangChanged();
    }
    endResetModel();
    saveLanguages();
    Q_EMIT shouldWarnMultipleLangChanged();
    Q_EMIT exampleChanged();
}

void SelectedLanguageModel::saveLanguages()
{
    // implicit lang means no change
    if (!m_settings || m_hasImplicitLang) {
        return;
    }
    if (m_selectedLanguages.empty()) {
        m_settings->setLang(m_settings->defaultLangValue());
        m_settings->config()->group(QStringLiteral("Formats")).deleteEntry("lang");
        m_settings->config()->group(QStringLiteral("Translations")).deleteEntry("language");
    } else {
        QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front());
        if (lang.isEmpty()) {
            m_unsupportedLanguage = m_selectedLanguages.front();
            Q_EMIT unsupportedLanguageChanged();
        } else {
            if (!m_unsupportedLanguage.isEmpty()) {
                m_unsupportedLanguage.clear();
                Q_EMIT unsupportedLanguageChanged();
            }
            // don't set LANG in non glibc systems, because in non glibc systems KCMRegionAndLang::toGlibcLocale return the language itself
            if (KCMRegionAndLang::isGlibc()) {
                m_settings->setLang(lang);
            }
        }
        QString languages;
        for (auto i = m_selectedLanguages.cbegin(); i != m_selectedLanguages.cend(); i++) {
            languages.push_back(*i);
            // no ':' at end
            if (i + 1 != m_selectedLanguages.cend()) {
                languages.push_back(QLatin1Char(':'));
            }
        }
        m_settings->setLanguage(languages);
    }
}

bool SelectedLanguageModel::hasImplicitLang() const
{
    return m_hasImplicitLang;
}

const QString &SelectedLanguageModel::unsupportedLanguage() const
{
    return m_unsupportedLanguage;
}

QString SelectedLanguageModel::envLang() const
{
    return qEnvironmentVariable("LANG");
}

QString SelectedLanguageModel::envLanguage() const
{
    return qEnvironmentVariable("LANGUAGE");
}
