using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Collections.Specialized;

namespace OSGeo.MapGuide.MaestroAPI
{
    public class ConnectionProviderEntry
    {
        public string Name { get; private set; }
        public string Description { get; private set; }

        internal ConnectionProviderEntry(string name, string desc)
        {
            this.Name = name;
            this.Description = desc;
        }
    }

    /// <summary>
    /// The <see cref="ConnectionProviderRegistry"/> supports dynamic creation of <see cref="ServerConnectionI"/> objects given a provider name
    /// and a connection string, which specifies the initialization parameters of the connection. The connection providers are defined in an XML
    /// file called ConnectionProviders.xml which contains all the registered providers. Each provider has the following properties:
    /// 
    /// 1. The name of the provider
    /// 2. The assembly containing the <see cref="ServerConnectionI"/> implementation
    /// 3. The name of this <see cref="ServerConnectionI"/> implementation.
    /// 
    /// The <see cref="ServerConnectionI"/> implementation is expected to have a non-public constructor which takes a single parameter, 
    /// a <see cref="System.Collections.Specialized.NameValueCollection"/> containing the initialization parameters parsed from the given connection
    /// string.
    /// </summary>
    public sealed class ConnectionProviderRegistry
    {
        const string PROVIDER_CONFIG = "ConnectionProviders.xml";

        static Dictionary<string, Type> _ctors;
        static List<ConnectionProviderEntry> _providers;

        static ConnectionProviderRegistry()
        {
            _ctors = new Dictionary<string, Type>();
            _providers = new List<ConnectionProviderEntry>();

            XmlDocument doc = new XmlDocument();
            doc.Load(PROVIDER_CONFIG);

            XmlNodeList providers = doc.SelectNodes("//ConnectionProviderRegistry/ConnectionProvider");
            foreach (XmlNode prov in providers)
            {
                string name = prov["Name"].InnerText.ToUpper();
                string desc = prov["Description"].InnerText;
                string dll = prov["Assembly"].InnerText;
                string type = prov["Type"].InnerText;

                Assembly asm = Assembly.LoadFrom(dll);
                Type t = asm.GetType(type);

                _ctors[name] = t;
                _providers.Add(new ConnectionProviderEntry(name, desc));
            }
        }

        private static NameValueCollection ParseConnectionString(string connectionString)
        {
            NameValueCollection values = new NameValueCollection();
            string[] tokens = connectionString.Split(';');
            foreach (string tok in tokens)
            {
                string[] nameValue = tok.Split('=');

                if (nameValue.Length == 2)
                {
                    values.Add(nameValue[0], nameValue[1]);
                }
            }
            return values;
        }

        /// <summary>
        /// Gets a list of registered provider names. The returned names are in upper-case.
        /// </summary>
        /// <returns></returns>
        public static ConnectionProviderEntry[] GetProviders()
        {
            return _providers.ToArray();
        }

        /// <summary>
        /// Creates an initialized <see cref="ServerConnectionI"/> object given the provider name and connection string
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        public static ServerConnectionI CreateConnection(string provider, string connectionString)
        {
            string name = provider.ToUpper();
            if (!_ctors.ContainsKey(name))
                throw new ArgumentException("Provider not registered: " + provider);

            Type t = _ctors[name];

            NameValueCollection initParams = ParseConnectionString(connectionString);

            BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance;
            ServerConnectionI conn = (ServerConnectionI)t.InvokeMember(null, flags, null, null, new object[] { initParams });
            return conn;
        }
    }
}