// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors: see git history
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <glibmm/markup.h>
#include <glibmm/regex.h>

#include <gtkmm/cellrenderertext.h>

#include <libnrtype/font-instance.h>

#include "font-lister.h"
#include "FontFactory.h"

#include "desktop.h"
#include "desktop-style.h"
#include "document.h"
#include "inkscape.h"
#include "preferences.h"

#include "object/sp-object.h"

// Following are needed to limit the source of updating font data to text and containers.
#include "object/sp-root.h"
#include "object/sp-object-group.h"
#include "object/sp-anchor.h"
#include "object/sp-text.h"
#include "object/sp-tspan.h"
#include "object/sp-textpath.h"
#include "object/sp-tref.h"
#include "object/sp-flowtext.h"
#include "object/sp-flowdiv.h"

#include "xml/repr.h"


//#define DEBUG_FONT

// CSS dictates that font family names are case insensitive.
// This should really implement full Unicode case unfolding.
bool familyNamesAreEqual(const Glib::ustring &a, const Glib::ustring &b)
{
    return (a.casefold().compare(b.casefold()) == 0);
}

static const char* sp_font_family_get_name(PangoFontFamily* family)
{
    const char* name = pango_font_family_get_name(family);
    if (strncmp(name, "Sans", 4) == 0 && strlen(name) == 4)
        return "sans-serif";
    if (strncmp(name, "Serif", 5) == 0 && strlen(name) == 5)
        return "serif";
    if (strncmp(name, "Monospace", 9) == 0 && strlen(name) == 9)
        return "monospace";
    return name;
}

namespace Inkscape {

FontLister::FontLister()
    : current_family_row (0)
    , current_family ("sans-serif")
    , current_style ("Normal")
    , block (false)
{
    font_list_store = Gtk::ListStore::create(FontList);
    font_list_store->freeze_notify();
    
    /* Create default styles for use when font-family is unknown on system. */
    default_styles = g_list_append(nullptr, new StyleNames("Normal"));
    default_styles = g_list_append(default_styles, new StyleNames("Italic"));
    default_styles = g_list_append(default_styles, new StyleNames("Bold"));
    default_styles = g_list_append(default_styles, new StyleNames("Bold Italic"));

    // Get sorted font families from Pango
    std::vector<PangoFontFamily *> familyVector;
    font_factory::Default()->GetUIFamilies(familyVector);

    // Traverse through the family names and set up the list store
    for (auto & i : familyVector) {
        const char* displayName = sp_font_family_get_name(i);
        
        if (displayName == nullptr || *displayName == '\0') {
            continue;
        }
        
        Glib::ustring familyName = displayName;
        if (!familyName.empty()) {
            Gtk::TreeModel::iterator treeModelIter = font_list_store->append();
            (*treeModelIter)[FontList.family] = familyName;

            // we don't set this now (too slow) but the style will be cached if the user 
            // ever decides to use this font
            (*treeModelIter)[FontList.styles] = NULL;
            // store the pango representation for generating the style
            (*treeModelIter)[FontList.pango_family] = i;
            (*treeModelIter)[FontList.onSystem] = true;
        }
    }

    font_list_store->thaw_notify();

    style_list_store = Gtk::ListStore::create(FontStyleList);

    // Initialize style store with defaults
    style_list_store->freeze_notify();
    style_list_store->clear();
    for (GList *l = default_styles; l; l = l->next) {
        Gtk::TreeModel::iterator treeModelIter = style_list_store->append();
        (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)l->data)->CssName;
        (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)l->data)->DisplayName;
    }
    style_list_store->thaw_notify();
}

FontLister::~FontLister()
{
    // Delete default_styles
    for (GList *l = default_styles; l; l = l->next) {
        delete ((StyleNames *)l->data);
    }

    // Delete other styles
    Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
    while (iter != font_list_store->children().end()) {
        Gtk::TreeModel::Row row = *iter;
        GList *styles = row[FontList.styles];
        for (GList *l = styles; l; l = l->next) {
            delete ((StyleNames *)l->data);
        }
        ++iter;
    }
}

FontLister *FontLister::get_instance()
{
    static Inkscape::FontLister *instance = new Inkscape::FontLister();
    return instance;
}

// To do: remove model (not needed for C++ version).
// Ensures the style list for a particular family has been created.
void FontLister::ensureRowStyles(Glib::RefPtr<Gtk::TreeModel> model, Gtk::TreeModel::iterator const iter)
{
    Gtk::TreeModel::Row row = *iter;
    if (!row[FontList.styles]) {
        if (row[FontList.pango_family]) {
            row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
        } else {
            row[FontList.styles] = default_styles;
        }
    }
}

Glib::ustring FontLister::get_font_family_markup(Gtk::TreeIter const &iter)
{
    Gtk::TreeModel::Row row = *iter;

    Glib::ustring family = row[FontList.family];
    bool onSystem        = row[FontList.onSystem];

    Glib::ustring family_escaped = Glib::Markup::escape_text( family );
    Glib::ustring markup;

    if (!onSystem) {
        markup = "<span foreground='darkblue'>";

        // See if font-family is on system (separately for each family in font stack).
        std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", family);

        for (auto token: tokens) {
            bool found = false;
            Gtk::TreeModel::Children children = get_font_list()->children();
            for (auto iter2: children) {
                Gtk::TreeModel::Row row2 = *iter2;
                Glib::ustring family2 = row2[FontList.family];
                bool onSystem2        = row2[FontList.onSystem];
                if (onSystem2 && familyNamesAreEqual(token, family2)) {
                    found = true;
                    break;
                }
            }

            if (found) {
                markup += Glib::Markup::escape_text (token);
                markup += ", ";
            } else {
                markup += "<span strikethrough=\"true\" strikethrough_color=\"red\">";
                markup += Glib::Markup::escape_text (token);
                markup += "</span>";
                markup += ", ";
            }
        }

        // Remove extra comma and space from end.
        if (markup.size() >= 2) {
            markup.resize(markup.size() - 2);
        }
        markup += "</span>";

    } else {
        markup = family_escaped;
    }

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    int show_sample = prefs->getInt("/tools/text/show_sample_in_list", 1);
    if (show_sample) {

        Glib::ustring sample = prefs->getString("/tools/text/font_sample");

        markup += "  <span foreground='gray' font_family='";
        markup += family_escaped;
        markup += "'>";
        markup += sample;
        markup += "</span>";
    }

    // std::cout << "Markup: " << markup << std::endl;
    return markup;
}

// Example of how to use "foreach_iter"
// bool
// FontLister::print_document_font( const Gtk::TreeModel::iterator &iter ) {
//   Gtk::TreeModel::Row row = *iter;
//   if( !row[FontList.onSystem] ) {
// 	   std::cout << " Not on system: " << row[FontList.family] << std::endl;
// 	   return false;
//   }
//   return true;
// }
// font_list_store->foreach_iter( sigc::mem_fun(*this, &FontLister::print_document_font ));

/* Used to insert a font that was not in the document and not on the system into the font list. */
void FontLister::insert_font_family(Glib::ustring new_family)
{
    GList *styles = default_styles;

    /* In case this is a fallback list, check if first font-family on system. */
    std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", new_family);
    if (!tokens.empty() && !tokens[0].empty()) {
        Gtk::TreeModel::iterator iter2 = font_list_store->get_iter("0");
        while (iter2 != font_list_store->children().end()) {
            Gtk::TreeModel::Row row = *iter2;
            if (row[FontList.onSystem] && familyNamesAreEqual(tokens[0], row[FontList.family])) {
                if (!row[FontList.styles]) {
                    row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
                }
                styles = row[FontList.styles];
                break;
            }
            ++iter2;
        }
    }

    Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
    (*treeModelIter)[FontList.family] = new_family;
    (*treeModelIter)[FontList.styles] = styles;
    (*treeModelIter)[FontList.onSystem] = false;
    (*treeModelIter)[FontList.pango_family] = NULL;

    current_family = new_family;
    current_family_row = 0;
    current_style = "Normal";

    emit_update();
}

void FontLister::update_font_list(SPDocument *document)
{
    SPObject *root = document->getRoot();
    if (!root) {
        return;
    }

    font_list_store->freeze_notify();

    /* Find if current row is in document or system part of list */
    gboolean row_is_system = false;
    if (current_family_row > -1) {
        Gtk::TreePath path;
        path.push_back(current_family_row);
        Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
        if (iter) {
            row_is_system = (*iter)[FontList.onSystem];
            // std::cout << "  In:  row: " << current_family_row << "  " << (*iter)[FontList.family] << std::endl;
        }
    }

    /* Clear all old document font-family entries */
    Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
    while (iter != font_list_store->children().end()) {
        Gtk::TreeModel::Row row = *iter;
        if (!row[FontList.onSystem]) {
            // std::cout << " Not on system: " << row[FontList.family] << std::endl;
            iter = font_list_store->erase(iter);
        } else {
            // std::cout << " First on system: " << row[FontList.family] << std::endl;
            break;
        }
    }

    /* Get "font-family"s and styles used in document. */
    std::map<Glib::ustring, std::set<Glib::ustring>> font_data;
    update_font_data_recursive(*root, font_data);

    /* Insert separator */
    if (!font_data.empty()) {
        Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
        (*treeModelIter)[FontList.family] = "#";
        (*treeModelIter)[FontList.onSystem] = false;
    }

    /* Insert font-family's in document. */
    for (auto i: font_data) {

        GList *styles = default_styles;

        /* See if font-family (or first in fallback list) is on system. If so, get styles. */
        std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", i.first);
        if (!tokens.empty() && !tokens[0].empty()) {

            Gtk::TreeModel::iterator iter2 = font_list_store->get_iter("0");
            while (iter2 != font_list_store->children().end()) {
                Gtk::TreeModel::Row row = *iter2;
                if (row[FontList.onSystem] && familyNamesAreEqual(tokens[0], row[FontList.family])) {
                    // Found font on system, set style list to system font style list.
                    if (!row[FontList.styles]) {
                        row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
                    }

                    // Add new styles (from 'font-variation-settings', these are not include in GetUIStyles()).
                    for (auto j: i.second) {
                        // std::cout << "  Inserting: " << j << std::endl;

                        bool exists = false;
                        for(GList *temp = row[FontList.styles]; temp; temp = temp->next) {
                            if( ((StyleNames*)temp->data)->CssName.compare( j ) == 0 ) {
                                exists = true;
                                break;
                            }
                        }

                        if (!exists) {
                            row[FontList.styles] = g_list_append(row[FontList.styles], new StyleNames(j,j));
                        }
                    }

                    styles = row[FontList.styles];
                    break;
                }
                ++iter2;
            }
        }

        Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
        (*treeModelIter)[FontList.family] = reinterpret_cast<const char *>(g_strdup((i.first).c_str()));
        (*treeModelIter)[FontList.styles] = styles;
        (*treeModelIter)[FontList.onSystem] = false;    // false if document font
        (*treeModelIter)[FontList.pango_family] = NULL; // CHECK ME (set to pango_family if on system?)

    }

    /* Now we do a song and dance to find the correct row as the row corresponding
     * to the current_family may have changed. We can't simply search for the
     * family name in the list since it can occur twice, once in the document
     * font family part and once in the system font family part. Above we determined
     * which part it is in.
     */
    if (current_family_row > -1) {
        int start = 0;
        if (row_is_system)
            start = font_data.size();
        int length = font_list_store->children().size();
        for (int i = 0; i < length; ++i) {
            int row = i + start;
            if (row >= length)
                row -= length;
            Gtk::TreePath path;
            path.push_back(row);
            Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
            if (iter) {
                if (familyNamesAreEqual(current_family, (*iter)[FontList.family])) {
                    current_family_row = row;
                    break;
                }
            }
        }
    }
    // std::cout << "  Out: row: " << current_family_row << "  " << current_family << std::endl;

    font_list_store->thaw_notify();
    emit_update();
}

void FontLister::update_font_data_recursive(SPObject& r, std::map<Glib::ustring, std::set<Glib::ustring>> &font_data)
{
    // Text nodes (i.e. the content of <text> or <tspan>) do not have their own style.
    if (r.getRepr()->type() == Inkscape::XML::TEXT_NODE) {
        return;
    }

    PangoFontDescription* descr = ink_font_description_from_style( r.style );
    const gchar* font_family_char = pango_font_description_get_family(descr);
    if (font_family_char) {
        Glib::ustring font_family(font_family_char);
        pango_font_description_unset_fields( descr, PANGO_FONT_MASK_FAMILY);
        Glib::ustring font_style = pango_font_description_to_string(descr);

        if (!font_family.empty() && !font_style.empty()) {
            font_data[font_family].insert(font_style);
        }
    } else {
        // We're starting from root and looking at all elements... we should probably white list text/containers.
        std::cerr << "FontLister::update_font_data_recursive: descr without font family! " << (r.getId()?r.getId():"null") << std::endl;
    }
    pango_font_description_free(descr);

    if (SP_IS_GROUP(&r)    ||
        SP_IS_ANCHOR(&r)   ||
        SP_IS_ROOT(&r)     ||
        SP_IS_TEXT(&r)     ||
        SP_IS_TSPAN(&r)    ||
        SP_IS_TEXTPATH(&r) ||
        SP_IS_TREF(&r)     ||
        SP_IS_FLOWTEXT(&r) ||
        SP_IS_FLOWDIV(&r)  ||
        SP_IS_FLOWPARA(&r) ||
        SP_IS_FLOWLINE(&r)) {
        for (auto& child: r.children) {
            update_font_data_recursive(child, font_data);
        }
    }
}

void FontLister::emit_update()
{
    if (block) return;

    block = true;
    update_signal.emit ();
    block = false;
}


Glib::ustring FontLister::canonize_fontspec(Glib::ustring fontspec)
{

    // Pass fontspec to and back from Pango to get a the fontspec in
    // canonical form.  -inkscape-font-specification relies on the
    // Pango constructed fontspec not changing form. If it does,
    // this is the place to fix it.
    PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
    gchar *canonized = pango_font_description_to_string(descr);
    Glib::ustring Canonized = canonized;
    g_free(canonized);
    pango_font_description_free(descr);

    // Pango canonized strings remove space after comma between family names. Put it back.
    // But don't add a space inside a 'font-variation-settings' declaration (this breaks Pango).
    size_t i = 0;
    while ((i = Canonized.find_first_of(",@", i)) != std::string::npos ) {
        if (Canonized[i] == '@') // Found start of 'font-variation-settings'.
            break;
        Canonized.replace(i, 1, ", ");
        i += 2;
    }

    return Canonized;
}

Glib::ustring FontLister::system_fontspec(Glib::ustring fontspec)
{
    // Find what Pango thinks is the closest match.
    Glib::ustring out = fontspec;

    PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
    font_instance *res = (font_factory::Default())->Face(descr);
    if (res && res->pFont) {
        PangoFontDescription *nFaceDesc = pango_font_describe(res->pFont);
        out = sp_font_description_get_family(nFaceDesc);
    }
    pango_font_description_free(descr);

    return out;
}

std::pair<Glib::ustring, Glib::ustring> FontLister::ui_from_fontspec(Glib::ustring fontspec)
{
    PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
    const gchar *family = pango_font_description_get_family(descr);
    if (!family)
        family = "sans-serif";
    Glib::ustring Family = family;

    // PANGO BUG...
    //   A font spec of Delicious, 500 Italic should result in a family of 'Delicious'
    //   and a style of 'Medium Italic'. It results instead with: a family of
    //   'Delicious, 500' with a style of 'Medium Italic'. We chop of any weight numbers
    //   at the end of the family:  match ",[1-9]00^".
    Glib::RefPtr<Glib::Regex> weight = Glib::Regex::create(",[1-9]00$");
    Family = weight->replace(Family, 0, "", Glib::REGEX_MATCH_PARTIAL);

    // Pango canonized strings remove space after comma between family names. Put it back.
    size_t i = 0;
    while ((i = Family.find(",", i)) != std::string::npos) {
        Family.replace(i, 1, ", ");
        i += 2;
    }

    pango_font_description_unset_fields(descr, PANGO_FONT_MASK_FAMILY);
    gchar *style = pango_font_description_to_string(descr);
    Glib::ustring Style = style;
    pango_font_description_free(descr);
    g_free(style);

    return std::make_pair(Family, Style);
}

std::pair<Glib::ustring, Glib::ustring> FontLister::selection_update()
{
#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister::selection_update: entrance" << std::endl;
#endif
    // Get fontspec from a selection, preferences, or thin air.
    Glib::ustring fontspec;
    SPStyle query(SP_ACTIVE_DOCUMENT);

    // Directly from stored font specification.
    int result =
        sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);

    //std::cout << "  Attempting selected style" << std::endl;
    if (result != QUERY_STYLE_NOTHING && query.font_specification.set) {
        fontspec = query.font_specification.value;
        //std::cout << "   fontspec from query   :" << fontspec << ":" << std::endl;
    }

    // From style
    if (fontspec.empty()) {
        //std::cout << "  Attempting desktop style" << std::endl;
        int rfamily = sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTFAMILY);
        int rstyle = sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);

        // Must have text in selection
        if (rfamily != QUERY_STYLE_NOTHING && rstyle != QUERY_STYLE_NOTHING) {
            fontspec = fontspec_from_style(&query);
        }
        //std::cout << "   fontspec from style   :" << fontspec << ":" << std::endl;
    }

    // From preferences
    if (fontspec.empty()) {
        //std::cout << "  Attempting preferences" << std::endl;
        query.readFromPrefs("/tools/text");
        fontspec = fontspec_from_style(&query);
        //std::cout << "   fontspec from prefs   :" << fontspec << ":" << std::endl;
    }

    // From thin air
    if (fontspec.empty()) {
        //std::cout << "  Attempting thin air" << std::endl;
        fontspec = current_family + ", " + current_style;
        //std::cout << "   fontspec from thin air   :" << fontspec << ":" << std::endl;
    }

    std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(fontspec);
    set_font_family(ui.first);
    set_font_style(ui.second);

#ifdef DEBUG_FONT
    std::cout << "   family_row:           :" << current_family_row << ":" << std::endl;
    std::cout << "   family:               :" << current_family << ":" << std::endl;
    std::cout << "   style:                :" << current_style << ":" << std::endl;
    std::cout << "FontLister::selection_update: exit" << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif

    emit_update();

    return std::make_pair(current_family, current_style);
}


// Set fontspec. If check is false, best style match will not be done.
void FontLister::set_fontspec(Glib::ustring new_fontspec, bool /*check*/)
{
    std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(new_fontspec);
    Glib::ustring new_family = ui.first;
    Glib::ustring new_style = ui.second;

#ifdef DEBUG_FONT
    std::cout << "FontLister::set_fontspec: family: " << new_family
              << "   style:" << new_style << std::endl;
#endif

    set_font_family(new_family, false);
    set_font_style(new_style);

    emit_update();
}


// TODO: use to determine font-selector best style
// TODO: create new function new_font_family(Gtk::TreeModel::iterator iter)
std::pair<Glib::ustring, Glib::ustring> FontLister::new_font_family(Glib::ustring new_family, bool /*check_style*/)
{
#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister::new_font_family: " << new_family << std::endl;
#endif

    // No need to do anything if new family is same as old family.
    if (familyNamesAreEqual(new_family, current_family)) {
#ifdef DEBUG_FONT
        std::cout << "FontLister::new_font_family: exit: no change in family." << std::endl;
        std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif
        return std::make_pair(current_family, current_style);
    }

    // We need to do two things:
    // 1. Update style list for new family.
    // 2. Select best valid style match to old style.

    // For finding style list, use list of first family in font-family list.
    GList *styles = nullptr;
    Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
    while (iter != font_list_store->children().end()) {

        Gtk::TreeModel::Row row = *iter;

        if (familyNamesAreEqual(new_family, row[FontList.family])) {
            if (!row[FontList.styles]) {
                row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
            }
            styles = row[FontList.styles];
            break;
        }
        ++iter;
    }

    // Newly typed in font-family may not yet be in list... use default list.
    // TODO: if font-family is list, check if first family in list is on system
    // and set style accordingly.
    if (styles == nullptr) {
        styles = default_styles;
    }

    // Update style list.
    style_list_store->freeze_notify();
    style_list_store->clear();

    for (GList *l = styles; l; l = l->next) {
        Gtk::TreeModel::iterator treeModelIter = style_list_store->append();
        (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)l->data)->CssName;
        (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)l->data)->DisplayName;
    }

    style_list_store->thaw_notify();

    // Find best match to the style from the old font-family to the
    // styles available with the new font.
    // TODO: Maybe check if an exact match exists before using Pango.
    Glib::ustring best_style = get_best_style_match(new_family, current_style);

#ifdef DEBUG_FONT
    std::cout << "FontLister::new_font_family: exit: " << new_family << " " << best_style << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif
    return std::make_pair(new_family, best_style);
}

std::pair<Glib::ustring, Glib::ustring> FontLister::set_font_family(Glib::ustring new_family, bool check_style)
{

#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister::set_font_family: " << new_family << std::endl;
#endif

    std::pair<Glib::ustring, Glib::ustring> ui = new_font_family(new_family, check_style);
    current_family = ui.first;
    current_style = ui.second;

#ifdef DEBUG_FONT
    std::cout << "   family_row:           :" << current_family_row << ":" << std::endl;
    std::cout << "   family:               :" << current_family << ":" << std::endl;
    std::cout << "   style:                :" << current_style << ":" << std::endl;
    std::cout << "FontLister::set_font_family: end" << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif

    emit_update();

    return ui;
}


std::pair<Glib::ustring, Glib::ustring> FontLister::set_font_family(int row, bool check_style)
{

#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister::set_font_family( row ): " << row << std::endl;
#endif

    current_family_row = row;
    Gtk::TreePath path;
    path.push_back(row);
    Glib::ustring new_family = current_family;
    Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
    if (iter) {
        new_family = (*iter)[FontList.family];
    }

    std::pair<Glib::ustring, Glib::ustring> ui = set_font_family(new_family, check_style);

#ifdef DEBUG_FONT
    std::cout << "FontLister::set_font_family( row ): end" << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif
    return ui;
}


void FontLister::set_font_style(Glib::ustring new_style)
{

// TODO: Validate input using Pango. If Pango doesn't recognize a style it will
// attach the "invalid" style to the font-family.

#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister:set_font_style: " << new_style << std::endl;
#endif
    current_style = new_style;

#ifdef DEBUG_FONT
    std::cout << "   family:                " << current_family << std::endl;
    std::cout << "   style:                 " << current_style << std::endl;
    std::cout << "FontLister::set_font_style: end" << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif

    emit_update();
}


// We do this ourselves as we can't rely on FontFactory.
void FontLister::fill_css(SPCSSAttr *css, Glib::ustring fontspec)
{
    if (fontspec.empty()) {
        fontspec = get_fontspec();
    }

    std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(fontspec);

    Glib::ustring family = ui.first;


    // Font spec is single quoted... for the moment
    Glib::ustring fontspec_quoted(fontspec);
    css_quote(fontspec_quoted);
    sp_repr_css_set_property(css, "-inkscape-font-specification", fontspec_quoted.c_str());

    // Font families needs to be properly quoted in CSS (used unquoted in font-lister)
    css_font_family_quote(family);
    sp_repr_css_set_property(css, "font-family", family.c_str());

    PangoFontDescription *desc = pango_font_description_from_string(fontspec.c_str());
    PangoWeight weight = pango_font_description_get_weight(desc);
    switch (weight) {
        case PANGO_WEIGHT_THIN:
            sp_repr_css_set_property(css, "font-weight", "100");
            break;
        case PANGO_WEIGHT_ULTRALIGHT:
            sp_repr_css_set_property(css, "font-weight", "200");
            break;
        case PANGO_WEIGHT_LIGHT:
            sp_repr_css_set_property(css, "font-weight", "300");
            break;
#if PANGO_VERSION_CHECK(1,36,6)
        case PANGO_WEIGHT_SEMILIGHT:
            sp_repr_css_set_property(css, "font-weight", "350");
            break;
#endif
        case PANGO_WEIGHT_BOOK:
            sp_repr_css_set_property(css, "font-weight", "380");
            break;
        case PANGO_WEIGHT_NORMAL:
            sp_repr_css_set_property(css, "font-weight", "normal");
            break;
        case PANGO_WEIGHT_MEDIUM:
            sp_repr_css_set_property(css, "font-weight", "500");
            break;
        case PANGO_WEIGHT_SEMIBOLD:
            sp_repr_css_set_property(css, "font-weight", "600");
            break;
        case PANGO_WEIGHT_BOLD:
            sp_repr_css_set_property(css, "font-weight", "bold");
            break;
        case PANGO_WEIGHT_ULTRABOLD:
            sp_repr_css_set_property(css, "font-weight", "800");
            break;
        case PANGO_WEIGHT_HEAVY:
            sp_repr_css_set_property(css, "font-weight", "900");
            break;
        case PANGO_WEIGHT_ULTRAHEAVY:
            sp_repr_css_set_property(css, "font-weight", "1000");
            break;
    }

    PangoStyle style = pango_font_description_get_style(desc);
    switch (style) {
        case PANGO_STYLE_NORMAL:
            sp_repr_css_set_property(css, "font-style", "normal");
            break;
        case PANGO_STYLE_OBLIQUE:
            sp_repr_css_set_property(css, "font-style", "oblique");
            break;
        case PANGO_STYLE_ITALIC:
            sp_repr_css_set_property(css, "font-style", "italic");
            break;
    }

    PangoStretch stretch = pango_font_description_get_stretch(desc);
    switch (stretch) {
        case PANGO_STRETCH_ULTRA_CONDENSED:
            sp_repr_css_set_property(css, "font-stretch", "ultra-condensed");
            break;
        case PANGO_STRETCH_EXTRA_CONDENSED:
            sp_repr_css_set_property(css, "font-stretch", "extra-condensed");
            break;
        case PANGO_STRETCH_CONDENSED:
            sp_repr_css_set_property(css, "font-stretch", "condensed");
            break;
        case PANGO_STRETCH_SEMI_CONDENSED:
            sp_repr_css_set_property(css, "font-stretch", "semi-condensed");
            break;
        case PANGO_STRETCH_NORMAL:
            sp_repr_css_set_property(css, "font-stretch", "normal");
            break;
        case PANGO_STRETCH_SEMI_EXPANDED:
            sp_repr_css_set_property(css, "font-stretch", "semi-expanded");
            break;
        case PANGO_STRETCH_EXPANDED:
            sp_repr_css_set_property(css, "font-stretch", "expanded");
            break;
        case PANGO_STRETCH_EXTRA_EXPANDED:
            sp_repr_css_set_property(css, "font-stretch", "extra-expanded");
            break;
        case PANGO_STRETCH_ULTRA_EXPANDED:
            sp_repr_css_set_property(css, "font-stretch", "ultra-expanded");
            break;
    }

    PangoVariant variant = pango_font_description_get_variant(desc);
    switch (variant) {
        case PANGO_VARIANT_NORMAL:
            sp_repr_css_set_property(css, "font-variant", "normal");
            break;
        case PANGO_VARIANT_SMALL_CAPS:
            sp_repr_css_set_property(css, "font-variant", "small-caps");
            break;
    }

#if PANGO_VERSION_CHECK(1,41,1)
    // Convert Pango variations string to CSS format
    const char* str = pango_font_description_get_variations(desc);

    std::string variations;

    if (str) {

        std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", str);

        Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("(\\w{4})=([-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?)");
        Glib::MatchInfo matchInfo;
        for (auto token: tokens) {
            regex->match(token, matchInfo);
            if (matchInfo.matches()) {
                variations += "'";
                variations += matchInfo.fetch(1);
                variations += "' ";
                variations += matchInfo.fetch(2);
                variations += ", ";
            }
        }
        if (variations.length() >= 2) { // Remove last comma/space
            variations.pop_back();
            variations.pop_back();
        }
    }

    if (!variations.empty()) {
        sp_repr_css_set_property(css, "font-variation-settings", variations.c_str());
    } else {
        sp_repr_css_unset_property(css,  "font-variation-settings" );
    }
#endif
}


Glib::ustring FontLister::fontspec_from_style(SPStyle *style)
{

    PangoFontDescription* descr = ink_font_description_from_style( style );
    Glib::ustring fontspec = pango_font_description_to_string( descr );
    pango_font_description_free(descr);

    //std::cout << "FontLister:fontspec_from_style: " << fontspec << std::endl;

    return fontspec;
}


Gtk::TreeModel::Row FontLister::get_row_for_font(Glib::ustring family)
{

    Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
    while (iter != font_list_store->children().end()) {

        Gtk::TreeModel::Row row = *iter;

        if (familyNamesAreEqual(family, row[FontList.family])) {
            return row;
        }

        ++iter;
    }

    throw FAMILY_NOT_FOUND;
}

Gtk::TreePath FontLister::get_path_for_font(Glib::ustring family)
{
    return font_list_store->get_path(get_row_for_font(family));
}

bool FontLister::is_path_for_font(Gtk::TreePath path, Glib::ustring family)
{
    Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
    if (iter) {
        return familyNamesAreEqual(family, (*iter)[FontList.family]);
    }

    return false;
}

Gtk::TreeModel::Row FontLister::get_row_for_style(Glib::ustring style)
{

    Gtk::TreeModel::iterator iter = style_list_store->get_iter("0");
    while (iter != style_list_store->children().end()) {

        Gtk::TreeModel::Row row = *iter;

        if (familyNamesAreEqual(style, row[FontStyleList.cssStyle])) {
            return row;
        }

        ++iter;
    }

    throw STYLE_NOT_FOUND;
}

static gint compute_distance(const PangoFontDescription *a, const PangoFontDescription *b)
{

    // Weight: multiples of 100
    gint distance = abs(pango_font_description_get_weight(a) -
                        pango_font_description_get_weight(b));

    distance += 10000 * abs(pango_font_description_get_stretch(a) -
                            pango_font_description_get_stretch(b));

    PangoStyle style_a = pango_font_description_get_style(a);
    PangoStyle style_b = pango_font_description_get_style(b);
    if (style_a != style_b) {
        if ((style_a == PANGO_STYLE_OBLIQUE && style_b == PANGO_STYLE_ITALIC) ||
            (style_b == PANGO_STYLE_OBLIQUE && style_a == PANGO_STYLE_ITALIC)) {
            distance += 1000; // Oblique and italic are almost the same
        } else {
            distance += 100000; // Normal vs oblique/italic, not so similar
        }
    }

    // Normal vs small-caps
    distance += 1000000 * abs(pango_font_description_get_variant(a) -
                              pango_font_description_get_variant(b));
    return distance;
}

// This is inspired by pango_font_description_better_match, but that routine
// always returns false if variant or stretch are different. This means, for
// example, that PT Sans Narrow with style Bold Condensed is never matched
// to another font-family with Bold style.
gboolean font_description_better_match(PangoFontDescription *target, PangoFontDescription *old_desc, PangoFontDescription *new_desc)
{
    if (old_desc == nullptr)
        return true;
    if (new_desc == nullptr)
        return false;

    int old_distance = compute_distance(target, old_desc);
    int new_distance = compute_distance(target, new_desc);
    //std::cout << "font_description_better_match: old: " << old_distance << std::endl;
    //std::cout << "                               new: " << new_distance << std::endl;

    return (new_distance < old_distance);
}

// void
// font_description_dump( PangoFontDescription* target ) {
//   std::cout << "  Font:      " << pango_font_description_to_string( target ) << std::endl;
//   std::cout << "    style:   " << pango_font_description_get_style(   target ) << std::endl;
//   std::cout << "    weight:  " << pango_font_description_get_weight(  target ) << std::endl;
//   std::cout << "    variant: " << pango_font_description_get_variant( target ) << std::endl;
//   std::cout << "    stretch: " << pango_font_description_get_stretch( target ) << std::endl;
//   std::cout << "    gravity: " << pango_font_description_get_gravity( target ) << std::endl;
// }

/* Returns style string */
// TODO: Remove or turn into function to be used by new_font_family.
Glib::ustring FontLister::get_best_style_match(Glib::ustring family, Glib::ustring target_style)
{

#ifdef DEBUG_FONT
    std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
    std::cout << "FontLister::get_best_style_match: " << family << " : " << target_style << std::endl;
#endif

    Glib::ustring fontspec = family + ", " + target_style;

    Gtk::TreeModel::Row row;
    try
    {
        row = get_row_for_font(family);
    }
    catch (...)
    {
        std::cerr << "FontLister::get_best_style_match(): can't find family: " << family << std::endl;
        return (target_style);
    }

    PangoFontDescription *target = pango_font_description_from_string(fontspec.c_str());
    PangoFontDescription *best = nullptr;

    //font_description_dump( target );

    GList *styles = default_styles;
    if (row[FontList.onSystem] && !row[FontList.styles]) {
        row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
        styles = row[FontList.styles];
    }

    for (GList *l = styles; l; l = l->next) {
        Glib::ustring fontspec = family + ", " + ((StyleNames *)l->data)->CssName;
        PangoFontDescription *candidate = pango_font_description_from_string(fontspec.c_str());
        //font_description_dump( candidate );
        //std::cout << "           " << font_description_better_match( target, best, candidate ) << std::endl;
        if (font_description_better_match(target, best, candidate)) {
            pango_font_description_free(best);
            best = candidate;
            //std::cout << "  ... better: " << std::endl;
        } else {
            pango_font_description_free(candidate);
            //std::cout << "  ... not better: " << std::endl;
        }
    }

    Glib::ustring best_style = target_style;
    if (best) {
        pango_font_description_unset_fields(best, PANGO_FONT_MASK_FAMILY);
        best_style = pango_font_description_to_string(best);
    }

    if (target)
        pango_font_description_free(target);
    if (best)
        pango_font_description_free(best);


#ifdef DEBUG_FONT
    std::cout << "  Returning: " << best_style << std::endl;
    std::cout << "FontLister::get_best_style_match: exit" << std::endl;
    std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
#endif
    return best_style;
}

const Glib::RefPtr<Gtk::ListStore> FontLister::get_font_list() const
{
    return font_list_store;
}

const Glib::RefPtr<Gtk::ListStore> FontLister::get_style_list() const
{
    return style_list_store;
}

} // namespace Inkscape

// Helper functions

// Separator function (if true, a separator will be drawn)
bool font_lister_separator_func(const Glib::RefPtr<Gtk::TreeModel>& model,
                                const Gtk::TreeModel::iterator& iter) {

    // Of what use is 'model', can we avoid using font_lister?
    Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
    Gtk::TreeModel::Row row = *iter;
    Glib::ustring entry = row[font_lister->FontList.family];
    return entry == "#";
}

// Needed until Text toolbar updated
gboolean font_lister_separator_func2(GtkTreeModel *model, GtkTreeIter *iter, gpointer /*data*/)
{
    gchar *text = nullptr;
    gtk_tree_model_get(model, iter, 0, &text, -1); // Column 0: FontList.family
    return (text && strcmp(text, "#") == 0);
}

// Draw system fonts in dark blue, missing fonts with red strikeout.
// Used by both FontSelector and Text toolbar.
void font_lister_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
{
    Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
    Glib::ustring markup = font_lister->get_font_family_markup(iter);
    renderer->set_property("markup", markup);
}

// Needed until Text toolbar updated
void font_lister_cell_data_func2(GtkCellLayout * /*cell_layout*/,
                                GtkCellRenderer *cell,
                                GtkTreeModel *model,
                                GtkTreeIter *iter,
                                gpointer /*data*/)
{
    gchar *family;
    gboolean onSystem = false;
    gtk_tree_model_get(model, iter, 0, &family, 2, &onSystem, -1);
    gchar* family_escaped = g_markup_escape_text(family, -1);
    //g_free(family);
    Glib::ustring markup;

    if (!onSystem) {
        markup = "<span foreground='darkblue'>";

        /* See if font-family on system */
        std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", family);
        for (auto token : tokens) {

            GtkTreeIter iter;
            gboolean valid;
            gchar *family = nullptr;
            gboolean onSystem = true;
            gboolean found = false;
            for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter);
                 valid;
                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter)) {

                gtk_tree_model_get(model, &iter, 0, &family, 2, &onSystem, -1);
                if (onSystem && familyNamesAreEqual(token, family)) {
                    found = true;
                    break;
                }
            }
            if (found) {
                markup += g_markup_escape_text(token.c_str(), -1);
                markup += ", ";
            } else {
                markup += "<span strikethrough=\"true\" strikethrough_color=\"red\">";
                markup += g_markup_escape_text(token.c_str(), -1);
                markup += "</span>";
                markup += ", ";
            }
        }
        // Remove extra comma and space from end.
        if (markup.size() >= 2) {
            markup.resize(markup.size() - 2);
        }
        markup += "</span>";
        // std::cout << markup << std::endl;
    } else {
        markup = family_escaped;
    }

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    int show_sample = prefs->getInt("/tools/text/show_sample_in_list", 1);
    if (show_sample) {

        Glib::ustring sample = prefs->getString("/tools/text/font_sample");
        gchar* sample_escaped = g_markup_escape_text(sample.data(), -1);

        markup += "  <span foreground='gray' font_family='";
        markup += family_escaped;
        markup += "'>";
        markup += sample_escaped;
        markup += "</span>";
        g_free(sample_escaped);
    }

    g_object_set(G_OBJECT(cell), "markup", markup.c_str(), NULL);
    g_free(family_escaped);
}

// Draw Face name with face style.
void font_lister_style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
{
    Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
    Gtk::TreeModel::Row row = *iter;

    Glib::ustring family = font_lister->get_font_family();
    Glib::ustring style  = row[font_lister->FontStyleList.cssStyle];

    Glib::ustring style_escaped  = Glib::Markup::escape_text( style );
    Glib::ustring font_desc = family + ", " + style;
    Glib::ustring markup;

    markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>";
    std::cout << "  markup: " << markup << std::endl;

    renderer->set_property("markup", markup);
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
