using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
namespace Svg
{
    /// 
    /// Provides the methods required in order to parse and create  
    internal class SvgElementFactory
    {
        private static List availableElements;
        /// 
        /// Gets a list of available types that can be used when creating an  
        private static List AvailableElements
        {
            get
            {
                if (availableElements == null)
                {
                    var svgTypes = from t in typeof(SvgDocument).Assembly.GetExportedTypes()
                                   where t.GetCustomAttributes(typeof(SvgElementAttribute), true).Length > 0
                                   && t.IsSubclassOf(typeof(SvgElement))
                                   select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t };
                    availableElements = svgTypes.ToList();
                }
                return availableElements;
            }
        }
        /// 
        /// Creates an  
        /// The null . 
        /// The CreateDocument method can only be used to parse root <svg> elements. 
        public static T CreateDocument(XmlTextReader reader) where T : SvgDocument, new()
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }
            if (reader.LocalName != "svg")
            {
                throw new InvalidOperationException("The CreateDocument method can only be used to parse root  elements.");
            }
            return (T)CreateElement(reader, true, null);
        }
        /// 
        /// Creates an  
        /// The null . 
        public static SvgElement CreateElement(XmlTextReader reader, SvgDocument document)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }
            return CreateElement(reader, false, document);
        }
        private static SvgElement CreateElement(XmlTextReader reader, bool fragmentIsDocument, SvgDocument document)  where T : SvgDocument, new()
        {
            SvgElement createdElement = null;
            string elementName = reader.LocalName;
            //Trace.TraceInformation("Begin CreateElement: {0}", elementName);
            if (elementName == "svg")
            {
                createdElement = (fragmentIsDocument) ? new T() : new SvgFragment();
            }
            else
            {
                ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName);
                if (validType != null)
                {
                    createdElement = (SvgElement)Activator.CreateInstance(validType.ElementType);
                }
            }
            if (createdElement != null)
            {
				SetAttributes(createdElement, reader, document);
            }
            //Trace.TraceInformation("End CreateElement");
            return createdElement;
        }
        private static void SetAttributes(SvgElement element, XmlTextReader reader, SvgDocument document)
        {
            //Trace.TraceInformation("Begin SetAttributes");
            string[] styles = null;
            string[] style = null;
            int i = 0;
            while (reader.MoveToNextAttribute())
            {
                // Special treatment for "style"
                if (reader.LocalName.Equals("style"))
                {
                    styles = reader.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    for (i = 0; i < styles.Length; i++)
                    {
                        if (!styles[i].Contains(":"))
                        {
                            continue;
                        }
                        style = styles[i].Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
                        SetPropertyValue(element, style[0].Trim(), style[1].Trim(), document);
                    }
                    continue; 
                }
                SetPropertyValue(element, reader.LocalName, reader.Value, document);
            }
            //Trace.TraceInformation("End SetAttributes");
        }
        private static Dictionary> _propertyDescriptors = new Dictionary>();
        private static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
        {
            var elementType = element.GetType();
            PropertyDescriptorCollection properties;
            if (_propertyDescriptors.Keys.Contains(elementType))
            {
                if (_propertyDescriptors[elementType].Keys.Contains(attributeName))
                {
                    properties = _propertyDescriptors[elementType][attributeName];
                }
                else
                {
                    properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
                    _propertyDescriptors[elementType].Add(attributeName, properties);
                }
            }
            else
            {
                properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
                _propertyDescriptors.Add(elementType, new Dictionary());
                _propertyDescriptors[elementType].Add(attributeName, properties);
            }
            if (properties.Count > 0)
            {
                PropertyDescriptor descriptor = properties[0];
                try
                {
                    descriptor.SetValue(element, descriptor.Converter.ConvertFrom(document, CultureInfo.InvariantCulture, attributeValue));
                }
                catch
                {
                    Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
                }
            }
            else
            {
                //check for namespace declaration in svg element
                if (string.Equals(element.ElementName, "svg", StringComparison.OrdinalIgnoreCase))
                {
                    if (string.Equals(attributeName, "xmlns", StringComparison.OrdinalIgnoreCase)
                        || string.Equals(attributeName, "xlink", StringComparison.OrdinalIgnoreCase)
                        || string.Equals(attributeName, "xmlns:xlink", StringComparison.OrdinalIgnoreCase)
                        || string.Equals(attributeName, "version", StringComparison.OrdinalIgnoreCase))
                    {
                        //nothing to do
                    }
                    else
                    {
                        //attribute is not a svg attribute, store it in custom attributes
                        element.CustomAttributes[attributeName] = attributeValue;
                    }
                }
                else
                {
                    //attribute is not a svg attribute, store it in custom attributes
                    element.CustomAttributes[attributeName] = attributeValue;
                }
            }
        }
        /// 
        /// Contains information about a type inheriting from  
        [DebuggerDisplay("{ElementName}, {ElementType}")]
        internal sealed class ElementInfo
        {
            /// 
            /// Gets the SVG name of the  
            public string ElementName { get; set; }
            /// 
            /// Gets the  
            public Type ElementType { get; set; }
            /// 
            /// Initializes a new instance of the  
            /// 
            /// Initializes a new instance of the  
            public ElementInfo()
            {
            }
        }
    }
}