/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the OpenSim Project nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 
*/

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

using libsecondlife;

using OpenSim.Framework.Console;
using OpenSim.Framework;
using System.Globalization;

namespace OpenSim.Framework
{
    public class ConfigurationMember
    {
        public delegate bool ConfigurationOptionResult(string configuration_key, object configuration_result);
        public delegate void ConfigurationOptionsLoad();

        private List<ConfigurationOption> configurationOptions = new List<ConfigurationOption>();
        private string configurationFilename = "";
        private string configurationDescription = "";
        private XmlNode configurationFromXMLNode = null;
        private ConfigurationOptionsLoad loadFunction;
        private ConfigurationOptionResult resultFunction;

        private IGenericConfig configurationPlugin = null;
        /// <summary>
        /// This is the default configuration DLL loaded
        /// </summary>
        private string configurationPluginFilename = "OpenSim.Framework.Configuration.XML.dll";
        public ConfigurationMember(string configuration_filename, string configuration_description, ConfigurationOptionsLoad load_function, ConfigurationOptionResult result_function)
        {
            this.configurationFilename = configuration_filename;
            this.configurationDescription = configuration_description;
            this.loadFunction = load_function;
            this.resultFunction = result_function;
        }
        public ConfigurationMember(XmlNode configuration_xml, string configuration_description, ConfigurationOptionsLoad load_function, ConfigurationOptionResult result_function)
        {
            this.configurationFilename = "";
            this.configurationFromXMLNode = configuration_xml;
            this.configurationDescription = configuration_description;
            this.loadFunction = load_function;
            this.resultFunction = result_function;
        }
        public void setConfigurationFilename(string filename)
        {
            configurationFilename = filename;
        }
        public void setConfigurationDescription(string desc)
        {
            configurationDescription = desc;
        }

        public void setConfigurationResultFunction(ConfigurationOptionResult result)
        {
            resultFunction = result;
        }

        public void forceConfigurationPluginLibrary(string dll_filename)
        {
            configurationPluginFilename = dll_filename;
        }
        private void checkAndAddConfigOption(ConfigurationOption option)
        {
            if ((option.configurationKey != "" && option.configurationQuestion != "") || (option.configurationKey != "" && option.configurationUseDefaultNoPrompt))
            {
                if (!configurationOptions.Contains(option))
                {
                    configurationOptions.Add(option);
                }
            }
            else
            {
                MainLog.Instance.Notice("Required fields for adding a configuration option is invalid. Will not add this option (" + option.configurationKey + ")");
            }
        }

        public void addConfigurationOption(string configuration_key, ConfigurationOption.ConfigurationTypes configuration_type, string configuration_question, string configuration_default, bool use_default_no_prompt)
        {
            ConfigurationOption configOption = new ConfigurationOption();
            configOption.configurationKey = configuration_key;
            configOption.configurationQuestion = configuration_question;
            configOption.configurationDefault = configuration_default;
            configOption.configurationType = configuration_type;
            configOption.configurationUseDefaultNoPrompt = use_default_no_prompt;
            configOption.shouldIBeAsked = null; //Assumes true, I can ask whenever
            checkAndAddConfigOption(configOption);
        }

        public void addConfigurationOption(string configuration_key, ConfigurationOption.ConfigurationTypes configuration_type, string configuration_question, string configuration_default, bool use_default_no_prompt, ConfigurationOption.ConfigurationOptionShouldBeAsked shouldIBeAskedDelegate)
        {
            ConfigurationOption configOption = new ConfigurationOption();
            configOption.configurationKey = configuration_key;
            configOption.configurationQuestion = configuration_question;
            configOption.configurationDefault = configuration_default;
            configOption.configurationType = configuration_type;
            configOption.configurationUseDefaultNoPrompt = use_default_no_prompt;
            configOption.shouldIBeAsked = shouldIBeAskedDelegate;
            checkAndAddConfigOption(configOption);
        }

        public void performConfigurationRetrieve()
        {
            configurationPlugin = this.LoadConfigDll(configurationPluginFilename);
            configurationOptions.Clear();
            if(loadFunction == null)
            {
                MainLog.Instance.Error("Load Function for '" + this.configurationDescription + "' is null. Refusing to run configuration.");
                return;
            }

            if(resultFunction == null)
            {
                MainLog.Instance.Error("Result Function for '" + this.configurationDescription + "' is null. Refusing to run configuration.");
                return;
            }

            MainLog.Instance.Verbose("CONFIG", "Calling Configuration Load Function...");
            this.loadFunction();

            if(configurationOptions.Count <= 0)
            {
                MainLog.Instance.Error("CONFIG", "No configuration options were specified for '" + this.configurationOptions + "'. Refusing to continue configuration.");
                return;
            }

            bool useFile = true;
            if (configurationPlugin == null)
            {
                MainLog.Instance.Error("CONFIG", "Configuration Plugin NOT LOADED!");
                return;
            }

            if (configurationFilename.Trim() != "")
            {
                configurationPlugin.SetFileName(configurationFilename);
                try
                {
                    configurationPlugin.LoadData();
                    useFile = true;
                }
                catch (XmlException e)
                {
                    MainLog.Instance.Error("Error loading " + configurationFilename + ": " + e.ToString());
                    useFile = false;
                }
            }
            else
            {
                if (this.configurationFromXMLNode != null)
                {
                    MainLog.Instance.Notice("Loading from XML Node, will not save to the file");
                    configurationPlugin.LoadDataFromString(configurationFromXMLNode.OuterXml);
                }

                MainLog.Instance.Notice("XML Configuration Filename is not valid; will not save to the file.");
                useFile = false;
            }

            foreach (ConfigurationOption configOption in configurationOptions)
            {
                bool convertSuccess = false;
                object return_result = null;
                string errorMessage = "";
                bool ignoreNextFromConfig = false;
                while (convertSuccess == false)
                {
                    string console_result = "";
                    string attribute = null;
                    if (useFile || configurationFromXMLNode != null)
                    {
                        if (!ignoreNextFromConfig)
                        {
                            attribute = configurationPlugin.GetAttribute(configOption.configurationKey);
                        }
                        else
                        {
                            ignoreNextFromConfig = false;
                        }
                    }

                    if (attribute == null)
                    {
                        if (configOption.configurationUseDefaultNoPrompt)
                        {
                            console_result = configOption.configurationDefault;
                        }
                        else
                        {
                            if ((configOption.shouldIBeAsked != null && configOption.shouldIBeAsked(configOption.configurationKey)) || configOption.shouldIBeAsked == null)
                            {
                                if (configurationDescription.Trim() != "")
                                {
                                    console_result = MainLog.Instance.CmdPrompt(configurationDescription + ": " + configOption.configurationQuestion, configOption.configurationDefault);
                                }
                                else
                                {
                                    console_result = MainLog.Instance.CmdPrompt(configOption.configurationQuestion, configOption.configurationDefault);
                                }
                            }
                            else
                            {
                                //Dont Ask! Just use default
                                console_result = configOption.configurationDefault;
                            }
                        }                        
                    }
                    else
                    {
                        console_result = attribute;
                    }

                    switch (configOption.configurationType)
                    {
                        case ConfigurationOption.ConfigurationTypes.TYPE_STRING:
                            return_result = console_result;
                            convertSuccess = true;
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY:
                            if (console_result.Length > 0)
                            {
                                return_result = console_result;
                                convertSuccess = true;
                            }
                            errorMessage = "a string that is not empty";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_BOOLEAN:
                            bool boolResult;
                            if (Boolean.TryParse(console_result, out boolResult))
                            {
                                convertSuccess = true;
                                return_result = boolResult;
                            }
                            errorMessage = "'true' or 'false' (Boolean)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_BYTE:
                            byte byteResult;
                            if (Byte.TryParse(console_result, out byteResult))
                            {
                                convertSuccess = true;
                                return_result = byteResult;
                            }
                            errorMessage = "a byte (Byte)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_CHARACTER:
                            char charResult;
                            if (Char.TryParse(console_result, out charResult))
                            {
                                convertSuccess = true;
                                return_result = charResult;
                            }
                            errorMessage = "a character (Char)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_INT16:
                            short shortResult;
                            if (Int16.TryParse(console_result, out shortResult))
                            {
                                convertSuccess = true;
                                return_result = shortResult;
                            }
                            errorMessage = "a signed 32 bit integer (short)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_INT32:
                            int intResult;
                            if (Int32.TryParse(console_result, out intResult))
                            {
                                convertSuccess = true;
                                return_result = intResult;
                            }
                            errorMessage = "a signed 32 bit integer (int)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_INT64:
                            long longResult;
                            if (Int64.TryParse(console_result, out longResult))
                            {
                                convertSuccess = true;
                                return_result = longResult;
                            }
                            errorMessage = "a signed 32 bit integer (long)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_IP_ADDRESS:
                            IPAddress ipAddressResult;
                            if (IPAddress.TryParse(console_result, out ipAddressResult))
                            {
                                convertSuccess = true;
                                return_result = ipAddressResult;
                            }
                            errorMessage = "an IP Address (IPAddress)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_LLUUID:
                            LLUUID uuidResult;
                            if (LLUUID.TryParse(console_result, out uuidResult))
                            {
                                convertSuccess = true;
                                return_result = uuidResult;
                            }
                            errorMessage = "a UUID (LLUUID)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_LLVECTOR3:
                            LLVector3 vectorResult;
                            if (LLVector3.TryParse(console_result, out vectorResult))
                            {
                                convertSuccess = true;
                                return_result = vectorResult;
                            }
                            errorMessage = "a vector (LLVector3)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_UINT16:
                            ushort ushortResult;
                            if (UInt16.TryParse(console_result, out ushortResult))
                            {
                                convertSuccess = true;
                                return_result = ushortResult;
                            }
                            errorMessage = "an unsigned 16 bit integer (ushort)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_UINT32:
                            uint uintResult;
                            if (UInt32.TryParse(console_result, out uintResult))
                            {
                                convertSuccess = true;
                                return_result = uintResult;
                            }
                            errorMessage = "an unsigned 32 bit integer (uint)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_UINT64:
                            ulong ulongResult;
                            if (UInt64.TryParse(console_result, out ulongResult))
                            {
                                convertSuccess = true;
                                return_result = ulongResult;
                            }
                            errorMessage = "an unsigned 64 bit integer (ulong)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_FLOAT:
                            float floatResult;
                            if (float.TryParse(console_result, NumberStyles.AllowDecimalPoint, Culture.NumberFormatInfo, out floatResult))
                            {
                                convertSuccess = true;
                                return_result = floatResult;
                            }
                            errorMessage = "a single-precision floating point number (float)";
                            break;
                        case ConfigurationOption.ConfigurationTypes.TYPE_DOUBLE:
                            double doubleResult;
                            if (Double.TryParse(console_result, NumberStyles.AllowDecimalPoint, Culture.NumberFormatInfo, out doubleResult))
                            {
                                convertSuccess = true;
                                return_result = doubleResult;
                            }
                            errorMessage = "an double-precision floating point number (double)";
                            break;
                    }

                    if (convertSuccess)
                    {
                        if (useFile)
                        {
                            configurationPlugin.SetAttribute(configOption.configurationKey, console_result);
                        }

                        if (!this.resultFunction(configOption.configurationKey, return_result))
                        {
                            Console.MainLog.Instance.Notice("The handler for the last configuration option denied that input, please try again.");
                            convertSuccess = false;
                            ignoreNextFromConfig = true;
                        }
                    }
                    else
                    {
                        if (configOption.configurationUseDefaultNoPrompt)
                        {
                            MainLog.Instance.Error("CONFIG", string.Format("[{3}]:[{1}] is not valid default for parameter [{0}].\nThe configuration result must be parsable to {2}.\n", configOption.configurationKey, console_result, errorMessage, configurationFilename));
                            convertSuccess = true;
                        }
                        else
                        {
                            MainLog.Instance.Warn("CONFIG", string.Format("[{3}]:[{1}] is not a valid value [{0}].\nThe configuration result must be parsable to {2}.\n", configOption.configurationKey, console_result, errorMessage, configurationFilename));
                            ignoreNextFromConfig = true;
                        }
                    }
                }
            }

            if(useFile)
            {
                configurationPlugin.Commit();
                configurationPlugin.Close();
            }
        }

        private IGenericConfig LoadConfigDll(string dllName)
        {
            Assembly pluginAssembly = Assembly.LoadFrom(dllName);
            IGenericConfig plug = null;

            foreach (Type pluginType in pluginAssembly.GetTypes())
            {
                if (pluginType.IsPublic)
                {
                    if (!pluginType.IsAbstract)
                    {
                        Type typeInterface = pluginType.GetInterface("IGenericConfig", true);

                        if (typeInterface != null)
                        {
                            plug = (IGenericConfig)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));
                        }
                    }
                }
            }

            pluginAssembly = null;
            return plug;
        }

        public void forceSetConfigurationOption(string configuration_key, string configuration_value)
        {
            this.configurationPlugin.LoadData();
            this.configurationPlugin.SetAttribute(configuration_key, configuration_value);
            this.configurationPlugin.Commit();
            this.configurationPlugin.Close();
        }
    }
}