/*
 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "utils.h"
#include <QApplication>
#include <QByteArray>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QProcessEnvironment>
#include <QString>
#include <QtGlobal>
#include <QDebug>
#include <QStyleFactory>
#include <QDateTime>
#include <QColor>
#include <QWindow>
#include <QScreen>
#include <QDirIterator>
#include <QTextEdit>
#include "charsetinfo.h"

#ifdef Q_OS_WIN
#include <QDirIterator>
#include <QFont>
#include <QFontMetrics>
#include <QMimeDatabase>
#include <windows.h>
#endif

#if QT_VERSION_MAJOR >= 6
# include <QStringConverter>
#else
# include <QTextCodec>
#endif

BaseError::BaseError(const QString &reason):
mReason(reason)
{

}

QString BaseError::reason() const
{
    return mReason;
}

IndexOutOfRange::IndexOutOfRange(int Index):
BaseError(QObject::tr("Index %1 out of range").arg(Index))
{

}

FileError::FileError(const QString &reason): BaseError(reason)
{

}

QString guessTextEncoding(const QByteArray& text){
    bool allAscii;
    int ii;
    int size;
    const QByteArray& s=text;
    size = s.length();
    if ( (size >= 3) && ((unsigned char)s[0]==0xEF) && ((unsigned char)s[1]==0xBB) && ((unsigned char)s[2]==0xBF)) {
        return ENCODING_UTF8_BOM;
    }
    allAscii = true;
    ii = 0;
    while (ii < size) {
        unsigned char ch = s[ii];
        if (ch < 0x80 ) {
            ii++; // is an ascii char
        } else if (ch < 0xC0) { // value between 0x80 and 0xC0 is an invalid UTF-8 char
            return ENCODING_SYSTEM_DEFAULT;
        } else if (ch < 0xE0) { // should be an 2-byte UTF-8 char
            if (ii>=size-1) {
                return ENCODING_SYSTEM_DEFAULT;
            }
            unsigned char ch2=s[ii+1];
            if ((ch2 & 0xC0) !=0x80)  {
                return ENCODING_SYSTEM_DEFAULT;
            }
            allAscii = false;
            ii+=2;
        } else if (ch < 0xF0) { // should be an 3-byte UTF-8 char
            if (ii>=size-2) {
                return ENCODING_SYSTEM_DEFAULT;
            }
            unsigned char ch2=s[ii+1];
            unsigned char ch3=s[ii+2];
            if (((ch2 & 0xC0)!=0x80) ||  ((ch3 & 0xC0)!=0x80)) {
                return ENCODING_SYSTEM_DEFAULT;
            }
            allAscii = false;
            ii+=3;
        } else { // invalid UTF-8 char
            return ENCODING_SYSTEM_DEFAULT;
        }
    }
    if (allAscii)
        return ENCODING_ASCII;
    return ENCODING_UTF8;
}



bool isTextAllAscii(const QByteArray& text) {
    for (QChar c:text) {
        if (c.unicode()>127) {
            return false;
        }
    }
    return true;
}

bool isTextAllAscii(const QString& text) {
    for (QChar c:text) {
        if (c.unicode()>127) {
            return false;
        }
    }
    return true;
}

bool isNonPrintableAsciiChar(char ch)
{
    return (ch<=32) && (ch>=0);
}

QStringList textToLines(const QString &text)
{
    QTextStream stream(&((QString&)text),QIODevice::ReadOnly);
    return readStreamToLines(&stream);
}


void textToLines(const QString &text, LineProcessFunc lineFunc)
{
    QTextStream stream(&((QString&)text),QIODevice::ReadOnly);
    readStreamToLines(&stream,lineFunc);
}

QString linesToText(const QStringList &lines, const QString& lineBreak)
{
    return lines.join(lineBreak);
}

QList<QByteArray> splitByteArrayToLines(const QByteArray &content)
{
    QList<QByteArray> lines;
    const char* p =content.constData();
    const char* end = p+content.length();
    const char* lineStart = p;
    QByteArray line;
    while (p<=end) {
        char ch=*p;
        switch(ch) {
        case '\r':
            line = QByteArray(lineStart, p-lineStart);
            lines.append(line);
            p++;
            if (*p=='\n')
                p++;
            lineStart = p;
            break;
        case '\n':
            line = QByteArray(lineStart, p-lineStart);
            lines.append(line);
            p++;
            lineStart = p;
            break;
        default:
            p++;
        }
    }
    if (lineStart>end) {
        lines.append("");
    } else {
        line = QByteArray(lineStart, end-lineStart+1);
        lines.append(line);
    }
    return lines;
}

QString trimRight(const QString &s)
{
    if (s.isEmpty())
        return s;
    int i = s.length()-1;
//   while ((i>=0) && ((s[i] == '\r') || (s[i]=='\n') || (s[i] == '\t') || (s[i]==' ')))  {
    while ((i>=0) && ((s[i] == '\t') || (s[i]==' '))) {
        i--;
    };
    if (i>=0) {
        return s.left(i+1);
    } else {
        return QString();
    }
}

QString trimLeft(const QString &s)
{
    if (s.isEmpty())
        return s;
    int i=0;
//    while ((i<s.length()) && ((s[i] == '\r') || (s[i]=='\n') || (s[i] == '\t') || (s[i]==' ')))  {
//        i++;
//    };
    while ((i<s.length()) && ((s[i] == '\t') || (s[i]==' ')))  {
        i++;
    };
    if (i<s.length()) {
        return s.mid(i);
    } else {
        return QString();
    }
}

int countLeadingWhitespaceChars(const QString &line)
{
    int n=0;
    while (n<line.length()) {
        if (line[n].unicode()>32)
            break;
        n++;
    }
    return n;
}

bool stringIsBlank(const QString &s)
{
    for (QChar ch:s) {
        if (ch != ' ' && ch != '\t')
            return false;
    }
    return true;
}

QByteArray toByteArray(const QString &s)
{
    //return s.toLocal8Bit();
    return s.toUtf8();
}

QString fromByteArray(const QByteArray &s)
{
#ifdef Q_OS_WIN
    TextDecoder decoder = TextDecoder::decoderForUtf8();
    auto [ok, result] = decoder.decode(s);
    if (ok)
        return result;
    else
        return QString::fromLocal8Bit(s);
#else
    return QString::fromUtf8(s);
#endif
}

QStringList readStreamToLines(QTextStream *stream)
{
    QStringList list;
    QString s;
    while (stream->readLineInto(&s)) {
        list.append(s);
    }
    return list;
}

void readStreamToLines(QTextStream *stream,
                              LineProcessFunc lineFunc)
{
    QString s;
    while (stream->readLineInto(&s)) {
        lineFunc(s);
    }
}

static QStringList tryLoadFileByEncoding(QByteArray encodingName, QFile& file, bool* isOk) {
    QStringList result;
    *isOk = false;
    TextDecoder decoder(encodingName);
    if (!decoder.isValid())
        return result;
    file.reset();
    while (true) {
        if (file.atEnd()){
            break;
        }
        QByteArray line = file.readLine();
        if (line.endsWith("\r\n")) {
            line.remove(line.length()-2,2);
        } else if (line.endsWith("\r")) {
            line.remove(line.length()-1,1);
        } else if (line.endsWith("\n")){
            line.remove(line.length()-1,1);
        }
        auto [ok, newLine] = decoder.decode(line);
        if (!ok) {
            return QStringList();
        }
        result.append(newLine);
    }
    *isOk=true;
    return result;
}

QStringList readFileToLines(const QString &fileName)
{
    QFile file(fileName);
    if (file.size()<=0)
        return QStringList();
    QStringList result;
    if (file.open(QFile::ReadOnly)) {
        bool ok;
        result = tryLoadFileByEncoding("UTF-8",file,&ok);
        if (ok) {
            return result;
        }

        QByteArray realEncoding = pCharsetInfoManager->getDefaultSystemEncoding();
        result = tryLoadFileByEncoding(realEncoding,file,&ok);
        if (ok) {
            return result;
        }
        QList<PCharsetInfo> charsets = pCharsetInfoManager->findCharsetByLocale(pCharsetInfoManager->localeName());
        if (!charsets.isEmpty()) {
            QSet<QString> encodingSet;
            for (int i=0;i<charsets.size();i++) {
                encodingSet.insert(charsets[i]->name);
            }
            encodingSet.remove(realEncoding);
            foreach (const QString& encodingName,encodingSet) {
                if (encodingName == ENCODING_UTF8)
                    continue;
                result = tryLoadFileByEncoding("UTF-8",file,&ok);
                if (ok) {
                    return result;
                }
            }
        }
    }
    return result;
}

QByteArray readFileToByteArray(const QString &fileName)
{
    QFile file(fileName);
    if (file.open(QFile::ReadOnly)) {
        return file.readAll();
    }
    return QByteArray();
}

bool stringToFile(const QString &str, const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
        return false;
    QTextStream stream(&file);
    stream<<str;
    return true;
}


bool stringsToFile(const QStringList &list, const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
        return false;
    QTextStream stream(&file);
    for (const QString& s:list) {
        stream<<s<<Qt::endl;
    }
    return true;
}

bool fileExists(const QString &file)
{
    if (file.isEmpty())
        return false;
    return QFile(file).exists();
}

bool fileExists(const QString &dir, const QString &fileName)
{
    if (dir.isEmpty() || fileName.isEmpty())
        return false;
    QDir dirInfo(dir);
    return dirInfo.exists(fileName);
}

bool directoryExists(const QString &file)
{
    if (file.isEmpty())
        return false;
   QFileInfo dir(file);
   return dir.exists() && dir.isDir();
}

bool removeFile(const QString &filename)
{
    QFile file(filename);
    return file.remove();
}

bool copyFile(const QString &fromPath, const QString &toPath, bool overwrite)
{
    QFile  fromFile(fromPath);
    QFile toFile(toPath);
    if (!fromFile.exists())
        return false;
    if (toFile.exists()) {
        if (!overwrite)
            return false;
        if (!toFile.remove())
            return false;
    }

    if (!fromFile.open(QFile::ReadOnly))
        return false;
    if (!toFile.open(QFile::WriteOnly | QFile::Truncate))
        return false;

    constexpr int bufferSize=64*1024;
    char buffer[bufferSize];

    while (!fromFile.atEnd()) {
        int readed = fromFile.read(buffer,bufferSize);
        toFile.write(buffer,readed);
    }
    toFile.close();
    fromFile.close();
    return true;
}

void copyFolder(const QString &fromDir, const QString &toDir)
{
    QDirIterator it(fromDir);
    QDir dir(fromDir);
    QDir targetDir(toDir);
    const int absSourcePathLength = dir.absolutePath().length();


    if (targetDir.exists())
        return;
    targetDir.mkpath(targetDir.absolutePath());

    while (it.hasNext()){
        it.next();
        const auto fileInfo = it.fileInfo();
        if(!fileInfo.isHidden() && !fileInfo.fileName().startsWith('.')) { //filters dot and dotdot
            const QString subPathStructure = fileInfo.absoluteFilePath().mid(absSourcePathLength);
            const QString constructedAbsolutePath = targetDir.absolutePath() + subPathStructure;
            if(fileInfo.isDir()){
                //Create directory in target folder
                dir.mkpath(constructedAbsolutePath);
                copyFolder(fileInfo.absoluteFilePath(), constructedAbsolutePath);
            } else if(fileInfo.isFile()) {
                //Copy File to target directory

                //Remove file at target location, if it exists, or QFile::copy will fail
                QFile::remove(constructedAbsolutePath);
                QFile::copy(fileInfo.absoluteFilePath(), constructedAbsolutePath);
                QFile newFile(constructedAbsolutePath);
                QFile::Permissions permissions = newFile.permissions();
                permissions |= (QFile::Permission::WriteOwner
                                | QFile::Permission::WriteUser
                                | QFile::Permission::WriteGroup
                                | QFile::Permission::WriteOther);
                newFile.setPermissions(permissions);
            }
        }
    }
}

QString includeTrailingPathDelimiter(const QString &path)
{
    if (path.endsWith(QDir::separator()) || path.endsWith(QDir::separator())) {
        return path;
    } else {
        return path + QDir::separator();
    }
}

QString excludeTrailingPathDelimiter(const QString &path)
{
    int pos = path.length()-1;
    while (pos>=0 && (path[pos]=='/' || path[pos]==QDir::separator()))
        pos--;
    return path.mid(0,pos+1);
}

QString changeFileExt(const QString& filename, QString ext)
{
    QFileInfo fileInfo(filename);
    QString suffix = fileInfo.suffix();
    QString name  = fileInfo.fileName();
    QString path;
    if (!ext.isEmpty() && !ext.startsWith(".")) {
        ext = "."+ext;
    }
    if (fileInfo.path() != ".") {
        path = includeTrailingPathDelimiter(fileInfo.path());
    }
    if (suffix.isEmpty()) {
        return path+name+ext;
    } else {
        return path+fileInfo.completeBaseName()+ext;
    }
}

QString extractRelativePath(const QString &base, const QString &dest)
{
    if (dest.isEmpty())
        return QString();

    QFileInfo baseInfo(base);
    QDir baseDir;
    if (baseInfo.isDir()) {
        baseDir = QDir(baseInfo.absoluteFilePath());
    } else {
        baseDir = baseInfo.absoluteDir();
    }
    return baseDir.relativeFilePath(dest);
}

QString localizePath(const QString &path)
{
    QString result = path;
    result.replace("/",QDir::separator());
    return result;
}

QString extractFileName(const QString &fileName)
{
    QFileInfo fileInfo(fileName);
    return fileInfo.fileName();
}

QString extractFileDir(const QString &fileName)
{
    return extractFilePath(fileName);
}

QString extractFilePath(const QString &filePath)
{
    QFileInfo info(filePath);
    return info.path();
}

QString extractAbsoluteFilePath(const QString &filePath)
{
    QFileInfo info(filePath);
    return info.absoluteFilePath();
}

bool isReadOnly(const QString &filename)
{
    return QFile(filename).isWritable();
}

int compareFileModifiedTime(const QString &filename1, const QString &filename2)
{
    QFileInfo fileInfo1(filename1);
    QFileInfo fileInfo2(filename2);
    qint64 time1=fileInfo1.lastModified().toMSecsSinceEpoch();
    qint64 time2=fileInfo2.lastModified().toMSecsSinceEpoch();
    if (time1 > time2)
        return 1;
    if (time1 < time2)
        return -1;
    return 0;
}


void inflateRect(QRectF &rect, float delta)
{
    inflateRect(rect,delta,delta);
}

void inflateRect(QRectF &rect, float dx, float dy)
{
    rect.setLeft(rect.left()-dx);
    rect.setRight(rect.right()+dx);
    rect.setTop(rect.top()-dy);
    rect.setBottom(rect.bottom()+dy);
}


static int defaultScreenDPI = -1;

int screenDPI()
{
    if (defaultScreenDPI<1) {
        defaultScreenDPI = qApp->primaryScreen()->logicalDotsPerInch();
    }
    return defaultScreenDPI;
}


void setScreenDPI(int dpi)
{
    defaultScreenDPI = dpi;
}

float pointToPixel(float point, float dpi)
{
     return point * dpi / 72;
}

float pointToPixel(float point)
{
    return pointToPixel(point,screenDPI());
}

float pixelToPoint(float pixel)
{
    return pixel * 72 / screenDPI();
}


void decodeKey(const int combinedKey, int &key, Qt::KeyboardModifiers &modifiers)
{
    modifiers = Qt::NoModifier;
    if (combinedKey & Qt::ShiftModifier) {
        modifiers|=Qt::ShiftModifier;
    }
    if (combinedKey & Qt::ControlModifier) {
        modifiers|=Qt::ControlModifier;
    }
    if (combinedKey & Qt::AltModifier) {
        modifiers|=Qt::AltModifier;
    }
    if (combinedKey & Qt::MetaModifier) {
        modifiers|=Qt::MetaModifier;
    }
    if (combinedKey & Qt::KeypadModifier) {
        modifiers|= Qt::KeypadModifier;
    }
    key = combinedKey & ~(Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier);
}

bool isInFolder(const QString &folderpath, const QString &filepath)
{
    QDir folder(folderpath);
    QFileInfo fileInfo(filepath);
    return fileInfo.absoluteFilePath().startsWith(includeTrailingPathDelimiter(folder.absolutePath()));
}

void createFile(const QString &fileName)
{
    stringToFile("",fileName);
}

QString cleanPath(const QString &dirPath)
{
    return QDir::cleanPath(dirPath);
}

QString generateAbsolutePath(const QString &dirPath, const QString &relativePath)
{
    if (relativePath.isEmpty())
        return QString();
    return QDir::cleanPath(QDir(dirPath).absoluteFilePath(relativePath));
}

QString escapeSpacesInString(const QString &str)
{
    QString result=str;
    return result.replace(' ',"%20");
}

QStringList extractRelativePaths(const QString &base, const QStringList &destList)
{
    QStringList list;
    foreach(const QString& dest,destList) {
        list.append(extractRelativePath(base,dest));
    }
    return list;
}

QStringList absolutePaths(const QString &dirPath, const QStringList &relativePaths)
{
    QStringList list;
    foreach(const QString& path,relativePaths) {
        list.append(generateAbsolutePath(dirPath,path));
    }
    return list;
}

bool isBinaryContent(const QByteArray &text)
{
    for (char c:text) {
        if (c==0) {
            return true;
        }
    }
    return false;
}

void clearQPlainTextEditFormat(QTextEdit *editor)
{
    QTextCursor cursor = editor->textCursor();
    cursor.select(QTextCursor::Document);
    cursor.setCharFormat(QTextCharFormat());
    cursor.clearSelection();
}

int compareFileModifiedTime(const QString &filename, qint64 timestamp)
{
    QFileInfo fileInfo1(filename);
    qint64 time=fileInfo1.lastModified().toMSecsSinceEpoch();
    if (time > timestamp)
        return 1;
    if (time < timestamp)
        return -1;
    return 0;
}

QString replacePrefix(const QString &oldString, const QString &prefix, const QString &newPrefix)
{
    QString result = oldString;
    if (oldString.startsWith(prefix)) {
        result = newPrefix+oldString.mid(prefix.length());
    }
    return result;
}

const QChar *getNullTerminatedStringData(const QString &str)
{
    const QChar* result = str.constData();
    if (result[str.size()]!=QChar(0)) {
        result = str.data();
    }
    return result;
}

TextEncoder::TextEncoder(const char *name)
{
#if QT_VERSION_MAJOR >= 6
    mEncoder = QStringEncoder(name, QStringConverter::Flag::Stateless);
#else
    mCodec = QTextCodec::codecForName(name);
#endif
}

TextEncoder::TextEncoder(const QByteArray &name) :
    TextEncoder(name.data())
{
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
TextEncoder::TextEncoder(QStringEncoder &&encoder)
{
    mEncoder = std::move(encoder);
}
#endif

bool TextEncoder::isValid() const
{
#if QT_VERSION_MAJOR >= 6
    return mEncoder.isValid();
#else
    return mCodec != nullptr;
#endif
}

QByteArray TextEncoder::name() const
{
#if QT_VERSION_MAJOR >= 6
    return mEncoder.name();
#else
    return mCodec->name();
#endif
}

std::pair<bool, QByteArray> TextEncoder::encode(const QString &text)
{
    if (!isValid())
        return {false, QByteArray()};
    QByteArray result;
#if QT_VERSION_MAJOR >= 6
    result = mEncoder(text);
    if (mEncoder.hasError()) {
        mEncoder.resetState();
        return {false, QByteArray()};
    }
#else
    QTextCodec::ConverterState state;
    result = mCodec->fromUnicode(text.constData(), text.length(), &state);
    if (state.invalidChars > 0)
        return {false, QByteArray()};
#endif
    return {true, result};
}

QByteArray TextEncoder::encodeUnchecked(const QString &text)
{
    if (!isValid())
        return QByteArray();
#if QT_VERSION_MAJOR >= 6
    QByteArray result = mEncoder(text);
    if (mEncoder.hasError())
        mEncoder.resetState();
    return result;
#else
    return mCodec->fromUnicode(text);
#endif
}

TextEncoder TextEncoder::encoderForUtf8()
{
#if QT_VERSION_MAJOR >= 6
    return QStringEncoder(QStringConverter::Utf8, QStringConverter::Flag::Stateless);
#else
    return TextEncoder(ENCODING_UTF8);
#endif
}

TextEncoder TextEncoder::encoderForUtf16()
{
#if QT_VERSION_MAJOR >= 6
    return QStringEncoder(QStringConverter::Utf16, QStringConverter::Flag::Stateless);
#else
    return TextEncoder(ENCODING_UTF16);
#endif
}

TextEncoder TextEncoder::encoderForUtf32()
{
#if QT_VERSION_MAJOR >= 6
    return QStringEncoder(QStringConverter::Utf32, QStringConverter::Flag::Stateless);
#else
    return TextEncoder(ENCODING_UTF32);
#endif
}

TextEncoder TextEncoder::encoderForSystem()
{
#ifdef Q_OS_WIN
# if QT_VERSION_MAJOR >= 6
    return QStringEncoder(QStringConverter::System, QStringConverter::Flag::Stateless);
# else
    return TextEncoder(ENCODING_SYSTEM_DEFAULT);
# endif
#else
    return encoderForUtf8();
#endif
}

TextDecoder::TextDecoder(const char *name)
{
#if QT_VERSION_MAJOR >= 6
    mDecoder = QStringDecoder(name, QStringConverter::Flag::Stateless);
#else
    mCodec = QTextCodec::codecForName(name);
#endif
}

TextDecoder::TextDecoder(const QByteArray &name) :
    TextDecoder(name.data())
{
}

#if QT_VERSION_MAJOR >= 6
TextDecoder::TextDecoder(QStringDecoder &&decoder)
{
    mDecoder = std::move(decoder);
}
#endif

bool TextDecoder::isValid() const
{
#if QT_VERSION_MAJOR >= 6
    return mDecoder.isValid();
#else
    return mCodec != nullptr;
#endif
}

QByteArray TextDecoder::name() const
{
#if QT_VERSION_MAJOR >= 6
    return mDecoder.name();
#else
    return mCodec->name();
#endif
}

std::pair<bool, QString> TextDecoder::decode(const QByteArray &text)
{
    if (!isValid())
        return {false, QString()};
    QString result;
#if QT_VERSION_MAJOR >= 6
    result = mDecoder(text);
    if (mDecoder.hasError()) {
        mDecoder.resetState();
        return {false, QString()};
    }
#else
    QTextCodec::ConverterState state;
    result = mCodec->toUnicode(text.constData(), text.length(), &state);
    if (state.invalidChars > 0)
        return {false, QString()};
#endif
    return {true, result};
}

QString TextDecoder::decodeUnchecked(const QByteArray &text)
{
    if (!isValid())
        return QString();
#if QT_VERSION_MAJOR >= 6
    QString result = mDecoder(text);
    if (mDecoder.hasError())
        mDecoder.resetState();
    return result;
#else
    return mCodec->toUnicode(text);
#endif
}

TextDecoder TextDecoder::decoderForUtf8()
{
#if QT_VERSION_MAJOR >= 6
    return QStringDecoder(QStringConverter::Utf8, QStringConverter::Flag::Stateless);
#else
    return TextDecoder(ENCODING_UTF8);
#endif
}

TextDecoder TextDecoder::decoderForUtf16()
{
#if QT_VERSION_MAJOR >= 6
    return QStringDecoder(QStringConverter::Utf16, QStringConverter::Flag::Stateless);
#else
    return TextDecoder(ENCODING_UTF16);
#endif
}

TextDecoder TextDecoder::decoderForUtf32()
{
#if QT_VERSION_MAJOR >= 6
    return QStringDecoder(QStringConverter::Utf32, QStringConverter::Flag::Stateless);
#else
    return TextDecoder(ENCODING_UTF32);
#endif
}

TextDecoder TextDecoder::decoderForSystem()
{
#ifdef Q_OS_WIN
# if QT_VERSION_MAJOR >= 6
    return QStringDecoder(QStringConverter::System, QStringConverter::Flag::Stateless);
# else
    return TextDecoder(ENCODING_SYSTEM_DEFAULT);
# endif
#else
    return decoderForUtf8();
#endif
}

const QStringList &availableEncodings() {
    static bool initialized = false;
    static QStringList encodings;

    if (initialized)
        return encodings;

#if QT_VERSION_MAJOR >= 6
    for (const QString &name : QStringConverter::availableCodecs()) {
        QString lname = name.toLower();
        if (lname.startsWith("cp"))
            continue;
        if (lname == "locale" || lname == "utf-8")
            continue;
        encodings.append(name);
    }
#else
    QSet<QByteArray> codecAlias = {"system", "utf-8"};
    for (const QByteArray &name : QTextCodec::availableCodecs()) {
        QByteArray lname = name.toLower();
        if (lname.startsWith("cp"))
            continue;
        if (codecAlias.contains(lname))
            continue;
        encodings.append(name);
        codecAlias.insert(lname);
        QTextCodec *codec = QTextCodec::codecForName(name);
        if (codec != nullptr) {
            for (const QByteArray &alias : codec->aliases())
                codecAlias.insert(alias.toLower());
        }
    }
#endif
    std::sort(encodings.begin(), encodings.end());
    initialized = true;
    return encodings;
}

bool isEncodingAvailable(const QByteArray &encoding)
{
    TextEncoder encoder(encoding);
    return encoder.isValid();
}