using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Collections;
using System.Xml;
using System.Drawing.Text;
using System.Drawing.Imaging;
using System.Drawing;
using Flipmind.Drawing;

namespace ImageTemplateNet
{
    /// <summary>
    /// Summary description for ImageTemplateManager
    /// </summary>
    public class ImageTemplateManager
    {
        #region "Private Static Variables"
        /// <summary>
        /// Used to log error and debug messages
        /// </summary>
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(ImageTemplateManager));
        /// <summary>
        /// The instance of the template manager that holds the loaded templates
        /// </summary>
        private static ImageTemplateManager _instance;
        #endregion

        /// <summary>
        /// The types of the elements we can expect
        /// </summary>
        Dictionary<string, ElementType> elementTypes = new Dictionary<string, ElementType>();
        /// <summary>
        /// The templates referenced by their key
        /// </summary>
        private Dictionary<string, ImageTemplate> templates = new Dictionary<string, ImageTemplate>();

        /// <summary>
        /// Loads the configuration from the image templates from the config file
        /// </summary>
        /// <param name="configPath">The path to the image template configuration file</param>
        protected ImageTemplateManager(string configPath)
        {
            System.IO.FileInfo configFile = new System.IO.FileInfo(configPath);
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(configPath);

            XmlNodeList elementTypeNodes = xmlDoc.GetElementsByTagName("elementType");
            
            foreach (XmlNode elementTypeNode in elementTypeNodes)
            {
                ElementType t = new ElementType();
                t.Element = Type.GetType(elementTypeNode.Attributes["elementClass"].Value);
                t.Config = Type.GetType(elementTypeNode.Attributes["configClass"].Value);
                elementTypes.Add (elementTypeNode.Attributes["id"].Value.ToLower(), t);
            }

            XmlNodeList templateNodes = xmlDoc.GetElementsByTagName("template");


            foreach (XmlElement templateNodeLoop in templateNodes)
            {
                XmlElement templateNode = templateNodeLoop;

                string templateId = templateNode.Attributes["id"].Value;

                //<template id="homePageProduct" width="235" height="359" imageFormat="Jpeg" backgroundColor="#FFFFFF">
                System.IO.DirectoryInfo templateBaseDir = configFile.Directory;
                if (templateNode.Attributes["configSource"] != null) // Lets the details of a template be configured in a different file
                {
                    string sConfigSourceFile = templateNode.Attributes["configSource"].Value;
                    sConfigSourceFile = System.IO.Path.Combine(configFile.DirectoryName, sConfigSourceFile);
                    System.IO.FileInfo configSourceFile = new System.IO.FileInfo(sConfigSourceFile);
                    if (configSourceFile.Exists)
                    {
                        XmlDocument xmlDoc2 = new XmlDocument();
                        xmlDoc2.Load(sConfigSourceFile);
                        templateNode = xmlDoc2.DocumentElement;
                        templateBaseDir = configSourceFile.Directory;
                    }
                }

                if (templateNode.Attributes["baseDirectory"] != null)
                {   // A template can set an explicit base directory.  This is useful when storing your images in a different place to the template definition
                    templateBaseDir = new System.IO.DirectoryInfo(templateNode.Attributes["baseDirectory"].Value);
                }

                ImageTemplate template = new ImageTemplate(templateBaseDir);
                template.Id = templateId;
                template.Width = GetElementValueAsInt(templateNode.Attributes["width"], 0);
                template.Height = GetElementValueAsInt(templateNode.Attributes["height"], 0);
                template.ImageFormat = GetElementValueAsImageFormat(templateNode.Attributes["imageFormat"], ImageFormat.Jpeg); // Enum.Parse(typeof(ImageFormat), templateNode.Attributes["imageFormat"].Value);
                template.BackgroundColor = ColorUtility.GetColor(templateNode.Attributes["backgroundColor"].Value);
                template.GraphicsUnit = (GraphicsUnit)Enum.Parse(typeof(GraphicsUnit), GetElementValue(templateNode.Attributes["graphicsUnit"], "Pixel"));
                template.Rotation = GetElementValueAsFloat(templateNode.Attributes["rotation"], 0);
                template.IsAnimation = GetElementValueAsBoolean(templateNode.Attributes["isAnimation"], false);
                template.FrameRate = GetElementValueAsInt(templateNode.Attributes["frameRate"], 25);

                foreach (XmlNode templateChildNode in templateNode.ChildNodes)
                {
                    if (templateChildNode is XmlElement)
                    {
                        XmlElement templateChildElement = (XmlElement)templateChildNode;
                        if (templateChildNode.LocalName == "element")
                        {
                            TemplateElementHolder ele = GetTemplateElementHolder(configFile, null, templateChildElement);
                            if (ele != null)
                            {
                                template.Elements.Add(ele);
                            }
                        }
                    }
                }
                templates.Add(template.Id, template);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="configFile"></param>
        /// <param name="parentConfig"></param>
        /// <param name="templateChildNode"></param>
        /// <returns></returns>
        private TemplateElementHolder GetTemplateElementHolder(System.IO.FileInfo configFile, ElementConfig parentConfig, XmlElement templateChildNode)
        {
            string eleId = GetElementValue(templateChildNode.Attributes["id"]);//.Value;


            int x = Convert.ToInt32(templateChildNode.Attributes["x"].Value);
            int y = Convert.ToInt32(templateChildNode.Attributes["y"].Value);
            string eleWidth = GetElementValueAsString(templateChildNode.Attributes["width"], "0");
            string eleHeight = GetElementValueAsString(templateChildNode.Attributes["height"], "0");
            uint eleDuration = (uint) GetElementValueAsInt(templateChildNode.Attributes["duration"],0);
            float eleRotation = GetElementValueAsFloat(templateChildNode.Attributes["rotation"], 0);
            Color eleBackground = GetElementValueAsColor(templateChildNode.Attributes["backgroundColor"],Color.Transparent);

            string eleType = templateChildNode.Attributes["type"].Value;
            

            ElementType elementT;
            if (elementTypes.TryGetValue(eleType.ToLower(), out elementT))
            {
                ElementConfig config = (ElementConfig) Activator.CreateInstance(elementT.Config,  new object[] { parentConfig } );
                TemplateElement element = (TemplateElement)Activator.CreateInstance(elementT.Element);
                config.Id = eleId;
                config.X = x;
                config.Y = y;
                config.Width = eleWidth;
                config.Height = eleHeight;
                //config.Rotation = eleRotation;
                config.Duration = eleDuration;
                config.BackgroundColor = eleBackground;

                // Set the element specific config
                XmlElement xmlParameters = templateChildNode["parameters"];
                if (xmlParameters != null)
                {
                    IDictionary parameters = Flipmind.Common.Xml.XmlUtils.GetNodesAsDictionary(xmlParameters); 
                    foreach (String propertyName in parameters.Keys)
                    {
                        setConvertedValue((string)parameters[propertyName],
                                           config.GetType().GetProperty(propertyName),
                                           config);
                    }
                }
                List<TemplateElementHolder> children = new List<TemplateElementHolder>();
                foreach (XmlNode childNode in templateChildNode.ChildNodes)
                {
                    if (childNode is XmlElement)
                    {
                        if (childNode.LocalName == "element")
                        {
                            XmlElement templateChildElement = (XmlElement)childNode;
                            TemplateElementHolder child = GetTemplateElementHolder(configFile, config, templateChildElement);
                            if (child != null)
                            {
                                children.Add(child);
                            }
                        }
                    }
                }
                return new TemplateElementHolder(element, config, children);
            }
            return null;
        }

        private string GetElementValue(XmlAttribute attrib)
        {
            return GetElementValue(attrib, null);
        }

        private Color GetElementValueAsColor(XmlAttribute attrib, Color defaultValue)
        {
            if (attrib != null)
            {
                return ColorUtility.GetColor(attrib.Value);
            }
            return defaultValue;
        }


        private ImageFormat GetElementValueAsImageFormat(XmlAttribute attrib, ImageFormat defaultValue)
        {
            if (attrib != null)
            {
                ImageFormatConverter c = new ImageFormatConverter();
                return (ImageFormat)c.ConvertFromString(attrib.Value);
            }
            return defaultValue;
        }

        private string GetElementValueAsString(XmlAttribute attrib, string defaultValue)
        {
            if (attrib != null)
            {
                return attrib.Value;
            }
            return defaultValue;
        }

        private Dimension GetElementValueAsDimension(XmlAttribute attrib, int defaultValue)
        {
            if (attrib != null)
            {
                return new Dimension(attrib.Value, defaultValue);
            }
            return new Dimension(SizeType.Absolute, defaultValue);
        }

        private int GetElementValueAsInt(XmlAttribute attrib, int defaultValue)
        {
            try
            {
                if (attrib != null)
                {
                    return Convert.ToInt32(attrib.Value);
                }
            }
            catch (Exception ex)
            {
            }
            return defaultValue;
        }

        private bool GetElementValueAsBoolean(XmlAttribute attrib, bool defaultValue)
        {
            try
            {
                if (attrib != null)
                {
                    return Convert.ToBoolean(attrib.Value);
                }
            }
            catch (Exception ex)
            {
            }
            return defaultValue;
        }

        private float GetElementValueAsFloat(XmlAttribute attrib, float defaultValue)
        {
            try
            {
                if (attrib != null)
                {
                    return Convert.ToSingle(attrib.Value);
                }
            }
            catch (Exception ex)
            {
            }
            return defaultValue;
        }

        private string GetElementValue(XmlAttribute attrib, string defaultValue)
        {
            if (attrib != null)
            {
                return attrib.Value;
            }
            return defaultValue;
        }

        public TextStyle GetTextStyle(string id)
        {
            return null;
        }

        public PenStyle GetPenStyle(string id)
        {
            return null;
        }

        public ImageTemplate GetTemplate(string id)
        {
            ImageTemplate val = null;
            if (id != null)
            {
                templates.TryGetValue(id, out val);
            }
            return val;
        }

        public List<ImageTemplate> GetTemplates()
        {
            return new List<ImageTemplate>(templates.Values);
        }


        public static void ResetTemplates()
        {
            _instance = null;
        }


        public ImageTemplate GetDefaultTemplate()
        {
            return GetTemplate("defaultTemplate");
        }

        private static DateTime lastConfigLoadTime;

        public static ImageTemplateManager Instance
        {
            get
            {
                TimeSpan ts = DateTime.Now.Subtract(lastConfigLoadTime);
                if (_instance == null || ts.Seconds > 10)
                {
                    string imageTemplateConfigPath = ImageTemplateNet.ImageTemplateConfiguration.Instance.ConfigFilePath;
                    if (imageTemplateConfigPath.StartsWith("~"))
                    {
                        imageTemplateConfigPath = System.Web.HttpContext.Current.Server.MapPath(imageTemplateConfigPath);
                    }
                    //Restart the config
                    _instance = new ImageTemplateManager(imageTemplateConfigPath);
                    // Remove all cached files
                    CachedFileManager.Instance.PurgeDirectory();
                    lastConfigLoadTime = DateTime.Now;
                }
                return _instance;
            }
        }

        public static void setConvertedValue(string propertyValue, System.Reflection.PropertyInfo prop, ElementConfig config)
        {
            if (prop != null && prop.CanWrite)
            {
                Object objValue = null;
                if (prop.PropertyType.IsAssignableFrom(typeof(FontFamily)))
                {
                    foreach (FontFamily family in FontFamily.Families)
                    {
                        if (string.Compare(family.Name, propertyValue, true) == 0)
                        {
                            objValue = family;
                            break;
                        }
                    }
                    if (objValue == null)
                    {
                        log.Warn("The font '" + propertyValue + "' does not exist.  You need to install it to the windows font directory (usually c:\\windows\fonts\\ if you want to use it.  Using a default font");
                        objValue = FontFamily.GenericSansSerif;
                    }
                }
                else if (prop.PropertyType.IsEnum)
                {
                    objValue = Enum.Parse(prop.PropertyType, propertyValue);
                }
                else if (prop.PropertyType.IsAssignableFrom (typeof(Range<float>))) {
                    if (!String.IsNullOrEmpty(propertyValue))
                    {
                        String[] rangeParts =propertyValue.Split(',');
                        if (rangeParts.Length > 1)
                        {
                            String part1 = rangeParts[0];
                            String part2 = rangeParts[1];
                            if (String.IsNullOrEmpty(part2))
                            {
                                part2 = part1;
                            }
                            objValue = new Range<float>(Convert.ToSingle(part1), Convert.ToSingle(part2));
                        }
                        else
                        {
                            objValue = new Range<float>(Convert.ToSingle(propertyValue), Convert.ToSingle(propertyValue));
                        }
                    }
                }
                else if (prop.PropertyType.IsAssignableFrom(typeof(Color)))
                {
                    ColorConverter colorConvert = new ColorConverter();
                    objValue = colorConvert.ConvertFromString(propertyValue);
                }
                else
                {
                    objValue = Convert.ChangeType(propertyValue, prop.PropertyType);
                }
                prop.SetValue(config, objValue, null);
            }
        }
    }
}