// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SvgRenderContext.cs" company="OxyPlot">
//   Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
//   Provides a render context for scalable vector graphics output.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text.RegularExpressions;

    /// <summary>
    /// Provides a render context for scalable vector graphics output.
    /// </summary>
    public class SvgRenderContext : RenderContextBase, IDisposable
    {
		private class FontDescriptor
		{
			public FontDescriptor(String fontFamily, String fontSrc)
			{
				FontFamily = fontFamily;
				FontSrc = fontSrc;
			}

			public String FontFamily { get; set; }
			public String FontSrc { get; set; }
		}

        private Dictionary<string, FontDescriptor> customFontsTable;
        private static Dictionary<string, OxyFontHolder> customFontDataTable = new Dictionary<string, OxyFontHolder>();

        /// <summary>
        /// The writer.
        /// </summary>
        private readonly SvgWriter w;

        /// <summary>
        /// The disposed flag.
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Initializes a new instance of the <see cref="SvgRenderContext" /> class.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="isDocument">Create an SVG document if set to <c>true</c>.</param>
        /// <param name="textMeasurer">The text measurer.</param>
        /// <param name="background">The background.</param>
        public SvgRenderContext(Stream s, double width, double height, bool isDocument, IRenderContext textMeasurer, OxyColor background)
        {
            if (textMeasurer == null)
            {
                throw new ArgumentNullException("textMeasurer", "A text measuring render context must be provided.");
            }

            this.w = new SvgWriter(s, width, height, isDocument);
            this.TextMeasurer = textMeasurer;
            if (background.IsVisible())
            {
                this.w.WriteRectangle(0, 0, width, height, this.w.CreateStyle(background, OxyColors.Undefined, 0));
            }
			this.customFontsTable = new Dictionary<string, FontDescriptor>();
        }

        /// <summary>
        /// Gets or sets the text measurer.
        /// </summary>
        /// <value>The text measurer.</value>
        public IRenderContext TextMeasurer { get; set; }

        /// <summary>
        /// Closes the svg writer.
        /// </summary>
        public void Close()
        {
            this.w.Close();
        }

        /// <summary>
        /// Completes the svg element.
        /// </summary>
        public void Complete()
        {
        	Defs();
            this.w.Complete();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Draws an ellipse.
        /// </summary>
        /// <param name="rect">The rectangle.</param>
        /// <param name="fill">The fill color.</param>
        /// <param name="stroke">The stroke color.</param>
        /// <param name="thickness">The thickness.</param>
        public override void DrawEllipse(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
        {
            this.w.WriteEllipse(rect.Left, rect.Top, rect.Width, rect.Height, this.w.CreateStyle(fill, stroke, thickness));
        }

        /// <summary>
        /// Draws the polyline from the specified points.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="stroke">The stroke color.</param>
        /// <param name="thickness">The stroke thickness.</param>
        /// <param name="dashArray">The dash array.</param>
        /// <param name="lineJoin">The line join type.</param>
        /// <param name="aliased">if set to <c>true</c> the shape will be aliased.</param>
        public override void DrawLine(
            IList<ScreenPoint> points,
            OxyColor stroke,
            double thickness,
            double[] dashArray,
            LineJoin lineJoin,
            bool aliased)
        {
            this.w.WritePolyline(points, this.w.CreateStyle(OxyColors.Undefined, stroke, thickness, dashArray, lineJoin));
        }

        /// <summary>
        /// Draws the polygon from the specified points. The polygon can have stroke and/or fill.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="fill">The fill color.</param>
        /// <param name="stroke">The stroke color.</param>
        /// <param name="thickness">The stroke thickness.</param>
        /// <param name="dashArray">The dash array.</param>
        /// <param name="lineJoin">The line join type.</param>
        /// <param name="aliased">if set to <c>true</c> the shape will be aliased.</param>
        public override void DrawPolygon(
            IList<ScreenPoint> points,
            OxyColor fill,
            OxyColor stroke,
            double thickness,
            double[] dashArray,
            LineJoin lineJoin,
            bool aliased)
        {
            this.w.WritePolygon(points, this.w.CreateStyle(fill, stroke, thickness, dashArray, lineJoin));
        }

        /// <summary>
        /// Draws the rectangle.
        /// </summary>
        /// <param name="rect">The rectangle.</param>
        /// <param name="fill">The fill color.</param>
        /// <param name="stroke">The stroke color.</param>
        /// <param name="thickness">The stroke thickness.</param>
        public override void DrawRectangle(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
        {
            this.w.WriteRectangle(rect.Left, rect.Top, rect.Width, rect.Height, this.w.CreateStyle(fill, stroke, thickness));
        }

        /// <summary>
        /// Draws the text.
        /// </summary>
        /// <param name="p">The p.</param>
        /// <param name="text">The text.</param>
        /// <param name="c">The c.</param>
        /// <param name="fontFamily">The font family.</param>
        /// <param name="fontSize">Size of the font.</param>
        /// <param name="fontWeight">The font weight.</param>
        /// <param name="rotate">The rotate.</param>
        /// <param name="halign">The horizontal alignment.</param>
        /// <param name="valign">The vertical alignment.</param>
        /// <param name="maxSize">Size of the max.</param>
        public override void DrawText(
            ScreenPoint p,
            string text,
            OxyColor c,
            string fontFamily,
            double fontSize,
            double fontWeight,
            double rotate,
            HorizontalAlignment halign,
            VerticalAlignment valign,
            OxySize? maxSize)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

			String fontUri = null;
			if (customFontsTable.ContainsKey(fontFamily))
			{
				fontUri = customFontsTable[fontFamily].FontSrc;
				fontFamily = customFontsTable[fontFamily].FontFamily;
			}

			if (fontFamily.Contains("/")) {
				fontFamily = AddCustomFont(fontFamily);
			}

            var lines = Regex.Split(text, "\r\n");
            if (valign == VerticalAlignment.Bottom)
            {
                for (var i = lines.Length - 1; i >= 0; i--)
                {
                    var line = lines[i];
                    var size = this.MeasureText(line, fontFamily, fontSize, fontWeight);
                    this.w.WriteText(p, line, c, fontFamily, fontSize, fontWeight, rotate, halign, valign);

                    p += new ScreenVector(Math.Sin(rotate / 180.0 * Math.PI) * size.Height, Math.Cos(rotate / 180.0 * Math.PI) * size.Height);
                }
            }
            else
            {
                foreach (var line in lines)
                {
                    var size = this.MeasureText(line, fontFamily, fontSize, fontWeight);
                    this.w.WriteText(p, line, c, fontFamily, fontSize, fontWeight, rotate, halign, valign);

                    p += new ScreenVector(-Math.Sin(rotate / 180.0 * Math.PI) * size.Height, Math.Cos(rotate / 180.0 * Math.PI) * size.Height);
                }
            }
        }

        /// <summary>
        /// Flushes this instance.
        /// </summary>
        public void Flush()
        {
            this.w.Flush();
        }

        /// <summary>
        /// Measures the text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="fontFamily">The font family.</param>
        /// <param name="fontSize">Size of the font.</param>
        /// <param name="fontWeight">The font weight.</param>
        /// <returns>The text size.</returns>
        public override OxySize MeasureText(string text, string fontFamily, double fontSize, double fontWeight)
        {
            if (string.IsNullOrEmpty(text))
            {
                return OxySize.Empty;
            }

			if (fontFamily.Contains("/")) {
				fontFamily = AddCustomFont(fontFamily);
			}

			if (customFontsTable.ContainsKey(fontFamily))
			{
				fontFamily = customFontsTable[fontFamily].FontFamily;

               	OxyFontHolder fh = customFontDataTable[fontFamily];
               	if(fh == null) {
            return this.TextMeasurer.MeasureText(text, fontFamily, fontSize, fontWeight);
        }

               	fh = new OxyFontHolder(fh, (float)fontSize, (float)fontWeight);
               	fh.register(text);

                var lines = Regex.Split(text, "\r\n");
    			var lineSpacing = fh.Height / 3.0;
    			OxySize result = new OxySize(0, -lineSpacing);

                foreach (var line in lines)
                {
    				result.Height += fh.Height + lineSpacing;
                    result.Width = Math.Max(fh.MeasureTextWidth(line), result.Width);
                }

                return result;
			}
            
            return this.TextMeasurer.MeasureText(text, fontFamily, fontSize, fontWeight);
        }

        /// <summary>
		/// Add custom font to render context.
		/// </summary>
		/// <param name="fontUri">Uri where file located.</param>
		/// <returns>Font family name.</returns>
		public override string AddCustomFont(string fontUri)
		{
		    IEnumerator<string> i = customFontsTable.Keys.GetEnumerator();
		    while(i.MoveNext()) {
		    	var fd = customFontsTable[i.Current];
		    	if(fd.FontSrc == fontUri) return fd.FontFamily;
		    }

        	var fh = new OxyFontHolder(fontUri, 1.0f);
        	var fontFamily = fh.SvgFontName;
        	customFontDataTable.Add(fontFamily, fh);
			customFontsTable.Add(fontFamily, new FontDescriptor(fontFamily, fontUri));
            return fontFamily;
		}

        public override string AddCustomFont(string aliasName, string fontUri)
        {
		    IEnumerator<string> i = customFontsTable.Keys.GetEnumerator();
		    while(i.MoveNext()) {
		    	var fd = customFontsTable[i.Current];
		    	if(fd.FontSrc == fontUri) return fd.FontFamily;
		    }

		    var fontFamily = AddCustomFont(fontUri);
			customFontsTable.Add(aliasName, new FontDescriptor(fontFamily, fontUri));
            return fontFamily;
        }

        /// <summary>
        /// Draws the specified portion of the specified <see cref="OxyImage" /> at the specified location and with the specified size.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="srcX">The x-coordinate of the upper-left corner of the portion of the source image to draw.</param>
        /// <param name="srcY">The y-coordinate of the upper-left corner of the portion of the source image to draw.</param>
        /// <param name="srcWidth">Width of the portion of the source image to draw.</param>
        /// <param name="srcHeight">Height of the portion of the source image to draw.</param>
        /// <param name="destX">The x-coordinate of the upper-left corner of drawn image.</param>
        /// <param name="destY">The y-coordinate of the upper-left corner of drawn image.</param>
        /// <param name="destWidth">The width of the drawn image.</param>
        /// <param name="destHeight">The height of the drawn image.</param>
        /// <param name="opacity">The opacity.</param>
        /// <param name="interpolate">Interpolate if set to <c>true</c>.</param>
        public override void DrawImage(
            OxyImage source,
            double srcX,
            double srcY,
            double srcWidth,
            double srcHeight,
            double destX,
            double destY,
            double destWidth,
            double destHeight,
            double opacity,
            bool interpolate)
        {
            this.w.WriteImage(srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, source);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.w.Dispose();
                }
            }

            this.disposed = true;
        }

        private void Defs() 
        {
        	w.WriteStartElementP("defs");
        	IEnumerator<string> i = customFontsTable.Keys.GetEnumerator();
        	while(i.MoveNext()) {
        		var faceName = customFontsTable[i.Current].FontFamily;
               	OxyFontHolder fh = customFontDataTable[faceName];
               	if(fh != null) {
	               	fh.dumpFontGlyphs(w);
	            }
        	}
            w.WriteEndElementP();
        }
    }
}