/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2024 KylinSoft  Co., Ltd.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */
#include <gio/gio.h>
#include <fontconfig/fontconfig.h>
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_TYPE1_TABLES_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_IDS_H
#include FT_MULTIPLE_MASTERS_H

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QGSettings>

#include "libfun.h"
#include "../../universalinterface.h"

#define FONT_BLACK_LIST_KEY  "font-black-list"

const QDBusArgument &operator<<(QDBusArgument &argument, const FontInformation &key)
{
    argument.beginStructure();
    argument << key.path;
    argument << key.name;
    argument << key.family;
    argument << key.style;
    argument << key.type;
    argument << key.foundry;
    argument << key.version;
    argument << key.copyright;
    argument << key.manufacturer;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, FontInformation &key)
{
    argument.beginStructure();
    argument >> key.path;
    argument >> key.name;
    argument >> key.family;
    argument >> key.style;
    argument >> key.type;
    argument >> key.foundry;
    argument >> key.version;
    argument >> key.copyright;
    argument >> key.manufacturer;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator<<(QDBusArgument &argument, const ViewData &key)
{
    argument.beginStructure();
    argument << key.path;
    argument << key.family;
    argument << key.style;
    argument << key.name;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, ViewData &key)
{
    argument.beginStructure();
    argument >> key.path;
    argument >> key.family;
    argument >> key.style;
    argument >> key.name;
    argument.endStructure();
    return argument;
}


bool LibFun::chooseFontFile(QString path)
{
    QStringList list = path.split(".");
    QString fontTypeTTF = "ttf";
    QString fontTypeOTF = "otf";
    QString fontTypeTTC = "ttc";
    QString fontType = list.at(list.size()-1);

    if (fontType.compare(fontTypeTTF, Qt::CaseInsensitive) == 0) {
        return true;
    } else if (fontType.compare(fontTypeOTF, Qt::CaseInsensitive) == 0) {
        return true;
    } else if (fontType.compare(fontTypeTTC, Qt::CaseInsensitive) == 0) {
        return true;
    }

    return false;
}


void LibFun::remove_whitespace(char **ori)
{
    g_auto(GStrv) split = NULL;
    g_autoptr(GString) reassembled = NULL;
    gchar *str = NULL;
    gint i = 0;
    gint stripped = 0;
    size_t len = 0;

    reassembled = g_string_new(NULL);

    split = g_strsplit(*ori , "\n" , -1);

    for (i = 0 ; split[i] != NULL ; i++) {
        str = split[i];

        len = strspn(str , WHITESPACE_CHARS);
        if (len) {
            str += len;
        }

        if (strnlen(str, sizeof(str)) == 0 && ((split[i + 1] == NULL) || (strnlen(split[i + 1], sizeof(split[i + 1])) == 0))) {
            continue;
        }

        if (stripped++ > 0) {
            g_string_append(reassembled , "\n");
        }

        g_string_append(reassembled , str);
    }

    g_free(*ori);
    *ori = g_strdup(reassembled->str);

    return;
}

void LibFun::remove_version(char **ori)
{
    gchar *p = NULL;
    gchar *stripped = NULL;

    p = g_strstr_len(*ori , -1 , MATCH_VERSION_STR);
    if (p == NULL) {
        return;
    }

    p += strlen(MATCH_VERSION_STR);
    stripped = g_strdup(p);

    remove_whitespace(&stripped);

    g_free(*ori);
    *ori = stripped;

    return;
}

QList<FontInformation> LibFun::getAllFontInformation(void)
{
    QList<FontInformation> ret;
    ret.clear();

    FT_Library ft_library;
    FT_Error err = FT_Init_FreeType(&ft_library);
    if (err != FT_Err_Ok) {
        qCritical() << "Error : LibFun , getAllFontInformation , init freetype fail";
        return ret;
    }
    if (!FcInitReinitialize()) {
        qCritical() << "Error : LibFun , getAllFontInformation , init font list fail";
        return ret;
    }

    FcConfig *config = FcInitLoadConfigAndFonts();
    if (!config) {
        qCritical() << "FcConfig is null";
        return ret;
    }
    FcPattern *pat = FcPatternCreate();
    if (!pat) {
        qCritical() << "FcPattern is null";
        return ret;
    }
    FcObjectSet *os = FcObjectSetBuild(FC_FILE , FC_FAMILY , FC_STYLE, FC_FOUNDRY, FC_INDEX , NULL); // 构建对象（字体文件路径，字体名，类别，字体铸造商）
    if (!os) {
        qCritical() << "FcObjectSet is null";
        return ret;
    }
    FcFontSet *fs = FcFontList(config , pat , os);
    if (!fs) {
        qCritical() << "FcFontSet is null";
        return ret;
    }
    QStringList fontPathList;
    QStringList fontNameStyleList;
    QGSettings* ukccGsettings = UniversalInterface::self()->ukccGsettings();
    QStringList blackNameList;
    if (ukccGsettings && ukccGsettings->keys().contains("fontBlackList")) {
        blackNameList = ukccGsettings->get("fontBlackList").toStringList();
        qDebug() << Q_FUNC_INFO << "blackNameList" << blackNameList;
    }
    for (int i = 0 ; i < fs->nfont && fs != NULL ; i++) {
        FontInformation item;
        FcChar8 *path = NULL;
        FcChar8 *family = NULL;
        FcChar8 *style = NULL;
        FcChar8 *foundry = NULL;
        int index;

        FcPattern *font = fs->fonts[i];

        if (FcPatternGetString(font, FC_FILE, 0, &path) == FcResultMatch &&
            FcPatternGetString(font, FC_FAMILY, 0, &family) == FcResultMatch &&
            FcPatternGetString(font, FC_STYLE, 0, &style) == FcResultMatch &&
            FcPatternGetString(font, FC_FOUNDRY, 0, &foundry) == FcResultMatch &&
            FcPatternGetInteger(font, FC_INDEX, 0, &index) == FcResultMatch)
        {
            item.path = QString((char *)path);
            item.family = QString((char *)family);
            item.style = QString((char *)style);
            item.foundry = QString((char *)foundry);
        }
        /* 对字体文件进行判断（判断后缀名是否为.ttf .otf .ttc）*/
        if (!chooseFontFile(item.path)) {
            continue;
        }

        gchar *fontData = NULL;
        gsize fontDataLenth;
        g_autoptr(GError) error = NULL;
        GFile *fd = g_file_new_for_path((const gchar *)path);
        if (!fd) {
            continue;
        }
        std::unique_ptr<GFile, decltype(&g_object_unref)> fileGuard(fd, g_object_unref);
        if (!g_file_load_contents(fd , NULL , &fontData , &fontDataLenth , NULL , &error)) {
            qWarning() << "Waring : LibFun , getAllFontInformation , load font file fail , Path is [ " << path << " ]" << " error is [ " << error->message << " ]";
            ret << item;
            continue;
        }
        std::unique_ptr<gchar, decltype(&g_free)> fontDataGuard(fontData, g_free);

        FT_Error ft_error;
        FT_Face ft_retval;
        ft_error = FT_New_Memory_Face(ft_library , (const FT_Byte *)fontData , (FT_Long)fontDataLenth , index , &ft_retval);
        if (ft_error != FT_Err_Ok) {
            ret << item;
            continue;
        }
        std::unique_ptr<FT_FaceRec_, decltype(&FT_Done_Face)> faceGuard(ft_retval, FT_Done_Face);

        /* 名字 */
        if (ft_retval->family_name) {
            item.name = QString((char *)ft_retval->family_name);
        }
        if ((item.name.contains("Noto Sans CJK") || item.name.contains("Noto Sans Mono")) && item.name != "Noto Sans CJK SC") {
            continue;
        } else if (fontPathList.contains(item.path)) {
            continue;
        }

        if (blackNameList.contains(item.name)) {
            continue;
        }

        if (item.name.isEmpty()) {
            qDebug() << Q_FUNC_INFO << "font name is emtpy, skip";
            continue;
        }

        /* 样式 */
        if (ft_retval->style_name) {
            item.style = QString((char *)ft_retval->style_name);
        }

        QString curNameStyle = item.name + " " + item.style;
        if (fontNameStyleList.contains(curNameStyle)) {
            continue;
        }
        /* 路径 */
        g_autofree gchar *location = NULL;
        location = g_file_get_path(fd);
        item.path = QString((char *)location);

        /* 种类 */
        g_autoptr(GFileInfo) fileInfo;
        fileInfo = g_file_query_info(fd , G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE , G_FILE_QUERY_INFO_NONE , NULL , NULL);
        if (fileInfo != NULL) {
            g_autofree gchar *fileType = g_content_type_get_description(g_file_info_get_content_type(fileInfo));
            item.type = QString((char *)fileType);
        }
        if (FT_IS_SFNT(ft_retval)) {
            gint i;
            gint len;
            g_autofree gchar *version = NULL;
            g_autofree gchar *copyright = NULL;
            g_autofree gchar *description = NULL;
            g_autofree gchar *designer = NULL;
            g_autofree gchar *manufacturer = NULL;
            g_autofree gchar *license = NULL;
            len = FT_Get_Sfnt_Name_Count(ft_retval);
            for (i = 0 ; i < len ; i++) {
                FT_SfntName ft_sname;
                if (FT_Get_Sfnt_Name(ft_retval , i , &ft_sname) != 0) {
                    continue;
                }

                if (!(ft_sname.platform_id == TT_PLATFORM_MICROSOFT &&
                      ft_sname.encoding_id == TT_MS_ID_UNICODE_CS &&
                      ft_sname.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES))
                {
                    continue;
                }

                switch (ft_sname.name_id) {
                case TT_NAME_ID_COPYRIGHT :
                    if (!copyright) {
                        copyright = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                case TT_NAME_ID_VERSION_STRING :
                    if (!version) {
                        version = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                case TT_NAME_ID_DESCRIPTION :
                    if (!description) {
                        description = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                case TT_NAME_ID_MANUFACTURER :
                    if (!manufacturer) {
                        manufacturer = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                case TT_NAME_ID_DESIGNER :
                    if (!designer) {
                        designer = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                case TT_NAME_ID_LICENSE :
                    if (!license) {
                        license = g_convert((gchar *)ft_sname.string , ft_sname.string_len , "UTF-8" , "UTF-16BE" , NULL , NULL , NULL);
                    }
                    break;
                default :
                    break;
                }
            }

            /* 版本 */
            if (version) {
                remove_version(&version);
                item.version = QString((char *)version);
            }

            /* 版权 */
            if (copyright) {
                remove_whitespace(&copyright);
                item.copyright = QString((char *)copyright);
            }

            /* 商标 */
            if (manufacturer) {
                remove_whitespace(&manufacturer);
                item.manufacturer = QString((char *)manufacturer);
            }
        }
        ret << item;
        fontPathList.append(item.path);
        fontNameStyleList.append(item.name + " " + item.style);
    }

    if (pat) {
        FcPatternDestroy(pat);
    }
    if (os) {
        FcObjectSetDestroy(os);
    }

    if (fs) {
        FcFontSetDestroy(fs);
    }
    qSort(ret.begin(), ret.end(), caseInsensitiveLessThan);

    return ret;
}

bool LibFun::installFont(QString path)
{
    if (path.isEmpty()) {
        return false;
    }

    QString installPath = QString(getenv("HOME")) + QString(INSTALL_PATH);
    QDir dir(installPath);
    if (!dir.exists()) {
        if (!dir.mkpath(installPath)) {
            qCritical() << "Error : LibFun , installFont , install path create fail";
            return false;
        }
    }

    QFileInfo fileInfo(path);
    QString installFilePath = installPath + fileInfo.fileName();
    QFile file(installFilePath);
    if (file.exists() && !QFile::remove(installFilePath)) {
        qDebug() << Q_FUNC_INFO << "remove file error" << installFilePath;
        return false;
    }

    if (!QFile::copy(path , installFilePath)) {
        qCritical() << "Error : LibFun , installFont , copy font file fail";
        return false;
    }

    if (system("fc-cache")) {
        qWarning() << "Waring : LibFun , installFont , update cache fail";
    }

    return true;
}

bool LibFun::uninstallFont(QString path)
{
    if (path.isEmpty()) {
        return false;
    }

    QFile file(path);
    if (file.exists()) {
        if (!file.remove()) {
            qCritical() << "Error : LibFun , uninstallFont , remove font file fail";
            return false;
        }
    }

    if (system("fc-cache")) {
        qWarning() << "Waring : LibFun , uninstall , update font cache fail";
    }

    return true;
}

bool LibFun::copyFont(QString src , QString dst)
{
    if (src.isEmpty() || dst.isEmpty()) {
        return false;
    }

    QString dstName;
    QFileInfo fileInfo(src);
    if (dst.back() == QChar('/')) {
        dstName = dst + fileInfo.fileName();
    } else {
        dstName = dst + "/" + fileInfo.fileName();
    }

    if (!QFile::copy(src , dstName)) {
        qCritical() << "Error : LibFun , copyFont , font file copy fail";

        return false;
    }

    return true;
}

bool LibFun::caseInsensitiveLessThan(const FontInformation &s1, const FontInformation &s2)
{
    if (s1.name == s2.name){
        return s1.style < s2.style;
    }
    return s1.name < s2.name;
}

QString LibFun::getFontInfo(QString path)
{
    QString ret;
    std::string str = path.toStdString();
    FcChar8* file = (FcChar8*)(str.c_str());
    FcPattern* pat = nullptr;
    int count;
    FcBlanks* blanks = FcConfigGetBlanks(NULL);
    pat = FcFreeTypeQuery((FcChar8 *)file, 0, blanks, &count);

    // 判断是否从字体文件中获取到字体数据
    if (pat == nullptr) {
        qDebug() << "判断字体文件，字体文件损坏：" <<__FILE__<< ","<<__FUNCTION__<<","<<__LINE__;
        ret = path;
    }
    FcPatternDestroy(pat);

    return ret;
}

bool LibFun::isFontFile(QString path)
{
    QString command = QString("file -i ") + '"' + path + '"';
    QProcess process;
    process.start(command);

    if (!process.waitForFinished()) {
        return false;
    }
    QString ret = process.readAll();
    if (ret.contains("font/sfnt") || ret.contains("application/vnd.ms-opentype")) {
        return true;
    }
    return false;
}
