﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml;

// public domain assumed from cyotek.com
namespace Cyotek.Drawing.BitmapFont
{
	// Parsing class for bitmap fonts generated by AngelCode BMFont
	// http://www.angelcode.com/products/bmfont/

	public static class BitmapFontLoader
	{
		/// <summary>
		/// Loads a bitmap font from an XML file.
		/// </summary>
		public static BitmapFont LoadFontFromXmlFile(Stream stream)
		{
			var document = new XmlDocument();
			IDictionary<int, Page> pageData = new SortedDictionary<int, Page>();
			IDictionary<Kerning, int> kerningDictionary = new Dictionary<Kerning, int>();
			IDictionary<char, Character> charDictionary = new Dictionary<char, Character>();
			var font = new BitmapFont();

			document.Load(stream);
			XmlNode root = document.DocumentElement;

			// load the basic attributes
			var properties = root.SelectSingleNode("info");
			font.FamilyName = properties.Attributes["face"].Value;
			font.FontSize = Convert.ToInt32(properties.Attributes["size"].Value);
			font.Bold = Convert.ToInt32(properties.Attributes["bold"].Value) != 0;
			font.Italic = Convert.ToInt32(properties.Attributes["italic"].Value) != 0;
			font.Unicode = Convert.ToInt32(properties.Attributes["unicode"].Value) != 0;
			font.StretchedHeight = Convert.ToInt32(properties.Attributes["stretchH"].Value);
			font.Charset = properties.Attributes["charset"].Value;
			font.Smoothed = Convert.ToInt32(properties.Attributes["smooth"].Value) != 0;
			font.SuperSampling = Convert.ToInt32(properties.Attributes["aa"].Value);
			font.Padding = ParsePadding(properties.Attributes["padding"].Value);
			font.Spacing = ParsePoint(properties.Attributes["spacing"].Value);
			font.OutlineSize = Convert.ToInt32(properties.Attributes["outline"].Value);

			// common attributes
			properties = root.SelectSingleNode("common");
			font.BaseHeight = Convert.ToInt32(properties.Attributes["lineHeight"].Value);
			font.LineHeight = Convert.ToInt32(properties.Attributes["base"].Value);
			font.TextureSize = new Size
			(
			  Convert.ToInt32(properties.Attributes["scaleW"].Value),
			  Convert.ToInt32(properties.Attributes["scaleH"].Value)
			);
			font.Packed = Convert.ToInt32(properties.Attributes["packed"].Value) != 0;
			font.AlphaChannel = Convert.ToInt32(properties.Attributes["alphaChnl"].Value);
			font.RedChannel = Convert.ToInt32(properties.Attributes["redChnl"].Value);
			font.GreenChannel = Convert.ToInt32(properties.Attributes["greenChnl"].Value);
			font.BlueChannel = Convert.ToInt32(properties.Attributes["blueChnl"].Value);

			// load texture information
			foreach (XmlNode node in root.SelectNodes("pages/page"))
			{
				var page = new Page
				{
					Id = Convert.ToInt32(node.Attributes["id"].Value),
					FileName = node.Attributes["file"].Value
				};

				pageData.Add(page.Id, page);
			}
			font.Pages = ToArray(pageData.Values);

			// load character information
			foreach (XmlNode node in root.SelectNodes("chars/char"))
			{
				var character = new Character
				{
					Char = (char) Convert.ToInt32(node.Attributes["id"].Value),
					Bounds = new Rectangle
					(
						Convert.ToInt32(node.Attributes["x"].Value),
						Convert.ToInt32(node.Attributes["y"].Value),
						Convert.ToInt32(node.Attributes["width"].Value),
						Convert.ToInt32(node.Attributes["height"].Value)
					),
					Offset = new Point
					(
						Convert.ToInt32(node.Attributes["xoffset"].Value),
						Convert.ToInt32(node.Attributes["yoffset"].Value)
					),
					XAdvance = Convert.ToInt32(node.Attributes["xadvance"].Value),
					TexturePage = Convert.ToInt32(node.Attributes["page"].Value),
					Channel = Convert.ToInt32(node.Attributes["chnl"].Value)
				};

				charDictionary.Add(character.Char, character);
			}

			font.Characters = charDictionary;

			// loading kerning information
			foreach (XmlNode node in root.SelectNodes("kernings/kerning"))
			{
				var key = new Kerning((char)Convert.ToInt32(node.Attributes["first"].Value), (char)Convert.ToInt32(node.Attributes["second"].Value), Convert.ToInt32(node.Attributes["amount"].Value));

				if (!kerningDictionary.ContainsKey(key))
				{
					kerningDictionary.Add(key, key.Amount);
				}
			}

			font.Kernings = kerningDictionary;

			return font;
		}

		/// <summary>
		/// Creates a Padding object from a string representation
		/// </summary>
		/// <param name="s">The string.</param>
		private static Padding ParsePadding(string s)
		{
			var parts = s.Split(',');

			return new Padding
			{
				Left = Convert.ToInt32(parts[3].Trim()),
				Top = Convert.ToInt32(parts[0].Trim()),
				Right = Convert.ToInt32(parts[1].Trim()),
				Bottom = Convert.ToInt32(parts[2].Trim())
			};
		}

		/// <summary>
		/// Creates a Point object from a string representation
		/// </summary>
		/// <param name="s">The string.</param>
		private static Point ParsePoint(string s)
		{
			var parts = s.Split(',');

			return new Point
			{
				X = Convert.ToInt32(parts[0].Trim()),
				Y = Convert.ToInt32(parts[1].Trim())
			};
		}

		/// <summary>
		/// Converts the given collection into an array
		/// </summary>
		/// <typeparam name="T">Type of the items in the array</typeparam>
		/// <param name="values">The values.</param>
		private static T[] ToArray<T>(ICollection<T> values)
		{
			// avoid a forced .NET 3 dependency just for one call to Linq
			var result = new T[values.Count];
			values.CopyTo(result, 0);

			return result;
		}
	}
}
