using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.Reflection;
using System.IO;

namespace VisimBox.Scene
{
    /// <summary>
    /// Node factory used for importing / exporting  nodes
    /// </summary>
    public static class NodeFactory
    {
        #region Node registering

        private static Dictionary<string, Type> _RegisteredNodes = null;

        private static void RegisterInternal()
        {
            RegisterNodes(Assembly.GetExecutingAssembly());
        }

        /// <summary>
        /// register all PUBLIC node types from assembly
        /// </summary>
        /// <param name="assembly">Assembly containg nodes</param>
        public static void RegisterNodes(Assembly assembly)
        {
            if (_RegisteredNodes == null)
            {
                _RegisteredNodes = new Dictionary<string, Type>(50);
            }

            Type[] types = assembly.GetTypes();
            
            

            foreach (Type t in types)
            {
                if (IsNodeType(t))
                {
                    if (t.IsGenericType)
                    {
                        Console.WriteLine("Warning: Node type " + t.Name + " is generic, generic nodes are not supported!");
                    }
                    else 
                    if (!_RegisteredNodes.ContainsValue(t))
                    {
                        bool ok = false;
                        ConstructorInfo ci = t.GetConstructor(Type.EmptyTypes);
                        if (ci != null)
                            ok = true;
                        //else Console.WriteLine("Warning: Node type " + t.Name + " doesn't contain parameterless constructor!");
                        ci = t.GetConstructor(new Type[] {typeof(XmlReader)});
                        if (ci == null)
                        {
                            Console.WriteLine("Warning: Node type " + t.Name + " doesn't contain constructor for XML reading!");
                        }
                        else ok = true;
                        if (ok)
                            _RegisteredNodes.Add(t.Name, t);
                        else
                            Console.WriteLine("Node type " + t.Name + " doesn't contain parameterless or XML reading constructor - cannot be registered!");
                    }
                }
            }
        }

        private static bool IsNodeType(Type type)
        {
            Type nodetype = typeof(Node);
            if (type == nodetype)
                return true;
            if (type == typeof(object))
                return false;
            if (type.BaseType == null)
                return false;
            return IsNodeType(type.BaseType);
        }

        /// <summary>
        /// Registers one user type node to node factory for importing
        /// </summary>
        /// <param name="type">Node type (must be class inheriting from Node)</param>
        public static void RegisterNodeType(Type t)
        {
            if (_RegisteredNodes == null)
                RegisterInternal();

            if (_RegisteredNodes.ContainsValue(t))
                return;

            if (!IsNodeType(t))
                throw new ArgumentException("NodeFactory.RegisterNode - type is not NODE type!");

            bool ok = false;
            ConstructorInfo ci = t.GetConstructor(Type.EmptyTypes);
            if (ci == null)
                Console.WriteLine("Warning: Node type " + t.Name + " doesn't contain parameterless constructor!");
            else ok = true;
            ci = t.GetConstructor(new Type[] { typeof(XmlReader) });
            if (ci == null)
            {
                Console.WriteLine("Warning: Node type " + t.Name + " doesn't contain constructor for XML reading!");
            }
            else ok = true;
            if (ok)
                _RegisteredNodes.Add(t.Name, t);
            else
                Console.WriteLine("Node type " + t.Name + " doesn't contain parameterless or XML reading constructor - cannot be registered!");
        }

        /// <summary>
        /// Returns type of the node based on it's name (if it's registered)
        /// </summary>
        /// <param name="name">Type name</param>
        /// <returns>Type of Node or null</returns>
        public static Type GetNodeTypeByName(string name)
        {
            if ((name == null) || (name == ""))
                return null;
            if (_RegisteredNodes == null)
                RegisterInternal();

            if (_RegisteredNodes.ContainsKey(name))
            {
                return _RegisteredNodes[name];
            }
            return null;
        }

        /// <summary>
        /// creates new node of selected type
        /// </summary>
        /// <param name="name">Type name of the node</param>
        /// <returns>New node or null if node name is not registered</returns>
        public static Node NewNodeFromTypeName(string name)
        {
            Type type = GetNodeTypeByName(name);
            if (type == null)
                return null;

            object obj;

            try
            {
                obj = Activator.CreateInstance(type);
            }
            catch (Exception ex)
            {
                Console.DebugLine(name + " node instantiation failed - probably has no default constructor?");
                Console.DebugLine(ex.Message);
                return null;
            }

            if (obj == null)
            {
                Console.DebugLine(name + " node instantiation failed - probably has no default constructor?");
                return null;
            }

            return (Node)obj;
        }

        #endregion


        #region VSB format reading
        public static Node DirectReading(string nodetypename, XmlReader reader)
        {
            Type type = GetNodeTypeByName(nodetypename);
            if (type != null)
            {
                object obj;

                try
                {
                    obj = Activator.CreateInstance(type, new object[] { reader });
                }
                catch (Exception ex)
                {
                    Console.DebugLine(nodetypename + " node instantiation failed - has no XML reader constructor, attempting fallback");
                    Console.DebugLine(ex.Message);
                    return null;
                }
                return (Node)obj;
            }
            return null;
        }

        /// <summary>
        /// Loads Node from xmlreader. If node is of uknown type, it will be stored as UnknownNode, storing contents of it's xml node as text
        /// </summary>
        /// <param name="reader">XML reader</param>
        /// <returns>new node</returns>
        public static Node ReadXml(XmlReader reader)
        {
            string nodetypename = reader.Name;

            //first try to use XML reader

            Node rval = DirectReading(nodetypename, reader);

            if (rval != null)
                return rval;



            rval = NewNodeFromTypeName(nodetypename);

            if (rval == null)
            {
                Console.WriteLine("Unknown node type " + nodetypename);
                rval = new UnknownNode(nodetypename);
            }

            rval.ReadXml(reader);

            return rval;
        }
        #endregion


        #region Importing

        private static NodeImportSettings _Settings;
        /// <summary>
        /// Settings to use for importing
        /// </summary>
        public static NodeImportSettings Settings
        {
            get
            {
                if (_Settings == null)
                    _Settings = new NodeImportSettings();
                return _Settings;
            }
            set
            {
                _Settings = value;
            }
        }


        /// <summary>
        /// Creates new ImportedNode and loads it with data from other file
        /// </summary>
        /// <param name="filename">Filename</param>
        /// <param name="format">format of the file</param>
        /// <returns>Imported node or null if reading failed</returns>
        public static Node ImportNodes(string filename, string format = null)
        {
            ImportedNode rval = new ImportedNode();
            rval.FileName = filename;
            rval.FileFormat = format;

            if (ImportToNode(rval, filename, format))
                return rval;
            rval.Dispose();
            return null;
        }


        /// <summary>
        /// Loads node from external files and places them to the specified node
        /// </summary>
        /// <param name="node">Node to hold loaded data</param>
        /// <param name="filename">File with data</param>
        /// <param name="format">Optionally format of the data</param>
        /// <returns>True if load successful</returns>
        public static bool ImportToNode(Node node, string filename, string format)
        {
            if (format == null)
            {
                //guess from extension
                int idx = filename.LastIndexOf('.');
                if ((idx < 0) || (idx == filename.Length-1))
                {
                    Console.DebugLine("File " + filename + " has no extension, can't guess it's type!");
                    return false;
                }
                format = filename.Substring(idx + 1);
            }

            if (NodeImporters == null)
                InitDefaultImporters();

            if (NodeImporters.ContainsKey(format.ToLower()))
            {
                NodeImporters[format.ToLower()].Settings = Settings;
                return NodeImporters[format.ToLower()].Import(node, filename);
            }
            else
            {
                Console.DebugLine("Unsupported geometry format " + format);
                return false;
            }
        }

        private static void InitDefaultImporters()
        {
            NodeImporters = new Dictionary<string, NodeImporter>();
            NodeImporters.Add("obj", new LightWaveObjImporter());
        }

        private static Dictionary<string, NodeImporter> NodeImporters;

        #endregion


    }
}
