package org.jrenner.smartfont;

import org.jrenner.smartfont.writer.BitmapFontWriter;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.PixmapPacker;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;

public class SmartFontGenerator {
	private static final String TAG = "SmartFontGenerator";
	private boolean forceGeneration;
	private String generatedFontDir;
	private int referenceScreenWidth;
	// TODO figure out optimal page size automatically
	private int pageSize;

	public SmartFontGenerator() {
		forceGeneration = false;
		generatedFontDir = "generated-fonts/";
		referenceScreenWidth = 1280;
		pageSize = 512; // size of atlas pages for font pngs
	}


	/** Will load font from file. If that fails, font will be generated and saved to file.
	 * @param fontFile the actual font (.otf, .ttf)
	 * @param fontName the name of the font, i.e. "arial-small", "arial-large", "monospace-10"
	 *                 This will be used for creating the font file names
	 * @param fontSize size of font when screen width equals referenceScreenWidth */
	public BitmapFont createFont(FileHandle fontFile, String fontName, int fontSize) {
		BitmapFont font = null;
		// if fonts are already generated, just load from file
		Preferences fontPrefs = Gdx.app.getPreferences("org.jrenner.smartfont");
		int displayWidth = fontPrefs.getInteger("display-width", 0);
		int displayHeight = fontPrefs.getInteger("display-height", 0);
		boolean loaded = false;
		if (displayWidth != Gdx.graphics.getWidth() || displayHeight != Gdx.graphics.getHeight()) {
			Gdx.app.debug(TAG, "Screen size change detected, regenerating fonts");
		} else {
			try {
				// try to load from file
				Gdx.app.debug(TAG, "Loading generated font from file cache");
				font = new BitmapFont(getFontFile(fontName + ".fnt", fontSize));
				loaded = true;
			} catch (GdxRuntimeException e) {
				Gdx.app.error(TAG, e.getMessage());
				Gdx.app.debug(TAG, "Couldn't load pre-generated fonts. Will generate fonts.");
			}
		}
		if (!loaded || forceGeneration) {
			forceGeneration = false;
			float width = Gdx.graphics.getWidth();
			float ratio = width / referenceScreenWidth; // use 1920x1280 as baseline, arbitrary
			float baseSize = 28f; // for 28 sized fonts at baseline width above

			// store screen width for detecting screen size change
			// on later startups, which will require font regeneration
			fontPrefs.putInteger("display-width", Gdx.graphics.getWidth());
			fontPrefs.putInteger("display-height", Gdx.graphics.getHeight());
			fontPrefs.flush();

			font = generateFontWriteFiles(fontName, fontFile, fontSize, pageSize, pageSize);
		}
		return font;
	}

	/** Convenience method for generating a font, and then writing the fnt and png files.
	 * Writing a generated font to files allows the possibility of only generating the fonts when they are missing, otherwise
	 * loading from a previously generated file.
	 * @param fontFile
	 * @param fontSize
	 */
	private BitmapFont generateFontWriteFiles(String fontName, FileHandle fontFile, int fontSize, int pageWidth, int pageHeight) {
		FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);

		PixmapPacker packer = new PixmapPacker(pageWidth, pageHeight, Pixmap.Format.RGBA8888, 2, false);
		FreeTypeFontParameter parameter = new FreeTypeFontParameter();
		parameter.size = fontSize;
		parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS;
		parameter.flip = false;
		parameter.packer = packer;
		FreeTypeFontGenerator.FreeTypeBitmapFontData fontData = generator.generateData(parameter);
		Array<PixmapPacker.Page> pages = packer.getPages();
		Array<TextureRegion> texRegions = new Array<>();
		for (int i = 0; i < pages.size; i++) {
			PixmapPacker.Page p = pages.get(i);
			Texture tex = new Texture(
					new PixmapTextureData(p.getPixmap(), p.getPixmap().getFormat(), false, false, true)) {
				@Override
				public void dispose() {
					super.dispose();
					getTextureData().consumePixmap().dispose();
				}
			};
			tex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
			texRegions.add(new TextureRegion(tex));
		}
		BitmapFont font = new BitmapFont((BitmapFont.BitmapFontData) fontData, texRegions, false);
		saveFontToFile(font, fontSize, fontName, packer);
		generator.dispose();
		packer.dispose();
		return font;
	}

	private void saveFontToFile(BitmapFont font, int fontSize, String fontName, PixmapPacker packer) {
		FileHandle fontFile = getFontFile(fontName + ".fnt", fontSize); // .fnt path
		FileHandle pixmapDir = getFontFile(fontName, fontSize); // png dir path
		BitmapFontWriter.setOutputFormat(BitmapFontWriter.OutputFormat.Text);

		String[] pageRefs = BitmapFontWriter.writePixmaps(packer.getPages(), pixmapDir, fontName);
		Gdx.app.debug(TAG, String.format("Saving font [%s]: fontfile: %s, pixmapDir: %s\n", fontName, fontFile, pixmapDir));
		// here we must add the png dir to the page refs
		for (int i = 0; i < pageRefs.length; i++) {
			pageRefs[i] = fontSize + "_" + fontName + "/" + pageRefs[i];
		}
		BitmapFontWriter.writeFont(font.getData(), pageRefs, fontFile, new BitmapFontWriter.FontInfo(fontName, fontSize), 1, 1);
	}

	private FileHandle getFontFile(String filename, int fontSize) {
		return Gdx.files.local(generatedFontDir + fontSize + "_" + filename);
	}

	// GETTERS, SETTERS -----------------------
	
	public void setForceGeneration(boolean force) {
		forceGeneration = force;
	}
	
	public boolean getForceGeneration() {
		return forceGeneration;
	}

	/** Set directory for storing generated fonts */
	public void setGeneratedFontDir(String dir) {
		generatedFontDir = dir;
	}

	/** @see org.jrenner.smartfont.SmartFontGenerator#setGeneratedFontDir(String) */
	public String getGeneratedFontDir() {
		return generatedFontDir;
	}

	/** Set the reference screen width for computing sizes.  If reference width is 1280, and screen width is 1280
	 * Then the fontSize paramater will be unaltered when creating a font.  If the screen width is 720, the font size
	 * will by scaled down to (720 / 1280) of original size. */
	public void setReferenceScreenWidth(int width) {
		referenceScreenWidth = width;
	}

	/** @see org.jrenner.smartfont.SmartFontGenerator#setReferenceScreenWidth(int) */
	public int getReferenceScreenWidth() {
		return referenceScreenWidth;
	}

	/** Set the width and height of the png files to which the fonts will be saved.
	 * In the future it would be nice for page size to be automatically set to the optimal size
	 * by the font generator.  In the mean time it must be set manually. */
	public void setPageSize(int size) {
		pageSize = size;
	}

	/** @see org.jrenner.smartfont.SmartFontGenerator#setPageSize(int) */
	public int getPageSize() {
		return pageSize;
	}
}