#include <stdlib.h>
#include <stdio.h>
#include <gd.h>
#include <string.h>
#include <stdint.h>
//#include <wchar.h>
#include <unistd.h>
//#include <sys/mman.h>
//#include <sys/types.h>
//#include <sys/stat.h>
//#include <fcntl.h>
//#include <locale.h>
//#include <iconv.h>
/* to determine codepoint categories */
//#include <unicode/uchar.h>
/* to output UTF-32 codepoints in proper encoding for terminal */
//#include <unicode/ustdio.h>

//#include <sys/types.h>
#include <limits.h>
#include <math.h>

#define SEMICOLON 59


//static int entity_to_dec(const char *str, uint32_t *chPtr){
//}

static int utf_to_dec(const char *str, uint32_t *chPtr){
	uint32_t b0 = *(unsigned char *)str;
	uint32_t b1, b2, b3;

	if (b0 < 0xC0) {
		*chPtr = b0;
		return 1;
	} else if (b0 < 0xE0) {
		b1=(unsigned char)str[1];
		if ((b1 & 0xC0) == 0x80) {
			*chPtr = ((b0 & 0x1F) << 6) | (b1 & 0x3F);
			return 2;
		}
		*chPtr = b0;
		return 1;
	} else if (b0 < 0xF0) {
		b1=(unsigned char)str[1];
		b2=(unsigned char)str[2];
		if (((b1 & 0xC0) == 0x80) && ((b2 & 0xC0) == 0x80)) {
			*chPtr = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F);
			return 3;
		}
		*chPtr = b0;
		return 1;
	}else if (b0 < 0xF8) {
		b1=(unsigned char)str[1];
		b2=(unsigned char)str[2];
		b3=(unsigned char)str[3];
		if (  ((b1 & 0xC0) == 0x80) && ((b2 & 0xC0) == 0x80) && ((b3 & 0xC0) == 0x80)  ) {
			*chPtr = ((b0 & 0x7) << 18) | ((b1 & 0x3F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F);
			return 4;
		}
		*chPtr = b0;
		return 1;
	}else{
		printf("utf-8 above 21-bit unicode range\n");
	}
	//else
	// *chPtr = b0;
	return 0;
}








static int utf8_encode(char *out, const uint32_t utf){
	int i = 0;
	static const uint8_t s0 = 0;
	static const uint8_t s1 = 6;
	static const uint8_t s2 = 12;
	static const uint8_t s3 = 18;
	static const uint8_t a0 = 0x07;
	static const uint8_t a1 = 0x0F;
	static const uint8_t a2 = 0x1F;
	static const uint8_t a3 = 0x3F;
	static const uint8_t o0 = 0x80;
	static const uint8_t o1 = 0xC0;
	static const uint8_t o2 = 0xE0;
	static const uint8_t o3 = 0xF0;
	if(utf < 0x0080){
		//Plain ASCII
		out[i++] = (char)utf;
		return i;
	}else if(utf < 0x0800){
		//2-byte unicode
		out[i++] = (char)(((utf >> s1) & a2) | o1);
		out[i++] = (char)(((utf >> s0) & a3) | o0);
		return i;
	}else if(utf < 0x10000){ //65,536
		//3-byte unicode
		out[i++] = (char)(((utf >> s2) & a1) | o2);
		out[i++] = (char)(((utf >> s1) & a3) | o0);
		out[i++] = (char)(((utf >> s0) & a3) | o0);
		return i;
	}else if(utf < 0x110000){ //RFC3629 §3 limit
		//4-byte unicode
		out[i++] = (char)(((utf >> s3) & a0) | o3);
		out[i++] = (char)(((utf >> s2) & a3) | o0);
		out[i++] = (char)(((utf >> s1) & a3) | o0);
		out[i++] = (char)(((utf >> s0) & a3) | o0);
		return i;
	}else{
		//utf-8 outside of current unicode code-space
		printf("invalid utf-8 range\n");
		return i;
	}
}

/*
struct Colors{
	int red;
	int green;
	int blue;
	char *name;
};
*/

struct Glyph_Metrics{
	char str[5+30];
	int xmin;
	int xmax;
	int ymin;
	int ymax;
	int orig_x;
	int orig_y;
	int bearingx;
	int bearingy;
	int advance;
	int height;
	int width;
};

static void Set_strex_flags(gdFTStringExtraPtr strex){
	strex->flags |= gdFTEX_LINESPACE;
	strex->flags |= gdFTEX_CHARMAP;
	strex->flags |= gdFTEX_RESOLUTION;
	strex->flags |= gdFTEX_DISABLE_KERNING;
	strex->flags |= gdFTEX_XSHOW;
	strex->flags |= gdFTEX_RETURNFONTPATHNAME;
        //*flags |= gdFTEX_FONTPATHNAME;
        //*flags |= gdFTEX_FONTCONFIG;
        //*flags |= gdFTEX_FONTPATHNAME;
	strex->linespacing = 1.00;
	strex->charmap = gdFTEX_Unicode;
	//FT_ENCODING_UNICODE
	//strex->charmap = gdFTEX_Big5;
	//strex->charmap = gdFTEX_Shift_JIS;
	//strex->charmap = gdFTEX_Adobe_Custom;
	strex->hdpi=187;
	strex->vdpi=187;
}

static int Gen_Charset(const char *input_ttf, const char *hex_file, const char *image_file, const char *font_file, const int *bg, const int *fg, const int *trans, const double pixel_pt_size){
	FILE *image_out;
	FILE *font_fnt_out;
	FILE *input_hex;
	FILE *out_test;
//FILE *entity;

	static const char *space = " ";
	static const int extra_char_spacing = 1.0;
	static const int image_border = 40;
	gdImagePtr im;
	int background_color;
	int foreground_color;
	int transparency_color;
	int brect[8];
	int x = 0;
	int y = 0;
	char *err;
	int image_width = 0;
	int image_height = 0;
	static const int linestr_size = 32+30;
	char linestring[linestr_size];
	char test[6+30];
	uint32_t hexchar = 0;
	int num1 = 0;
	int global_bb_width = 0;
	int global_bb_height = 0;
	int global_ymin = 0;
	int global_ymax = 0;
	int global_max_advance = 0;
	struct Glyph_Metrics *glyph_metrics;
	gdFTStringExtra Strex = {0,0,0,0,0,NULL,NULL};
	//gdFTStringExtraPtr strex = &Strex;
	int cur_char = 0;
	size_t totalchars = 0;
	char *xshow_ptr = Strex.xshow;


	Set_strex_flags(&Strex);
//printf("here 3\n");


	int utf_bytes = 0;
        if( ( out_test = fopen("dec.txt", "w" ) ) == NULL ) {
                fprintf( stderr, "Error opening %s\n", "dec.txt" );
                return 1;
        }

//        if( ( entity = fopen("entities.txt", "r" ) ) == NULL ) {
  //              fprintf( stderr, "Error opening %s\n", "entities.txt" );
    //            return 1;
      //  }




	//count how many chars we have
        if( ( input_hex = fopen(hex_file, "r" ) ) == NULL ) {
                fprintf( stderr, "Error opening %s\n", hex_file );
                return 1;
        }
	memset ((void*) linestring, '\0', linestr_size);

        while( fgets(linestring, linestr_size, input_hex) != NULL){
		if(linestring[0] != '\n')
			totalchars++;
	}
	if(fseek(input_hex, 0, SEEK_SET) != 0)
		return 1;

	glyph_metrics = (struct Glyph_Metrics *)malloc(totalchars * sizeof(struct Glyph_Metrics));

	x=0;
	y=0;

	//populate glyph_metrics
	while( fgets(linestring, linestr_size, input_hex) != NULL){
		//make sure we skip last newline
		if(linestring[0] == '\n')
			break;
		//replace newline with null
		for(num1=0; num1<linestr_size; num1++){
			if(linestring[num1] == '\n'){
				linestring[num1] = '\0';
				break;
			}
		}
memset ((void*)test, '\0', 6+30);
strcpy(test, (const char*)linestring);

//		memset ((void*)test, '\0', 6);
//		hexchar = (uint32_t)strtoul((const char *)linestring, NULL, 16);

//fprintf(out_test, "%s, %d, ", linestring, hexchar);

//		if(utf8_encode(test, hexchar) == 0)
//			exit(1);
//utf_bytes = utf_to_dec((const char *)test, &hexchar);
//fprintf(out_test, "%d, %d\n", utf_bytes, hexchar);

		err = gdImageStringFTEx(NULL, brect, 0, input_ttf, pixel_pt_size, 0.0, x, y, (const char *)test, &Strex);
		if (err) {fprintf(stderr,err); return 1;}

		memset ((void*)glyph_metrics[cur_char].str, '\0', 5+30);
		(void)strcpy(glyph_metrics[cur_char].str, (const char *)test );

		//all these should be in relation to the requested x,y coordinate
		//glyph bounding box offsets from x/y
		glyph_metrics[cur_char].xmin = x - brect[0]; //brect[0] < x ? brect[0] : brect[0]
		glyph_metrics[cur_char].xmax = brect[4] - x;
		glyph_metrics[cur_char].ymin = brect[3] - y;
		glyph_metrics[cur_char].ymax = y - brect[7];

		//set these to zero
		//xshow seems to be x at the glyph origin
		glyph_metrics[cur_char].orig_x = 0;//atof((const char *)strex->xshow);
		//can't know this for sure
		glyph_metrics[cur_char].orig_y = 0;

		//bearings should be included in brect output
		glyph_metrics[cur_char].bearingx = glyph_metrics[cur_char].xmin - glyph_metrics[cur_char].orig_x;
		glyph_metrics[cur_char].bearingy = glyph_metrics[cur_char].orig_y - glyph_metrics[cur_char].ymax;

		glyph_metrics[cur_char].height = abs(glyph_metrics[cur_char].ymin + glyph_metrics[cur_char].ymax);
		glyph_metrics[cur_char].width = glyph_metrics[cur_char].xmax + glyph_metrics[cur_char].xmin;

		gdFree((void *)Strex.xshow);

		//need back to back chars to calculate advance
		(void)strcat((char *)test, (const char *)space);
		err = gdImageStringFTEx(NULL, brect, 0, input_ttf, pixel_pt_size , 0.0, x, y, (const char *)test, &Strex);
		if (err) {fprintf(stderr,err); return 1;}
//printf("here 5\n");
		xshow_ptr = Strex.xshow;
		//xshow_ptr = strex->xshow;
		while(*(xshow_ptr) != ' ')
			xshow_ptr++;
		//x offset to next glyph's origin
		glyph_metrics[cur_char].advance = atoi((const char *)xshow_ptr) - x;
		cur_char++;

	}
//	fclose(entity);
	fclose(out_test);
	fclose(input_hex);
	gdFree((void *)Strex.xshow);


	//find global bounding box dimensions
	for(cur_char=0; cur_char<totalchars; cur_char++){
		if(glyph_metrics[cur_char].advance > global_max_advance)
			global_max_advance = glyph_metrics[cur_char].advance;
		if(glyph_metrics[cur_char].ymax > global_ymax)
			global_ymax = glyph_metrics[cur_char].ymax;
		if(glyph_metrics[cur_char].ymin > global_ymin)
			global_ymin = glyph_metrics[cur_char].ymin;
	}
	global_bb_height = global_ymin + global_ymax;
	global_bb_width = global_max_advance;

	printf("global_bb_width: %d global_bb_height: %d global_max_advance: %d\n", global_bb_width, global_bb_height, global_max_advance);
	printf("added_spacing: %d\n", extra_char_spacing);
	image_height = image_border*2 + global_bb_height;
	image_width  = image_border*2 + (global_bb_width+extra_char_spacing)*totalchars;
	//create image
	im = gdImageCreateTrueColor(image_width, image_height);


	//white = gdImageColorResolve(im, 940, 940, 940);
	//black = gdImageColorResolve(im, 0, 255, 0);
background_color = gdImageColorResolve(im, bg[0], bg[1], bg[2]);
foreground_color = gdImageColorResolve(im, fg[0], fg[1], fg[2]);
transparency_color = gdImageColorResolve(im, trans[0], trans[1], trans[2]);


gdImageFill(im, 0, 0, background_color);

//gdImageSetAntiAliasedDontBlend(im, transparency_color, 0);
//gdImageSetAntiAliasedDontBlend(im, background_color, 1);

//gdImageFilledRectangle(im, 0, 0, image_width, image_height, background_color);

	Set_strex_flags(&Strex);

	gdImageSetResolution(im,Strex.hdpi,Strex.vdpi);

	//open output font config file
	if( ( font_fnt_out = fopen(font_file, "w" ) ) == NULL ) {
		fprintf( stderr, "Error opening %s\n", font_file );
		return 1;
	}

	fprintf(font_fnt_out, "%c image file\n", SEMICOLON);
	fprintf(font_fnt_out, "image=font.png\n");
	fprintf(font_fnt_out, "%c \"character\"=x,y,width,height\n", SEMICOLON);

	x = image_border;
	y = global_bb_height + image_border;
	for(cur_char=0; cur_char<totalchars; cur_char++){

		err = gdImageStringFTEx(im, brect, foreground_color, input_ttf, pixel_pt_size, 0.0, x, y, (const char *)glyph_metrics[cur_char].str, &Strex);
                if (err) {fprintf(stderr,err); return 1;}
		fprintf(font_fnt_out, "\"%s\"=%d,%d,%d,%d\n", glyph_metrics[cur_char].str, x, y-global_ymax, global_bb_width+extra_char_spacing, y+glyph_metrics[cur_char].ymin - y+global_ymax);
		x += global_bb_width+extra_char_spacing;
	}


	printf("total chars: %ld\n", totalchars);


	gdFree((void *)Strex.xshow);
	free(glyph_metrics);

	//replace all background color with transparency
	for(x=0; x<image_width; x++){
		for(y=0; y<image_height; y++){
			if(gdImageGetPixel(im, x, y) == background_color)
				gdImageSetPixel(im, x, y, transparency_color);
		}
	}


        image_out = fopen(image_file, "w");
        gdImageInterlace(im, 1);
        gdImagePng(im, image_out);

        fclose(image_out);
        gdImageDestroy(im);
	printf("image size: %d x %d\n", image_width, image_height);

	//free(outputstring);
	//fclose(input_hex);
	fclose(font_fnt_out);
	return 0;
}


int main(int argc, char **argv){
	const char *font_suffix = ".fnt";
	const char *image_suffix = ".png";
	int cur_color;
	char fnt[64];
	char image[64];

	#define COLORS 13
	const char *color_name[COLORS] = {"red", "green", "limegreen", "blue", "yellow", "orange", "cyan", "teal", "white", "gray", "lightgray", "saddlebrown", "purple"};
	const int bg[3] = {24, 24, 24};
	const int trans[3] = {255, 0, 255};
	const int fg[COLORS][3] = {
					//red
					{255, 0, 0},
					//green
					{0, 128, 0},
					//limegreen
					{50, 205, 50},
					//blue
					{0, 0, 255},
					//yellow
					{255, 255, 0},
					//orange
					{255, 165, 0},
					//cyan
					{0, 255, 255},
					//teal
					{0, 128, 128},
					//white
					{255, 255, 255},
					//gray
					{128, 128, 128},
					//lightgray
					{211, 211, 211},
					//saddlebrown
					{139, 69, 19},
					//purple
					{128, 0, 128}
				};
	const char *ttf_dejavu = "./DejaVuSansMono.ttf";
	const char *hex_dejavu = "./DejaVu-Sans-Mono-Book/hex.txt";

//const char *hex_dejavu = "./entities.txt";
	const char *image_dejavu = "./DejaVu-Sans-Mono-Book/font";
	const char *fnt_dejavu = "./DejaVu-Sans-Mono-Book/font";
	const char *ttf_liberation = "./LiberationMono-Regular.ttf";
	const char *hex_liberation = "./Liberation-Mono-Regular/hex.txt";
	const char *image_liberation = "./Liberation-Mono-Regular/font";
	const char *fnt_liberation = "./Liberation-Mono-Regular/font";

	const char *ttf_iosevka = "./iosevka-fixed-slab-extended.ttf";
	//const char *hex_iosevka = "./Iosevka-Fixed-Slab-Extended/hex.txt";
const char *hex_iosevka = "./entities.txt";
	const char *image_iosevka = "./Iosevka-Fixed-Slab-Extended/font";
	const char *fnt_iosevka = "./Iosevka-Fixed-Slab-Extended/font";

	const char *ttf_plex = "./IBMPlexMono-Regular.ttf";
	const char *hex_plex = "./IBM-Plex-Mono-Regular/hex.txt";
	const char *image_plex = "./IBM-Plex-Mono-Regular/font";
	const char *fnt_plex = "./IBM-Plex-Mono-Regular/font";

	const double font_size_dejavu = 5.75;
	const double font_size_liberation = 6.5;
	const double font_size_iosevka = 4.5;
	const double font_size_plex = 5.75;



cur_color=2;
                (void)sprintf(image, "%s-%s%s", image_iosevka, color_name[cur_color], image_suffix);
                //(void)sprintf(fnt, "%s-%s%s", fnt_iosevka, color_name[cur_color], font_suffix);
                (void)sprintf(fnt, "%s%s", fnt_iosevka, font_suffix);
                (void)Gen_Charset(ttf_iosevka, hex_iosevka, (const char *)image, (const char *)fnt, bg, fg[cur_color], trans, font_size_iosevka);
return 0;



//	for(cur_color=0; cur_color<COLORS; cur_color++){
		(void)sprintf(image, "%s-%s%s", image_dejavu, color_name[cur_color], image_suffix);
		//(void)sprintf(fnt, "%s-%s%s", fnt_dejavu, color_name[cur_color], font_suffix);
		(void)sprintf(fnt, "%s%s", fnt_dejavu, font_suffix);
//printf("here 1\n");
		(void)Gen_Charset(ttf_dejavu, hex_dejavu, (const char *)image, (const char *)fnt, bg, fg[cur_color], trans, font_size_dejavu);
//printf("here 2\n");

		(void)sprintf(image, "%s-%s%s", image_liberation, color_name[cur_color], image_suffix);
		//(void)sprintf(fnt, "%s-%s%s", fnt_liberation, color_name[cur_color], font_suffix);
		(void)sprintf(fnt, "%s%s", fnt_liberation, font_suffix);
		(void)Gen_Charset(ttf_liberation, hex_liberation, (const char *)image, (const char *)fnt, bg, fg[cur_color], trans, font_size_liberation);

		(void)sprintf(image, "%s-%s%s", image_iosevka, color_name[cur_color], image_suffix);
		//(void)sprintf(fnt, "%s-%s%s", fnt_iosevka, color_name[cur_color], font_suffix);
		(void)sprintf(fnt, "%s%s", fnt_iosevka, font_suffix);
		(void)Gen_Charset(ttf_iosevka, hex_iosevka, (const char *)image, (const char *)fnt, bg, fg[cur_color], trans, font_size_iosevka);

		(void)sprintf(image, "%s-%s%s", image_plex, color_name[cur_color], image_suffix);
		//(void)sprintf(fnt, "%s-%s%s", fnt_plex, color_name[cur_color], font_suffix);
		(void)sprintf(fnt, "%s%s", fnt_plex, font_suffix);
		(void)Gen_Charset(ttf_plex, hex_plex, (const char *)image, (const char *)fnt, bg, fg[cur_color], trans, font_size_plex);
	//}



	//return 0;
}
