/*
 * Copyright (C) 1992 by Software Research Associates, Inc.
 *      Author: Y. Kawabe <kawabe@sra.co.jp>
 *
 * Permission to use, copy, modify, and distribute, and sell this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Software Research Associates
 * makes no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */

#include <InterViews/font.h>
#include <InterViews/fontset.h>
#include <InterViews/session.h>
#include <InterViews/display.h>
#include <IV-X11/Xlib.h>
#include <IV-X11/xatom.h>
#include <IV-X11/xdisplay.h>
#include <NLS/charset.h>
#include <NLS/nlsfile.h>
#include <NLS/wchar.h>
#include <hyperg/OS/table.h>
#include <hyperg/OS/table2.h>
#include <hyperg/OS/ustring.h>
#include <hyperg/OS/math.h>
#include <hyperg/utils/str.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream.h>
#include <string.h>

static inline char* Strdup (const char* s) {
    if (s) {
	char * dup = new char [strlen(s) + 1];
	register char* p = dup ;
	 while (*p++ = *s++);
	return dup;
    } else {
	return nil;
    }
}

/*
 * class FontSetImpl
 */

class FontSetImpl : public Resource {
  public:
    FontSetImpl(const char*, const Font*);
    ~FontSetImpl ();

  public:
    const Font* font () const { return font_; }
    const char* name () const { return name_ ? name_ : font_->name(); }

  public:
    const Font* font_;
    const char* name_;
};

FontSetImpl::FontSetImpl (const char* name, const Font* font) {
    font_ = font;
    Resource::ref (font);
    name_ = Strdup (name);
}

FontSetImpl::~FontSetImpl () {
    Resource::unref (font_);
    delete (char*) name_;
}

/*
 * class FontSetImplTable
 */

inline unsigned long key_to_hash(UniqueString &s) {
    return (unsigned long) s.hash();
}

declareTable(FontSetImplTable, UniqueString, const FontSetImpl*);
implementTable(FontSetImplTable, UniqueString, const FontSetImpl*);

/*
 * class FontSetRepTable
 */

declareTable2(FontSetRepTable, CharSet_T, int, const FontSetRep*);
implementTable2(FontSetRepTable, CharSet_T, int, const FontSetRep*);

/*
 * class FontSetRep
 */

#ifndef IV_FONTSET_PATH
#define IV_FONTSET_PATH         "FontSet"
#endif

FontSetRepTable *FontSetRep::table_;

FontSetRep::FontSetRep (const String& name, int mask) {
    name_ = new UniqueString(name);
    mask_ = mask;
}

FontSetRep::~FontSetRep () {
    delete name_;
}

inline int getMask(CharSet_T charset) {
  int mask = 0;
  for (int m = CharSet::rep(charset)->bytes(); m > 0; m--) {
    mask = (mask << 8) | 0x80;
  }
  return mask;
}

const FontSetRep* FontSetRep::Find (CharSet_T c, int i) {
    if (table_ == nil) {
        table_ = new FontSetRepTable(32);

        FontSetRep* rep = new FontSetRep("ISO8859-1", 0);
        table_->insert(CharSet::ascii(), 0, rep);

        const int       argsize = 64;
        char            *argv[argsize], **av;
        int             argc, ac;
        CharSet_T       charset = -1;
        nlsFile         file(IV_FONTSET_PATH);

        charset = CharSet::find("ISO8859-1");
        rep = new FontSetRep("ISO8859-1", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-2");
        rep = new FontSetRep("ISO8859-2", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-3");
        rep = new FontSetRep("ISO8859-3", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-4");
        rep = new FontSetRep("ISO8859-4", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-5");
        rep = new FontSetRep("ISO8859-5", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-6");
        rep = new FontSetRep("ISO8859-6", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-7");
        rep = new FontSetRep("ISO8859-7", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-8");
        rep = new FontSetRep("ISO8859-8", getMask(charset));
        table_->insert(charset, 0, rep);
        charset = CharSet::find("ISO8859-9");
        rep = new FontSetRep("ISO8859-9", getMask(charset));
        table_->insert(charset, 0, rep);

        charset = CharSet::find("JISX0201.1976-R");
        rep = new FontSetRep("JISX0201.1976-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("ISO8859-1", 0);
        table_->insert(charset, 1, rep);

        charset = CharSet::find("JISX0201.1976-K");
        rep = new FontSetRep("JISX0201.1976-0", getMask(charset));
        table_->insert(charset, 0, rep);

        charset = CharSet::find("JISX0208.1978");
        int mask = getMask(charset);
        rep = new FontSetRep("JISX0208.1978-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("JISX0208.1978-1", mask);
        table_->insert(charset, 1, rep);
        rep = new FontSetRep("JISX0208.1983-0", 0);
        table_->insert(charset, 2, rep);
        rep = new FontSetRep("JISX0208.1983-1", mask);
        table_->insert(charset, 3, rep);

        charset = CharSet::find("GB2312.1980");
        mask = getMask(charset);
        rep = new FontSetRep("GB2312.1980-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("GB2312.1980-1", mask);
        table_->insert(charset, 1, rep);

        charset = CharSet::find("JISX0208.1983");
        mask = getMask(charset);
        rep = new FontSetRep("JISX0208.1983-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("JISX0208.1983-1", mask);
        table_->insert(charset, 1, rep);

        charset = CharSet::find("KSC5601.1987");
        mask = getMask(charset);
        rep = new FontSetRep("KSC5601.1987-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("KSC5601.1987-1", mask);
        table_->insert(charset, 1, rep);

        charset = CharSet::find("JISX0212.1990");
        mask = getMask(charset);
        rep = new FontSetRep("JISX0212.1990-0", 0);
        table_->insert(charset, 0, rep);
        rep = new FontSetRep("JISX0212.1990-1", mask);
        table_->insert(charset, 1, rep);

        charset = CharSet::find("BIG5.HKU");
        rep = new FontSetRep("BIG5.HKU-0", 0);
        table_->insert(charset, 0, rep);

        while ((argc = file.getline(argv, argsize)) >= 0) {
            if (argc == 0) continue;

            if (!file.continued()) {
                charset = CharSet::find (argv[0]);
                ac = argc - 1, av = argv + 1;
            } else {
                ac = argc, av = argv;
            }

            if (ac > 0 && CharSet::rep(charset)) {
                const FontSetRep *rep;
                int i;
                for (i = 0; table_->find(rep, charset, i) ;i++);
        
//                 int mask = 0;
//                 for (int m = CharSet::rep(charset)->bytes(); m > 0; m--) {
//                     mask = (mask << 8) | 0x80;
//                 }
                mask = getMask(charset);
                while (ac > 0) {
                    String name(av[0]);
                    if (name[name.length() - 1] == '*') {
                        name.set_to_left(name.length() - 1);
                        rep = new FontSetRep (name, mask);
                    } else {
                        rep = new FontSetRep (name, 0);
                    }
                    table_->insert(charset, i++, rep);
                    ac--, av++;
                }
            }
        }
    }
    const FontSetRep *rep;
    if (table_->find(rep, c, i)) {
	return rep;
    }
    return nil;
}

/*
 * class FontSetPoolTable
 */

declareTable(FontSetPool,UniqueString,FontSet*)
implementTable(FontSetPool,UniqueString,FontSet*)

/*
 * class FontSet
 */

static const int MAX_FONT = 64;

FontSetPool *FontSet::pool_;

FontSet::FontSet () {
    table_ = new FontSetImplTable(8);
    ascent_ = descent_ = 0;
    name_ = nil;
}

FontSet::FontSet (const char *name) {
    table_ = new FontSetImplTable(4);
    ascent_ = descent_ = 0;
    name_ = nil;
    Insert (String(name));
}

FontSet::FontSet (const String& name) {
    table_ = new FontSetImplTable(4);
    ascent_ = descent_ = 0;
    name_ = nil;
    Insert (name);
}

FontSet::FontSet (const FontSet& fs) {
    table_ = new FontSetImplTable(4);
    for (TableIterator(FontSetImplTable) i(*fs.table_); i.more(); i.next()) {
	table_->insert(i.cur_key(), i.cur_value());
	Resource::ref(i.cur_value());
    }
    ascent_ = fs.ascent_;
    descent_ = fs.descent_;
    name_ = nil;
}

FontSet::~FontSet () {
    for (TableIterator(FontSetImplTable) i(*table_); i.more(); i.next()) {
	Resource::unref(i.cur_value());
    }
    delete table_;
    delete name_;
}

const FontSet* FontSet::lookup (const char* basename) {
    return lookup (String(basename));
}

const FontSet* FontSet::lookup (const String &basename) {
    FontSet* fontset = nil;
    
    if (pool_ == nil) {
	pool_ = new FontSetPool (8);
    }
    
    if (pool_->find (fontset, basename)) {
        return fontset;
    } else {
	Display* dpy = Session::instance()->default_display();
	XDisplay* xdpy = dpy->rep()->display_;

        int start = 0, index, i;
	fontset = new FontSet ();

	static Atom XA_CHARSET_REGISTRY = 0;
        static Atom XA_CHARSET_ENCODING = 0;

	if (XA_CHARSET_REGISTRY == 0)
	    XA_CHARSET_REGISTRY = XInternAtom(xdpy, "CHARSET_REGISTRY", false);

	if (XA_CHARSET_ENCODING == 0)
	    XA_CHARSET_ENCODING = XInternAtom(xdpy, "CHARSET_ENCODING", false);
	
        while (start < basename.length()) {

	    String substr;
	    XFontStruct* info;
	    int count;
	    
	    if ((index = basename.search(start, ',')) > 0) {
		substr =  basename.substr(start, index - start);
		start = index + 1;
	    } else {
		substr =  basename.substr(start, -1);
		start = basename.length();
	    }
            /* Trim beginning of white space */
            for (i = 0; i < substr.length() && isspace(substr[i]) ; i++) ;
            if (i > 0) substr.set_to_right(i);
	    
            /* Trim end of white space */
            for (i = substr.length(); i > 0 && isspace(substr[i-1]); i--);
            if (i < substr.length()) substr.set_to_left(substr.length() - i);

	    NullTerminatedString str(substr);
	    
	    char **list= XListFontsWithInfo(xdpy, str.string(),
					    MAX_FONT, &count, &info);
	    if (list == nil) continue;
	    
	    for (i = 0 ; list + i && i < count; i++) {
		const char *name = list[i];
		unsigned long registry;
		unsigned long encoding;
		const FontSetImpl* impl;
		const char* p;
		char buf[256];

		if (XGetFontProperty(info+i, XA_CHARSET_REGISTRY, &registry) &&
		    XGetFontProperty(info+i, XA_CHARSET_ENCODING, &encoding)) {
		    const String& s1 = XAtom::name(xdpy, (Atom)registry);
		    const String& s2 = XAtom::name(xdpy, (Atom)encoding);
		    
                    sprintf(buf, "%s-%s", s1.string(), s2.string());
                } else if (p = strrchr (name, '-')) {
		     while (p > name && *(p - 1) != '-')
			p--;
		    if (p > name) {
                        register char* q;
			for (q = buf; *p; p++, q++) {
			    if (*p >= 'a' && *p <= 'z') {
			        *q = *p - 'a' + 'A';
			    } else {
				*q = *p;
			    }
			}
			*q = '\0';
		    } else {
			buf[0] = '\0';
		    }
                } else {
		    buf[0] = '\0';
                }
	
		if (buf[0] == '\0') {	// unknown encoding
		    sprintf(buf, "ISO8859-1");
		}

                RString encodingstring(buf);
                encodingstring=encodingstring.toUpper();

		if (!fontset->table_->find(impl,encodingstring.string() )) {
		    impl = new FontSetImpl(name, new Font(name));
		    Resource::ref(impl);
                    fontset->table_->insert(encodingstring.string(), impl);
                    fontset->ascent_ = Math::max(fontset->ascent_,
					 dpy->to_coord(info[i].ascent)); 
                    fontset->descent_= Math::max(fontset->descent_,
					 dpy->to_coord(info[i].descent));
		}
	    }
	    XFreeFontInfo (list, info, count);
	}
	pool_->insert(basename, fontset);
        Resource::ref(fontset);
	return fontset;
    }
}

/// Find

const Font *FontSet::Find (CharSet_T charset, const FontSetRep*& r) const {
    const FontSetImpl* impl = nil;
    if (CharSet::rep(charset)) {
        for (int i = 0; r = FontSetRep::Find(charset, i) ; i++) {
            if (table_->find(impl, r->name())) {
                break;
            }
        }
    }
    return impl ? impl->font() : nil;
}

const Font *FontSet::Find (const char *encoding) const {
    return Find(String(encoding));
}

const Font *FontSet::Find (const String &encoding) const {
    const FontSetImpl* impl;
    if (table_->find(impl, encoding)) {
	return impl->font();
    } else {
	return nil;
    }
}

void FontSet::Remove (const Font *f) {
    if (f && f->encoding()) {
	const FontSetImpl *impl;
	String encoding (f->encoding());
	if (table_->find_and_remove(impl, encoding)) {
	    Resource::unref(impl);
	    delete name_;
	    name_ = nil;
	}
    }
}

void FontSet::Insert (const Font *f) {
    if (f && f->encoding()) {
	const FontSetImpl *impl;
	String encoding (f->encoding());
	if (table_->find_and_remove(impl, encoding)) {
	    Resource::unref(impl);
	}
	impl = new FontSetImpl(nil, f);
	Resource::ref(impl);
	table_->insert(encoding, impl);
	
	FontBoundingBox b;
	f->font_bbox(b);
	ascent_ = Math::max(ascent_, b.font_ascent()); 
	descent_ = Math::max(descent_, b.font_descent());
	
	delete name_;
	name_ = nil;
    } else if (f && f->name()) {
	Insert(f->name());
    }
}

void FontSet::Insert (const String &basename) {
    const FontSet* f = FontSet::lookup(basename);
    for (TableIterator(FontSetImplTable) i(*f->table_); i.more(); i.next()) {
	FontSetImpl *impl;
	if (table_->find_and_remove(impl, i.cur_key())) {
	    Resource::unref(impl);
	}
	table_->insert(i.cur_key(), i.cur_value());
	Resource::ref(i.cur_value());
    }
    ascent_ = Math::max(ascent_, f->ascent_);
    descent_ = Math::max(descent_,f->descent_);
    
    delete name_;
    name_ = nil;
}

const char *FontSet::name () const {
    if (name_) {
	return name_;
    }
    int count = 0;
    for (TableIterator(FontSetImplTable) i(*table_); i.more(); i.next()) {
        count += strlen(i.cur_value()->name()) + 1;
    }
    if (count > 0) {
	int n = 0;
	char *name = new char[count];
	for (TableIterator(FontSetImplTable) i(*table_); i.more(); i.next()) {
	    for (const char *s = i.cur_value()->name(); *s; s++) {
		name[n++] = *s;
	    }
	    name[n++] = ',';
	}
	name[n-1] = '\0';
	((FontSet*) this)->name_ = name;
	return name_;
    } else {
	return nil;
    }
}

/*
 * Metric functions
 */

Coord FontSet::ascent() const {
    return ascent_;
};

Coord FontSet::descent() const {
    return descent_;
};

void FontSet::char_bbox(const WChar& c, FontBoundingBox& box) const {
    const FontSetRep* rep;
    const Font *fn = Find(c.charset(), rep);
    if (fn) {
	fn->char_bbox(rep->convert(c.charcode()), box);
    } else {
	box.font_ascent_ = ascent_;
	box.font_descent_ = descent_;
	box.left_bearing_ = 0;
	box.right_bearing_ = 0;
	box.width_ = 0;
	box.ascent_ = 0;
	box.descent_ = 0;
    }
}

void FontSet::string_bbox(const WChar* s, int n, FontBoundingBox& box) const {
    CharSet_T charset = -1;
    const Font *fn = nil;
    const FontSetRep* rep;

    box.font_ascent_ = ascent_;
    box.font_descent_ = descent_;
    box.left_bearing_ = 0;
    box.right_bearing_ = 0;
    box.width_ = 0;
    box.ascent_ = 0;
    box.descent_ = 0;
    
    for (int i = 0; i < n ; i++) {
	if (s[i].charset() != charset) {
	    charset = s[i].charset();
	    fn = Find(charset, rep);
	}
	if (fn) {
	    FontBoundingBox b;
	    fn->char_bbox(rep->convert(s[i].charcode()), b);
	    if (b.descent_ > box.descent_)
		box.descent_ = b.descent_;
	    if (b.ascent_ > box.ascent_)
		box.ascent_ = b.ascent_;
	    if (box.width_ + b.right_bearing_ > box.right_bearing_)
		box.right_bearing_ = box.width_ + b.right_bearing_;
	    if (box.width_ + b.left_bearing_ < box.left_bearing_)
		box.left_bearing_ = box.width_ + b.left_bearing_;
	    box.width_ += b.width_;
	}
    }
}

Coord FontSet::width(const WChar& ch) const {
    const FontSetRep* rep;
    const Font *fn = Find(ch.charset(), rep);
    return fn ? fn->width(rep->convert(ch.charcode())) : 0;
}

Coord FontSet::width(const WChar* s, int n) const {
    Coord width = 0;
    CharSet_T charset = -1;
    const Font *fn = nil;
    const FontSetRep* rep;
    
    for (int i = 0; i < n ; i++) {
	if (s[i].charset() != charset) {
	    charset = s[i].charset();
	    fn = Find(charset, rep);
	}
	if (fn) {
	    width += fn->width(rep->convert(s[i].charcode()));
	}
    }
    return width;
}


int FontSet::index(const WChar* s, int n, Coord offset, boolean between) const {
    if (offset < 0 || n == 0) {
        return 0;
    }
    
    const FontSetRep* rep;
    CharSet_T charset = -1;
    const Font *fn = nil;
    Coord width = 0;
    Coord cw;
    
    int i;
    for (i = 0; i < n ; i++) {
	if (s[i].charset() != charset) {
	    charset = s[i].charset();
	    fn = Find(charset, rep);
	}
	cw = fn ? fn->width(rep->convert(s[i].charcode())) : 0; 
	if (width + cw > offset) {
	    break;
	}
	width += cw;
    }
    if (between &&  width + cw / 2 < offset) {
	i++;
    }
    return (i < n) ? i : n;
}

/* Anachronisms Metric */
    
int FontSet::Baseline() const {
    Display *display = Session::instance()->default_display();
    return display->to_pixels(descent()) - 1;
}

int FontSet::Height() const {
    Display *display = Session::instance()->default_display();
    return display->to_pixels(ascent() + descent());
}

int FontSet::Width(const WChar* str, int len) const {
    Display *display = Session::instance()->default_display();
    return display->to_pixels(width(str, len));
}

