using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.IO;
using System.Globalization;
using FLocal.Core;

namespace FLocal.IISHandler {
	class TemplateEngine {

		public static class EncodingInfo {

			public class XmlEncoderReplacementFallback : EncoderFallback {

				private readonly Encoding originalEncoding;

				public XmlEncoderReplacementFallback(Encoding encoding) {
					this.originalEncoding = encoding;
				}

				public override EncoderFallbackBuffer CreateFallbackBuffer() {
					return new XmlEncoderReplacementFallbackBuffer(this.originalEncoding);
				}

				public override int MaxCharCount {
					get {
						return 20;
					}
				}

			}

			private class XmlEncoderReplacementFallbackBuffer : EncoderFallbackBuffer {

				const int SurHighStart = 0xd800;
				const int SurHighEnd   = 0xdbff; 
				const int SurLowStart  = 0xdc00; 
				const int SurLowEnd    = 0xdfff;

				int fallbackIndex = -1;
				string currentReplacement = "";

				private readonly Encoding originalEncoding;

				public XmlEncoderReplacementFallbackBuffer(Encoding originalEncoding) {
					this.originalEncoding = originalEncoding;
				}

				private bool Fallback(int ch) {
					this.currentReplacement = string.Format(CultureInfo.InvariantCulture, "&#x{0:X};", ch);
					return this.currentReplacement.Length > 0;
				}

				public override bool Fallback(char highChar, char lowChar, int index) {
					if (!char.IsSurrogatePair(highChar, lowChar)) {
						throw new CriticalException("Invalid surrogate pair: " + (int)lowChar + (int)highChar); 
					}
					return this.Fallback(char.ConvertToUtf32(highChar, lowChar));
				}

				public override bool Fallback(char ch, int index) {
					if (char.IsSurrogate(ch)) {
						throw new FLocalException("Wrong char: " + (int)ch);
					} 
					return this.Fallback(ch);
				}

				public override char GetNextChar() {
					this.fallbackIndex++;
					return this.currentReplacement[this.fallbackIndex];
				}

				public override bool MovePrevious() {
					if(this.fallbackIndex > 0) {
						this.fallbackIndex--;
						return true;
					} else {
						return false;
					}
				}

				public override int Remaining {
					get {
						return this.currentReplacement.Length - this.fallbackIndex - 1;
					}
				}

				public override void Reset() {
					this.fallbackIndex = -1;
					this.currentReplacement = "";
					base.Reset();
				}
			}

		}

		private class TemplateCacher {

			public static TemplateCacher instance = new TemplateCacher();

			private object locker = new object();

			private Dictionary<string, XslCompiledTransform> cache = new Dictionary<string,XslCompiledTransform>();

			public XslCompiledTransform getCompiledTransform(string templateName) {
				if(!this.cache.ContainsKey(templateName)) {
					lock(this.locker) {
						if(!this.cache.ContainsKey(templateName)) {
							XslCompiledTransform xslt = new XslCompiledTransform();
							xslt.Load(FLocal.Common.Config.instance.dataDir + "Templates" + FLocal.Common.Config.instance.DirSeparator + templateName);
							this.cache[templateName] = xslt;
						}
					}
				}
				return this.cache[templateName];
			}

		}

		public static void WriteCompiled(string templateName, XDocument data, TextWriter outStream) {
			//var fallback = new EncodingInfo.XmlEncoderReplacementFallback(outStream.Encoding);
			using(XmlWriter writer = XmlWriter.Create(outStream, new XmlWriterSettings { Indent = false })) {
				using(XmlReader reader = data.CreateReader()) {
					TemplateCacher.instance.getCompiledTransform(templateName).Transform(reader, writer);
				}
			}
		}

	}
}
