#include <boost/dynamic_bitset.hpp>
#include <bitset>
#include <iomanip>
#include <png++/png.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <iostream>

png::rgb_pixel blue = {0x61, 0xAF, 0xEF};
png::rgb_pixel red  = {0xE0, 0x6C, 0x75};
png::rgb_pixel green= {0x98, 0xC3, 0x79};
png::rgb_pixel dblue= {0x56, 0xB6, 0xC2};
png::rgb_pixel white= {0xEE, 0xEE, 0xEE};
png::rgb_pixel purpl= {0xC6, 0x78, 0xDD};

void write_block(png::image<png::rgb_pixel> &image, const char* bitmap, FT_Vector pos_vec, FT_Vector glyph_vec, FT_Vector offset_vec, png::rgb_pixel color = {0, 0, 0}, png::rgb_pixel blank_color = {255, 255, 255})
{
		int x = pos_vec.x;
		int y = pos_vec.y;

		for (int dx = 0; dx < glyph_vec.x; dx++)
		{
			for (int dy = 0; dy < glyph_vec.y; dy++)
			{
				if (bitmap[dx + glyph_vec.x*dy] == '1')
					image.set_pixel(x+dx+offset_vec.x, y+dy+offset_vec.y, blank_color);
				else
					image.set_pixel(x+dx+offset_vec.x, y+dy+offset_vec.y, color);
			}
		}
}

namespace ft
{
	class Library 
	{
		private:
			FT_Library _l;
		public:
			Library()
			{
				FT_Init_FreeType(&this->_l);
			}

			~Library()
			{
				FT_Done_FreeType(this->_l);
			}

			operator FT_Library& ()
			{
				return this->_l;
			}
	};

	class Face
	{
		private:
			FT_Face _f;
		public:
			Face(FT_Library& lib, std::string path)
			{
				signed error;
				error = FT_New_Face(lib, path.c_str(), 0, &_f);
				if (error == FT_Err_Unknown_File_Format)
				{
					std::stringstream s;
					s << "The font you're trying to load has an unknown file format. (Font: " << path << ")." << std::endl;
					throw std::runtime_error(s.str());
				} else if (error)
				{
					std::stringstream s;
					s << "The font you're trying to load could not be opened or read, or is broken. (Font: " << path << ")." << std::endl;
					throw std::runtime_error(s.str());
				}
			}

			~Face()
			{
				FT_Done_Face(_f);
			}

			operator FT_Face& ()
			{
				return _f;
			}

			FT_Face& operator->()
			{
				return _f;
			}
	};

	signed error;
	void set_face_size(FT_Face& face, int pt)
	{
		error = FT_Set_Char_Size(face, 0, pt*64, 96, 96);
	}

	void print_available_sizes(FT_Face& face)
	{
		std::cout << "Available sizes: " << std::endl;
		int nsizes = face->num_fixed_sizes;
		for (int i = 0; i < nsizes; i++)
		{
			auto size = face->available_sizes[i];
			std::cout << "Height between baselines: " << size.height << "; Width (avg.): " << size.width << std::endl;
		}
	}

	void get_face_glyph_and_render(FT_Face& face, uint32_t charcode, FT_Render_Mode render_mode = FT_RENDER_MODE_MONO)
	{
		signed glyph_index = FT_Get_Char_Index(face, charcode);
		error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);

		if (error)
		{
			std::cerr << "Freetype error: " << error << std::endl;
		}

		error = FT_Render_Glyph(face->glyph, render_mode);
	}

	/* This function expands bitpacked monochrome bitmaps
	 * to byte-per-pixel bitmaps (which wastes space, but is
	 * much easier to handle in code.
	 */
	std::string expand_monochrome_bitmap(FT_Face& face)
	{
		assert(face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO);
		std::stringstream expanded_bitmap;

		auto& bitmap = face->glyph->bitmap;
		auto& metrics = face->glyph->metrics;

		for (int row = 0; row < bitmap.rows; row++)
		{
			signed row_width_left = bitmap.width;
			for (int packed_pixels = 0; packed_pixels < bitmap.pitch && row_width_left > 0; packed_pixels++, row_width_left-=8)
			{
				uint8_t data = bitmap.buffer[row * bitmap.pitch + packed_pixels];
				if (row_width_left < 8)
				{
					for (int bit = 1; bit <= row_width_left; bit++)
					{
						if (data & (1 << (8 - bit)))
							expanded_bitmap << '1';
						else
							expanded_bitmap << '0';
					}
				} else
				{
					for (int bit = 0; bit < 8; bit++)
					{
						if (data & (1 << (7 - bit)))
							expanded_bitmap << '1';
						else
							expanded_bitmap << '0';
					}
				}
			}
		}

		return expanded_bitmap.str();
	}

	/*
	if (face->glyph->metrics.horiBearingX > 64)
		row_string << std::string(face->glyph->metrics.horiBearingX / 64, ' ');
	*/

	template<typename T>
	decltype(auto) get_string(FT_Face& face, std::basic_string<T> str)
	{
		std::vector<std::string> rows(face->size->metrics.height / 64, "");
		auto origin = -face->size->metrics.descender / 64;
		std::cout << "origin: " << origin << std::endl;
		for (T& ch : str)
		{
			if (ch == ' ')
			{
				for (auto& line : rows)
				{
					line += std::string(face->glyph->advance.x / 128, ' ');
				}
				continue;
			}
			std::vector<std::string> out = {}; //_output_character(face, ch);

			auto yMin = (face->glyph->metrics.height - face->glyph->metrics.horiBearingY) / 64;
			auto offset = origin - yMin;
			for (auto reverse_it = out.rbegin(), line_reverse_it = rows.rbegin(); line_reverse_it != rows.rend(); line_reverse_it++)
			{
				if (reverse_it == out.rend() || offset)
				{
					*line_reverse_it += std::string(face->glyph->bitmap.width + (face->glyph->metrics.horiBearingX / 64), ' ');
					offset--;
				} else {
					*line_reverse_it += *reverse_it;
					reverse_it++;
				}
			}
		}
		return rows;
	}

	FT_Vector get_bitmap_size(FT_Face& face, uint32_t charcode)
	{
		ft::get_face_glyph_and_render(face, charcode, FT_RENDER_MODE_MONO);
		return {face->glyph->bitmap.width, face->glyph->bitmap.rows};
	}	
}

FT_Vector write_char(FT_Face& face, png::image<png::rgb_pixel> &image, uint32_t charcode, FT_Vector pos_vec, FT_Vector offset_vec)
{
	ft::get_face_glyph_and_render(face, charcode, FT_RENDER_MODE_MONO);

	auto& bitmap = face->glyph->bitmap;
	auto&& buf = ft::expand_monochrome_bitmap(face);

	write_block(image, buf.c_str(), pos_vec, {bitmap.width, bitmap.rows}, offset_vec);
	return {bitmap.width + offset_vec.x, 0};
}

template<std::size_t T>
png::image<png::rgb_pixel> write_string(FT_Face& face, const char (&str)[T])
{
	if (T <= 1)
	{
		return {};
	}

	FT_Vector pos_vec = {0, 0};
	FT_Vector glyph_size = ft::get_bitmap_size(face, 'q'); // assume monospace
	png::image<png::rgb_pixel> image((T-1) * glyph_size.x*10, glyph_size.y*10);

	for (int i = 0; i < T; ++i)
	{
		pos_vec += write_char(face, image, str[i], pos_vec, {0,0});
	}
	return image;
}

FT_Vector& operator+=(FT_Vector& rhs, FT_Vector lhs)
{
	rhs.x += lhs.x;
	rhs.y += lhs.y;
	return rhs;
}

int main(int argc, char** argv)
{
	if (argc < 3)
		return -1;

	ft::Library lib;

	ft::Face face(lib, argv[1]);
	ft::set_face_size(face, atoi(argv[2]));

	auto img = write_string(face, "abugida");
	img.write("f2test.png");

	return 0;
}
