package com.github.hydos.ginger.engine.font;

import java.io.*;
import java.util.*;

import com.github.hydos.ginger.engine.io.Window;

/** Provides functionality for getting the values from a font file. */
public class MetaFile
{
	private static final int PAD_TOP = 0;
	private static final int PAD_LEFT = 1;
	private static final int PAD_BOTTOM = 2;
	private static final int PAD_RIGHT = 3;
	private static final int DESIRED_PADDING = 8;
	private static final String SPLITTER = " ";
	private static final String NUMBER_SEPARATOR = ",";
	private double aspectRatio;
	private double verticalPerPixelSize;
	private double horizontalPerPixelSize;
	private double spaceWidth;
	private int[] padding;
	private int paddingWidth;
	private int paddingHeight;
	private Map<Integer, Character> metaData = new HashMap<Integer, Character>();
	private BufferedReader reader;
	private Map<String, String> values = new HashMap<String, String>();

	/** Opens a font file in preparation for reading.
	 * 
	 * @param file
	 *             - the font file. */
	protected MetaFile(String file)
	{
		this.aspectRatio = (double) Window.width / (double) Window.height;
		openFile(file);
		loadPaddingData();
		loadLineSizes();
		int imageWidth = getValueOfVariable("scaleW");
		loadCharacterData(imageWidth);
		close();
	}

	/** Closes the font file after finishing reading. */
	private void close()
	{
		try
		{
			reader.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	protected Character getCharacter(int ascii)
	{ return metaData.get(ascii); }

	protected double getSpaceWidth()
	{ return spaceWidth; }

	/** Gets the {@code int} value of the variable with a certain name on the
	 * current line.
	 * 
	 * @param  variable
	 *                  - the name of the variable.
	 * @return          The value of the variable. */
	private int getValueOfVariable(String variable)
	{ return Integer.parseInt(values.get(variable)); }

	/** Gets the array of ints associated with a variable on the current line.
	 * 
	 * @param  variable
	 *                  - the name of the variable.
	 * @return          The int array of values associated with the variable. */
	private int[] getValuesOfVariable(String variable)
	{
		String[] numbers = values.get(variable).split(NUMBER_SEPARATOR);
		int[] actualValues = new int[numbers.length];
		for (int i = 0; i < actualValues.length; i++)
		{ actualValues[i] = Integer.parseInt(numbers[i]); }
		return actualValues;
	}

	/** Loads all the data about one character in the texture atlas and converts
	 * it all from 'pixels' to 'screen-space' before storing. The effects of
	 * padding are also removed from the data.
	 * 
	 * @param  imageSize
	 *                   - the size of the texture atlas in pixels.
	 * @return           The data about the character. */
	private Character loadCharacter(int imageSize)
	{
		int id = getValueOfVariable("id");
		if (id == TextMeshCreator.SPACE_ASCII)
		{
			this.spaceWidth = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize;
			return null;
		}
		double xTex = ((double) getValueOfVariable("x") + (padding[PAD_LEFT] - DESIRED_PADDING)) / imageSize;
		double yTex = ((double) getValueOfVariable("y") + (padding[PAD_TOP] - DESIRED_PADDING)) / imageSize;
		int width = getValueOfVariable("width") - (paddingWidth - (2 * DESIRED_PADDING));
		int height = getValueOfVariable("height") - ((paddingHeight) - (2 * DESIRED_PADDING));
		double quadWidth = width * horizontalPerPixelSize;
		double quadHeight = height * verticalPerPixelSize;
		double xTexSize = (double) width / imageSize;
		double yTexSize = (double) height / imageSize;
		double xOff = (getValueOfVariable("xoffset") + padding[PAD_LEFT] - DESIRED_PADDING) * horizontalPerPixelSize;
		double yOff = (getValueOfVariable("yoffset") + (padding[PAD_TOP] - DESIRED_PADDING)) * verticalPerPixelSize;
		double xAdvance = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize;
		return new Character(id, xTex, yTex, xTexSize, yTexSize, xOff, yOff, quadWidth, quadHeight, xAdvance);
	}

	/** Loads in data about each character and stores the data in the
	 * {@link Character} class.
	 * 
	 * @param imageWidth
	 *                   - the width of the texture atlas in pixels. */
	private void loadCharacterData(int imageWidth)
	{
		processNextLine();
		processNextLine();
		while (processNextLine())
		{
			Character c = loadCharacter(imageWidth);
			if (c != null)
			{ metaData.put(c.getId(), c); }
		}
	}

	/** Loads information about the line height for this font in pixels, and uses
	 * this as a way to find the conversion rate between pixels in the texture
	 * atlas and screen-space. */
	private void loadLineSizes()
	{
		processNextLine();
		int lineHeightPixels = getValueOfVariable("lineHeight") - paddingHeight;
		verticalPerPixelSize = TextMeshCreator.LINE_HEIGHT / lineHeightPixels;
		horizontalPerPixelSize = verticalPerPixelSize / aspectRatio;
	}

	/** Loads the data about how much padding is used around each character in
	 * the texture atlas. */
	private void loadPaddingData()
	{
		processNextLine();
		this.padding = getValuesOfVariable("padding");
		this.paddingWidth = padding[PAD_LEFT] + padding[PAD_RIGHT];
		this.paddingHeight = padding[PAD_TOP] + padding[PAD_BOTTOM];
	}

	/** Opens the font file, ready for reading.
	 * 
	 * @param file
	 *             - the font file. */
	private void openFile(String file)
	{
		try
		{
			reader = new BufferedReader(new InputStreamReader(MetaFile.class.getResourceAsStream("/fonts/" + file)));
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.err.println("Couldn't read font meta file!");
		}
	}

	/** Read in the next line and store the variable values.
	 * 
	 * @return {@code true} if the end of the file hasn't been reached. */
	private boolean processNextLine()
	{
		values.clear();
		String line = null;
		try
		{
			line = reader.readLine();
		}
		catch (IOException e1)
		{
		}
		if (line == null)
		{ return false; }
		for (String part : line.split(SPLITTER))
		{
			String[] valuePairs = part.split("=");
			if (valuePairs.length == 2)
			{ values.put(valuePairs[0], valuePairs[1]); }
		}
		return true;
	}
}
