#include "config.h"

#ifdef CONFIG_XFREETYPE

#include "ystring.h"
#include "ypaint.h"
#include "ypointer.h"
#include "yxapp.h"
#include "intl.h"
#include <stdio.h>
#include <ft2build.h>
#include <X11/Xft/Xft.h>

#ifdef CONFIG_FRIBIDI
        // remove deprecated warnings for now...
        #include <fribidi/fribidi-config.h>
        #if FRIBIDI_USE_GLIB+0
                #include <glib.h>
                #undef G_GNUC_DEPRECATED
                #define G_GNUC_DEPRECATED
        #endif
        #include <fribidi/fribidi.h>
#endif

/******************************************************************************/

class YXftFont : public YFont {
public:
#ifdef CONFIG_I18N
    typedef class YUnicodeString string_t;
    typedef XftChar32 char_t;
#else
    typedef class YLocaleString string_t;
    typedef XftChar8 char_t;
#endif

    YXftFont(mstring name, bool xlfd, bool antialias);
    virtual ~YXftFont();

    bool valid() const override { return (fFontCount > 0); }
    int descent() const override { return fDescent; }
    int ascent() const override { return fAscent; }
    int textWidth(mstring s) const override;
    int textWidth(char const * str, int len) const override;

    int textWidth(string_t const & str) const;
    void drawGlyphs(class Graphics & graphics, int x, int y,
                            char const * str, int len) override;

    bool supports(unsigned utf32char) override;

private:
    struct TextPart {
        XftFont * font;
        size_t length;
        unsigned width;
    };

    TextPart * partitions(char_t * str, size_t len, size_t nparts = 0) const;

    unsigned fFontCount, fAscent, fDescent;
    XftFont ** fFonts;
};

class XftGraphics {
public:
#ifdef CONFIG_I18N
    typedef XftChar32 char_t;

    #define XftDrawString XftDrawString32
    #define XftTextExtents XftTextExtents32
#else
    typedef XftChar8 char_t;

    #define XftTextExtents XftTextExtents8
    #define XftDrawString XftDrawString8
#endif

    static void drawString(Graphics &g, XftFont * font, int x, int y,
                           char_t * str, size_t len)
    {
#ifdef CONFIG_FRIBIDI
        const size_t bufsize = 256;
        char_t buf[bufsize];
        char_t *vis_str = buf;
        asmart<char_t> big;

        if (len >= bufsize) {
            big = new char_t[len+1];
            if (big == nullptr)
                return;
            vis_str = big;
        }

        FriBidiCharType pbase_dir = FRIBIDI_TYPE_N;

        if (fribidi_log2vis(str, len, &pbase_dir, //input
                            vis_str, // output
                            nullptr, nullptr, nullptr // "statistics" that we don't need
                            ))
        {
            str = vis_str;
        }
#endif

        XftDrawString(g.handleXft(), g.color().xftColor(), font,
                      x - g.xorigin(),
                      y - g.yorigin(),
                      str, len);
    }

    static void textExtents(XftFont * font, char_t * str, size_t len,
                            XGlyphInfo & extends) {
        XftTextExtents(xapp->display(), font, str, len, &extends);
    }

};

/******************************************************************************/

YXftFont::YXftFont(mstring name, bool use_xlfd, bool /*antialias*/):
    fFontCount(0), fAscent(0), fDescent(0)
{
    fFontCount = 0;
    mstring s(null), r(null);

    for (s = name; s.splitall(',', &s, &r); s = r) {
        if (s.nonempty())
            fFontCount++;
    }

    XftFont ** fptr(fFonts = new XftFont* [fFontCount]);


    for (s = name; s.splitall(',', &s, &r); s = r) {
        if (s.isEmpty())
            continue;

//    for (char const *s(name); '\0' != *s; s = strnxt(s, ",")) {
        XftFont *& font(*fptr);

        mstring fname = s.trim();
        //char * fname(newstr(s + strspn(s, " \t\r\n"), ","));
        //char * endptr(fname + strlen(fname) - 1);
        //while (endptr > fname && strchr(" \t\r\n", *endptr)) --endptr;
        //endptr[1] = '\0';

        if (use_xlfd) {
            font = XftFontOpenXlfd(xapp->display(), xapp->screen(), fname);
        } else {
            font = XftFontOpenName(xapp->display(), xapp->screen(), fname);
        }

        if (nullptr != font) {
            fAscent = max(fAscent, (unsigned) max(0, font->ascent));
            fDescent = max(fDescent, (unsigned) max(0, font->descent));
            ++fptr;
        } else {
            warn(_("Could not load font \"%s\"."), fname.c_str());
            --fFontCount;
        }
    }

    if (0 == fFontCount) {
        msg("xft: fallback from '%s'", name.c_str());
        XftFont *sans =
            XftFontOpen(xapp->display(), xapp->screen(),
                        XFT_FAMILY, XftTypeString, "sans-serif",
                        XFT_PIXEL_SIZE, XftTypeInteger, 12,
                        NULL);

        if (nullptr != sans) {
            delete[] fFonts;

            fFontCount = 1;
            fFonts = new XftFont* [fFontCount];
            fFonts[0] = sans;

            fAscent = sans->ascent;
            fDescent = sans->descent;
        } else
            warn(_("Loading of fallback font \"%s\" failed."), "sans-serif");
    }
}

YXftFont::~YXftFont() {
    for (unsigned n = 0; n < fFontCount; ++n) {
        // this leaks memory when xapp is destroyed before fonts
        if (xapp != nullptr)
            XftFontClose(xapp->display(), fFonts[n]);
    }
    delete[] fFonts;
}

int YXftFont::textWidth(mstring s) const {
    return textWidth(s.c_str(), s.length());
}

int YXftFont::textWidth(string_t const & text) const {
    char_t * str((char_t *) text.data());
    size_t len(text.length());

    TextPart *parts = partitions(str, len);
    unsigned width(0);

    for (TextPart * p = parts; p && p->length; ++p) width+= p->width;

    delete[] parts;
    return width;
}

int YXftFont::textWidth(char const * str, int len) const {
    return textWidth(string_t(str, len));
}

void YXftFont::drawGlyphs(Graphics & graphics, int x, int y,
                          char const * str, int len) {
    string_t xtext(str, len);
    if (0 == xtext.length()) return;

    int const y0(y - ascent());
    int const gcFn(graphics.function());

    char_t * xstr((char_t *) xtext.data());
    size_t xlen(xtext.length());

    TextPart *parts = partitions(xstr, xlen);
///    unsigned w(0);
///    unsigned const h(height());

///    for (TextPart *p = parts; p && p->length; ++p) w+= p->width;

///    YPixmap *pixmap = new YPixmap(w, h);
///    Graphics canvas(*pixmap, 0, 0);
//    XftGraphics textarea(graphics, xapp->visual(), xapp->colormap());

    switch (gcFn) {
        case GXxor:
///         textarea.drawRect(*YColor::black, 0, 0, w, h);
            break;

        case GXcopy:
///            canvas.copyDrawable(graphics.drawable(),
///                                x - graphics.xorigin(), y0 - graphics.yorigin(), w, h, 0, 0);
            break;
    }


    int xpos(0);
    for (TextPart *p = parts; p && p->length; ++p) {
        if (p->font) {
            XftGraphics::drawString(graphics, p->font,
                                    xpos + x, ascent() + y0,
                                    xstr, p->length);
        }

        xstr += p->length;
        xpos += p->width;
    }

    delete[] parts;

///    graphics.copyDrawable(canvas.drawable(), 0, 0, w, h, x, y0);
///    delete pixmap;
}

bool YXftFont::supports(unsigned utf32char) {
    // be conservative, only report when all font candidates can do it
    for (unsigned i = 0; i < fFontCount; ++i) {
        if (!XftCharExists(xapp->display(), fFonts[i], utf32char))
            return false;
    }
    return true;
}

YXftFont::TextPart * YXftFont::partitions(char_t * str, size_t len,
                                          size_t nparts) const
{
    XGlyphInfo extends;
    XftFont ** lFont(fFonts + fFontCount);
    XftFont ** font(nullptr);
    char_t * c(str);

    if (fFonts == nullptr || fFontCount == 0)
        return nullptr;

    for (char_t * endptr(str + len); c < endptr; ++c) {
        XftFont ** probe(fFonts);

        while (probe < lFont && !XftGlyphExists(xapp->display(), *probe, *c))
            ++probe;

        if (probe != font) {
            if (nullptr != font) {
                TextPart *parts = partitions(c, len - (c - str), nparts + 1);
                parts[nparts].length = (c - str);

                if (font < lFont) {
                    XftGraphics::textExtents(*font, str, (c - str), extends);
                    parts[nparts].font = *font;
                    parts[nparts].width = extends.xOff;
                } else {
                    parts[nparts].font = nullptr;
                    parts[nparts].width = 0;
                    MSG(("glyph not found: %d", *(c - 1)));
                }

                return parts;
            } else
                font = probe;
        }
    }

    TextPart *parts = new TextPart[nparts + 2];
    parts[nparts + 1].font =  nullptr;
    parts[nparts + 1].width = 0;
    parts[nparts + 1].length = 0;
    parts[nparts].length = (c - str);

    if (nullptr != font && font < lFont) {
        XftGraphics::textExtents(*font, str, (c - str), extends);
        parts[nparts].font = *font;
        parts[nparts].width = extends.xOff;
    } else {
        parts[nparts].font = nullptr;
        parts[nparts].width = 0;
    }

    return parts;
}

ref<YFont> getXftFontXlfd(mstring name, bool antialias) {
    ref<YFont> font(new YXftFont(name, true, antialias));
    if (font == null || !font->valid()) {
        msg("failed to load font '%s', trying fallback", name.c_str());
        font.init(new YXftFont("sans-serif:size=12", false, antialias));
        if (font == null || !font->valid()) {
            msg("Could not load fallback Xft font.");
            return null;
        }
    }
    return font;
}

ref<YFont> getXftFont(mstring name, bool antialias) {
    ref<YFont>font(new YXftFont(name, false, antialias));
    if (font == null || !font->valid()) {
        msg("failed to load font '%s', trying fallback", name.c_str());
        font.init(new YXftFont("sans-serif:size=12", false, antialias));
        if (font == null || !font->valid()) {
            msg("Could not load fallback Xft font.");
            return null;
        }
    }
    return font;
}

#endif // CONFIG_XFREETYPE

// vim: set sw=4 ts=4 et:
