// Font.cc
// Copyright (c) 2002 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include "Font.hh"
#include "FontImp.hh"
#include "StringUtil.hh"
#include "stringstream.hh"
#include "App.hh"
#include "GContext.hh"
#include "XFontImp.hh"

// for antialias
#ifdef USE_XFT
#include "XftFontImp.hh"
#endif // USE_XFT

// for multibyte support
#ifdef USE_XMB
#include "XmbFontImp.hh"
#endif //USE_XMB

#include <cstring>
#include <cstdlib>
#include <list>
#include <map>
#include <typeinfo>
#include <langinfo.h>

#ifdef HAVE_SETLOCALE
#include <locale.h>
#endif //HAVE_SETLOCALE

using std::string;
using std::map;
using std::list;

namespace {

// use to map <font1>|<font2>|<font3> => <fontthatworks>
typedef map<string, string> StringMap;
typedef StringMap::iterator StringMapIt;

// stores <fontthatworks and the fontimp
typedef map<string, FbTk::FontImp* > FontCache;
typedef FontCache::iterator FontCacheIt;


void resetEffects(FbTk::Font& font) {
    int nr_scr = DefaultScreen(FbTk::App::instance()->display());
    font.setHalo(false);
    font.setHaloColor(FbTk::Color("white", nr_scr));
    font.setShadow(false);
    font.setShadowColor(FbTk::Color("black", nr_scr));
    font.setShadowOffY(2);
    font.setShadowOffX(2);
}


StringMap s_lookup_map;
FontCache s_font_cache;
bool s_multibyte = false; // if the fontimp should be a multibyte font
bool s_utf8mode = false; // should the font use utf8 font imp


} // end nameless namespace



namespace FbTk {

const char Font::DEFAULT_FONT[] = "__DEFAULT__";


void Font::shutdown() {

    FontCacheIt fit;
    for (fit = s_font_cache.begin(); fit != s_font_cache.end(); ++fit) {
        FontImp* font = fit->second;
        if (font) {
            FontCacheIt it;
            for (it = fit; it != s_font_cache.end(); ++it)
                if (it->second == font)
                    it->second = 0;
            delete font;
        }
    }
}

bool Font::multibyte() {
    return s_multibyte;
}

bool Font::utf8() {
    return s_utf8mode;
}


Font::Font(const char *name):
    m_fontimp(0),
    m_shadow(false), m_shadow_color("black", DefaultScreen(App::instance()->display())),
    m_shadow_offx(2), m_shadow_offy(2),
    m_halo(false), m_halo_color("white", DefaultScreen(App::instance()->display()))
{
    // MB_CUR_MAX returns the size of a char in the current locale
    if (MB_CUR_MAX > 1) // more than one byte, then we're multibyte
        s_multibyte = true;


    char *locale_codeset = 0;

    // openbsd doesnt have this (yet?)
#if defined(CODESET) && !defined(_WIN32)
    locale_codeset = nl_langinfo(CODESET);
#endif

    // check for utf-8 mode
    if (locale_codeset && strcmp("UTF-8", locale_codeset) == 0) {
        s_utf8mode = true;
    } else if (locale_codeset != 0) {
        s_utf8mode = FbStringUtil::haveUTF8();
    }

    if (name != 0) {
        load(name);
    }

}

Font::~Font() {
}

bool Font::load(const string &name) {

    if (name.empty())
        return false;

    StringMapIt lookup_entry;
    FontCacheIt cache_entry;

    // check if one of <font1>|<font2>|<font3> is already there
    if ((lookup_entry = s_lookup_map.find(name)) != s_lookup_map.end() &&
            (cache_entry = s_font_cache.find(lookup_entry->second)) != s_font_cache.end()) {
        m_fontstr = cache_entry->first;
        m_fontimp = cache_entry->second;
        resetEffects(*this);
        return true;
     }

    // split up the namelist
    typedef list<string> StringList;
    typedef StringList::iterator StringListIt;
    StringList names;
    FbTk::StringUtil::stringtok<StringList>(names, name, "|");

    StringListIt name_it;
    for (name_it = names.begin(); name_it != names.end(); ++name_it) {
        FbTk::StringUtil::removeTrailingWhitespace(*name_it);
        FbTk::StringUtil::removeFirstWhitespace(*name_it);

        if ((cache_entry = s_font_cache.find(*name_it)) != s_font_cache.end()) {
            m_fontstr = cache_entry->first;
            m_fontimp = cache_entry->second;
            s_lookup_map[name] = m_fontstr;
            resetEffects(*this);
            return true;
        }

        FontImp* tmp_font(0);

        // Xft and X/Xmb fonts have different defaults
        // (fixed doesn't really work right with Xft, especially rotated)

        // HOWEVER, note that if a Xft-style font is requested (does not start
        // with "-"), and it turns out to be a bitmapped XFont, then Xft will
        // load it, BUT it does not currently (2007-01-05) rotate bitmapped
        // fonts (ok-ish), nor adjust the baseline for its lack of rotation
        // (not ok: messes up placement). I can't see a neat way around this, 
        // other than the user re-specifying their font explicitly in XFont
        // form so we don't use the Xft backend.

        std::string realname = *name_it;

#ifdef USE_XFT
        if ((*name_it)[0] != '-') {

            if (*name_it == Font::DEFAULT_FONT)
                realname = "monospace";

            tmp_font = new XftFontImp(0, utf8());
        }
#endif // USE_XFT

        if (!tmp_font) {
            if (*name_it == Font::DEFAULT_FONT)
                realname = "fixed";

#ifdef USE_XMB

            if (multibyte() || utf8()) {
                tmp_font = new XmbFontImp(0, utf8());
            }
#endif // USE_XMB
        }

        if (!tmp_font) {
           tmp_font = new XFontImp();
        }

        if (tmp_font && tmp_font->load(realname.c_str())) {
            s_lookup_map[name] = (*name_it);
            m_fontimp = tmp_font;
            s_font_cache[(*name_it)] = tmp_font;
            m_fontstr = name;
            resetEffects(*this);
            return true;
        }

        delete tmp_font;
    }

    return false;
}

unsigned int Font::textWidth(const char* text, unsigned int size) const {
    return m_fontimp->textWidth(text, size);
}

unsigned int Font::textWidth(const BiDiString &text) const {
    return textWidth(text.visual().c_str(), text.visual().size());
}

unsigned int Font::height() const {
    return m_fontimp->height();
}

int Font::ascent() const {
    return m_fontimp->ascent();
}

int Font::descent() const {
    return m_fontimp->descent();
}

bool Font::validOrientation(FbTk::Orientation orient) {
    return m_fontimp->validOrientation(orient);
}

void Font::drawText(const FbDrawable &w, int screen, GC gc,
                    const char* text, size_t len, int x, int y,
                    Orientation orient) const {

    if (!text || !*text || len == 0)
        return;

    // draw "effects" first
    if (m_shadow) {
        FbTk::GContext shadow_gc(w);
        shadow_gc.setForeground(m_shadow_color);
        m_fontimp->drawText(w, screen, shadow_gc.gc(), text, len,
                 x + m_shadow_offx, y + m_shadow_offy, orient);
    } else if (m_halo) {
        FbTk::GContext halo_gc(w);
        halo_gc.setForeground(m_halo_color);
        m_fontimp->drawText(w, screen, halo_gc.gc(), text, len, x + 1, y + 1, orient);
        m_fontimp->drawText(w, screen, halo_gc.gc(), text, len, x - 1, y + 1, orient);
        m_fontimp->drawText(w, screen, halo_gc.gc(), text, len, x - 1, y - 1, orient);
        m_fontimp->drawText(w, screen, halo_gc.gc(), text, len, x + 1, y - 1, orient);
    }

    m_fontimp->drawText(w, screen, gc, text, len, x, y, orient);
}

}