// Compile with:
// g++ -lbe $(pkg-config --cflags --libs freetype2) loadfonts.cpp

#include <Directory.h>
#include <Entry.h>
#include <Path.h>
#include <PathFinder.h>
#include <String.h>
#include <StringList.h>

#include <stdio.h>
#include <vector>

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H
#include FT_TRUETYPE_IDS_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H


static FT_Library fLibrary;


const char*
weightFromInteger(int weight)
{
    if (weight < 150)
        return "Thin";
    if (weight < 250)
        return "ExtraLight";
    if (weight < 350)
        return "Light";
    if (weight < 450)
        return "Normal";
    if (weight < 550)
        return "Medium";
    if (weight < 650)
        return "DemiBold";
    if (weight < 750)
        return "Bold";
    if (weight < 850)
        return "ExtraBold";
    return "Black";
}


float
fixedToFloat(FT_Fixed value)
{
    return value / (float) 0x10000;
}


BString
readUTF16BE(FT_Byte* s, FT_UInt length) {
	BString ret;
	for (FT_UInt i = 0; i < length; i += 2) {
		uint32 cp = (s[i] << 8) + s[i + 1];
		char c;
		if (cp < 0x80) {
			c = cp;
			ret += c;
		} else if (cp < 0x800) {
			c = 0xc0 | (cp >> 6);
			ret += c;
			c = 0x80 | (cp & 0x3f);
			ret += c;
		} else if (cp < 0x10000) {
			c = 0xe0 | (cp >> 12);
			ret += c;
			c = 0x80 | ((cp >> 6) & 0x3f);
			ret += c;
			c = 0x80 | (cp & 0x3f);
			ret += c;
		} else if (cp <= 0x10ffff) {
			c = 0xf0 | (cp >> 18);
			ret += c;
			c = 0x80 | ((cp >> 12) & 0x3f);
			ret += c;
			c = 0x80 | ((cp >> 6) & 0x3f);
			ret += c;
			c = 0x80 | (cp & 0x3f);
			ret += c;
		}
	}
	return ret;
}


BString
getSfntName(FT_Face face, FT_UInt id)
{
	FT_Byte* buffer;
	FT_UInt length = 0;
	FT_UInt count = FT_Get_Sfnt_Name_Count(face);

	for (FT_UInt i = 0; i < count; i++) {
		FT_SfntName name;

		if (FT_Get_Sfnt_Name(face, i, &name) != FT_Err_Ok || name.name_id != id
				|| name.platform_id != TT_PLATFORM_MICROSOFT
				|| name.encoding_id != TT_MS_ID_UNICODE_CS)
			continue;

		if (length == 0 || name.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES) {
			buffer = name.string;
			length = name.string_len;
		}

		if (name.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)
			break;
	}

	if (length > 0) {
		return readUTF16BE(buffer, length);
	}

	return BString();
}


int
readBE(const char* src, uint16* dst)
{
	char* _dst = (char*)dst;
	_dst[1] = src[0];
	_dst[0] = src[1];
	return 2;
}

int
readBE(const char* src, uint32* dst)
{
	char* _dst = (char*)dst;
	_dst[3] = src[0];
	_dst[2] = src[1];
	_dst[1] = src[2];
	_dst[0] = src[3];
	return 4;
}


struct STAT_Header
{
	STAT_Header(const char* src)
	{
		src += readBE(src, &majorVersion);
		if (majorVersion != 1) {
			printf("      Unsupported header version %u\n", majorVersion);
			return;
		}
		src += readBE(src, &minorVersion);
		src += readBE(src, &designAxisSize);
		src += readBE(src, &designAxisCount);
		src += readBE(src, &designAxesOffset);
		src += readBE(src, &axisValueCount);
		src += readBE(src, &axisValueOffsetsOffset);
		if (minorVersion >= 1)
			src += readBE(src, &elidedFallbackNameID);
		else
			elidedFallbackNameID = 0xffff;
		printf("      -- STAT HEADER\n");
		printf("      version %u.%u\n", majorVersion, minorVersion);
		printf("      designs %u (%u @ %u)\n", designAxisCount, designAxisSize, designAxesOffset);
		printf("      values  %u @ %u\n", axisValueCount, axisValueOffsetsOffset);
		printf("      fallback name %u\n", elidedFallbackNameID);
	}

	uint16	majorVersion;
	uint16	minorVersion;
	uint16	designAxisSize;
	uint16	designAxisCount;
	uint32	designAxesOffset;
	uint16	axisValueCount;
	uint32	axisValueOffsetsOffset;
	uint16	elidedFallbackNameID;
};


struct STAT_Value
{
	STAT_Value(const char* src)
	{
		src += readBE(src, &format);
		if (format > 3) {
			printf("      Unsupported value format %u\n", format);
			return;
		}
		src += readBE(src, &index);
		src += readBE(src, &flags);
		src += readBE(src, &nameID);
		src += readBE(src, (uint32*)&value);
		if (format == 2) {
			src += readBE(src, (uint32*)&min);
			src += readBE(src, (uint32*)&max);
		} else {
			min = max = value;
		}
		printf("      -- STAT VALUE\n");
		printf("      format %u\n", format);
		printf("      index  %u\n", index);
		printf("      flags  0x%04x\n", flags);
		//printf("      name   %u\n", nameID);
		printf("      value  %f\n", fixedToFloat(value));
		printf("      min    %f\n", fixedToFloat(min));
		printf("      max    %f\n", fixedToFloat(max));
	}

	uint16	format;
	uint16	index;
	uint16	flags;
	uint16	nameID;
	int32	value;
	int32	min;
	int32	max;
};


class AxisData {

	struct Span
	{
		int32 min;
		int32 max;
		BString name;
	};

	class Axis {
	public:
		Axis() : tag(0) {};

		void Insert(const STAT_Value& value, BString name) {
			printf("      name   %u %s\n", value.nameID, name.String());
			if (value.format > 3)
				return;

			if (value.flags & 0x0002)
				def = value.value;

			Span current;
			current.min = value.min;
			current.max = value.max;
			current.name = name;
			span.push_back(current);
		}

		BString Name(FT_Fixed value) {
			BString name;

			if (tag != 'wdth' && (value == def || min == max))
				return name;

			for (unsigned int i = 0; i < span.size(); i++) {
				if (span[i].min <= value && span[i].max >= value)
					return span[i].name;
			}

			float pct;
			if (tag == 'wdth')
				pct = fixedToFloat(value);
			else
				pct = 100 * (fixedToFloat(value) - min) / (max - min);
			name.SetToFormat("%s: %d%%", axisName.String(), (int)pct);

			return name;
		}

	public:
		BString axisName;
		uint32 tag;
		FT_Fixed def;
		float min;
		float max;
		std::vector<Span> span;
	};

public:
	AxisData(FT_Face face) : count(0), axis(NULL), fake(false) {
		FT_MM_Var* mm;
		if (FT_Get_MM_Var(face, &mm) != 0) {
			printf("    No multimaster data\n");
			return;
		}

		count = mm->num_axis;
		printf("    %d axis\n", count);
		if (count == 0) {
			FT_Done_MM_Var(fLibrary, mm);
			return;
		}
		axis = new Axis[count];

		for (FT_UInt i = 0; i < count; i++) {
			FT_Var_Axis* mm_axis = mm->axis + i;
			axis[i].tag = mm_axis->tag;
			axis[i].def = mm_axis->def;
			axis[i].min = fixedToFloat(mm_axis->minimum);
			axis[i].max = fixedToFloat(mm_axis->maximum);
			axis[i].axisName = getSfntName(face, mm_axis->strid);
			if (mm_axis->tag != tagWeight
					&& mm_axis->tag != tagItalics && mm_axis->tag != tagSlant){
				fake = true;
			}
			printf("      %d %04x %s %f .. %f (%f)\n", i, axis[i].tag, axis[i].axisName.String(), axis[i].min, axis[i].max, fixedToFloat(axis[i].def));
		}

		FT_Done_MM_Var(fLibrary, mm);

		FT_ULong length = 0;
		if (FT_Load_Sfnt_Table(face, 'STAT', 0, (FT_Byte*)NULL, &length) != FT_Err_Ok) {
			printf("    STAT table error\n");
			return;
		}
		printf("    STAT table: %u bytes\n", length);

		char* buffer = (char*)malloc(length);
		if (buffer == NULL) {
			printf("    Could not allocate buffer for STAT table\n");
			return;
		}
		if (FT_Load_Sfnt_Table(face, 'STAT', 0, (FT_Byte*)buffer, &length) != FT_Err_Ok) {
			printf("    Could not load STAT table\n");
			free(buffer);
			return;
		}

		STAT_Header header(buffer);
		if (header.majorVersion != 1 || header.designAxisCount == 0 || header.axisValueCount == 0) {
			free(buffer);
			return;
		}

		unsigned int map[(unsigned int)header.designAxisCount];

		for (unsigned int i = 0; i < header.designAxisCount; i++) {
			uint32 tag;
			readBE(buffer + header.designAxesOffset + i * header.designAxisSize, &tag);
			unsigned int j;
			for (j = 0; j < count; j++) {
				if (axis[j].tag == tag) {
					map[i] = j;
					break;
				}
			}
		}

		uint16* offsets = (uint16*)(buffer + header.axisValueOffsetsOffset);
		for (int i = 0; i < header.axisValueCount; i++) {
			uint16 offset;
			readBE((char*)(offsets + i), &offset);
			STAT_Value value(buffer + header.axisValueOffsetsOffset + offset);
			if (value.format > 3 || value.index >= count)
				continue;
			axis[map[value.index]].Insert(value, getSfntName(face, value.nameID));
		}

		regularName = getSfntName(face, header.elidedFallbackNameID);
		if (regularName.IsEmpty())
			regularName = "Regular";
		printf("    Regular name: %s\n", regularName.String());

		free(buffer);
	};

	~AxisData() {
		delete[] axis;
	};

    BString Coordinates(FT_Face face, BString& weight, BString& style) {
        BString fakeFoundry;
        if (count == 0)
            return fakeFoundry;

        FT_Fixed coords[count];
        FT_Get_Var_Design_Coordinates(face, count, coords);
        for (unsigned int i = 0; i < count; i++) {
            if (axis[i].tag == tagWeight) {
                weight = weightFromInteger(coords[i] >> 16);
            } else if (axis[i].tag == tagItalics) {
                if (coords[i] > 0x8000)
                    style = "Italic";
                else
                    style = "Normal";
            } else if (axis[i].tag == tagSlant) {
                if (coords[i] == 0)
                    style = "Normal";
                else
                    style = "Oblique";
            } else {
                if (!fakeFoundry.IsEmpty())
                    fakeFoundry += ' ';
                fakeFoundry += axis[i].Name(coords[i]);
            }
        }

        if (fake && fakeFoundry.IsEmpty())
            fakeFoundry = regularName;
        return fakeFoundry;
    }

public:
    static const int tagWeight = 'wght';
    static const int tagItalics = 'ital';
    static const int tagSlant = 'slnt';

    unsigned int count;
    Axis* axis;
    bool fake;
    BString regularName;
};


void
addTTFile(const char* path)
{
	printf("Read font %s\n", path);

    int index = 0;
    int numFaces = 0;

    FT_Face face;
    if (FT_New_Face(fLibrary, path, -1, &face) != FT_Err_Ok) {
    	printf("  Error loading\n");
        return;
    }
    numFaces = face->num_faces;
    FT_Done_Face(face);
    printf("  %d faces\n", numFaces);

    do {
      if (FT_New_Face(fLibrary, path, index, &face) != FT_Err_Ok) {
          printf("  Error loading face %d\n", index);
          break;
      }

      printf("  face %d\n", index);
      AxisData variableData(face);
      int variableCount = (face->style_flags & 0x7fff0000) >> 16;
      printf("    %d named styles\n", variableCount);
      FT_Done_Face(face);

      int namedStyle = variableCount == 0 ? 0 : 1;
      do {
        int faceIndex = index | (namedStyle << 16);
       	printf("    Load index 0x%08x (style %d of face %d)\n", faceIndex, namedStyle, index);
        if (FT_New_Face(fLibrary, path, faceIndex, &face) != FT_Err_Ok) {
        	printf("    Error loading\n");
            break;
        }

        BString weight = "Normal";

        BString style = "Normal";
        if (face->style_flags & FT_STYLE_FLAG_ITALIC)
            style = "Italic";

        if (face->style_flags & FT_STYLE_FLAG_BOLD)
            weight = "Bold";

        TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
        if (os2) {
            if (os2->usWeightClass) {
            	weight = weightFromInteger(os2->usWeightClass);
            } else if (os2->panose[2]) {
                int w = os2->panose[2];
                if (w <= 1)
                    weight = "Thin";
                else if (w <= 2)
                    weight = "ExtraLight";
                else if (w <= 3)
                    weight = "Light";
                else if (w <= 5)
                    weight = "Normal";
                else if (w <= 6)
                    weight = "Medium";
                else if (w <= 7)
                    weight = "DemiBold";
                else if (w <= 8)
                    weight = "Bold";
                else if (w <= 9)
                    weight = "ExtraBold";
                else if (w <= 10)
                    weight = "Black";
            }
        }

        BString fakeFoundry;
        if (namedStyle > 0) {
            fakeFoundry = variableData.Coordinates(face, weight, style);
        } else if (os2) {
            switch (os2->usWidthClass) {
            case 1:
                fakeFoundry = "UltraCondensed";
                break;
            case 2:
                fakeFoundry = "ExtraCondensed";
                break;
            case 3:
                fakeFoundry = "Condensed";
                break;
            case 4:
                fakeFoundry = "SemiCondensed";
                break;
            case 6:
                fakeFoundry = "SemiExpanded";
                break;
            case 7:
                fakeFoundry = "Expanded";
                break;
            case 8:
                fakeFoundry = "ExtraExpanded";
                break;
            case 9:
                fakeFoundry = "UltraExpanded";
                break;
            default:
                fakeFoundry = "Medium";
            }
        }

        printf("      %s [%s] / %s (%s, %s)\n", face->family_name, fakeFoundry.String(), face->style_name, weight.String(), style.String());
        FT_Done_Face(face);
        ++namedStyle;
      } while (namedStyle <= variableCount);
      ++index;
    } while (index < numFaces);
}


void
readDir(const char* name)
{
	printf("Find fonts in %s\n", name);
	BDirectory dir(name);
	BEntry entry;
	while (dir.GetNextEntry(&entry, true) == B_OK) {
		BPath bpath;
		entry.GetPath(&bpath);
		const char* path = bpath.Path();
		if (entry.IsDirectory()) {
			readDir(path);
		} else {
			BString spath(path);
			if (spath.EndsWith(".ttf") || spath.EndsWith(".otf"))
				addTTFile(path);
			else
				printf("Discard %s\n", path);
		}
	}
}


int
main(int, char**)
{
	FT_Init_FreeType(&fLibrary);

	BStringList fontPaths;
	BPathFinder::FindPaths(NULL, B_FIND_PATH_FONTS_DIRECTORY,
		NULL, B_FIND_PATH_EXISTING_ONLY, fontPaths);

	for (int32 i = 0; i < fontPaths.CountStrings(); i++) {
		readDir(fontPaths.StringAt(i).String());
	}

	return 0;
}
