/
etc/
lib/
src/Abilities/
src/Abilities/Skills/
src/Abilities/Spells/
src/Abilities/Spells/Enums/
src/Affects/
src/ArtheaConsole/
src/ArtheaConsole/Properties/
src/ArtheaGUI/Properties/
src/Clans/Enums/
src/Commands/Communication/
src/Commands/ItemCommands/
src/Connections/
src/Connections/Colors/
src/Connections/Enums/
src/Connections/Players/
src/Connections/Players/Enums/
src/Continents/
src/Continents/Areas/
src/Continents/Areas/Characters/
src/Continents/Areas/Characters/Enums/
src/Continents/Areas/Items/
src/Continents/Areas/Items/Enums/
src/Continents/Areas/Rooms/
src/Continents/Areas/Rooms/Enums/
src/Continents/Areas/Rooms/Exits/
src/Creation/
src/Creation/Attributes/
src/Creation/Interfaces/
src/Database/
src/Database/Interfaces/
src/Environment/
src/Properties/
src/Scripts/Enums/
src/Scripts/Interfaces/
#region Arthea License

/***********************************************************************
*  Arthea MUD by R. Jennings (2007)      http://arthea.googlecode.com/ *
*  By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Channels;
#if !MONO
using System.Runtime.Remoting.Channels.Tcp;
#endif
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Serialization;
using Arthea.Commands.Admin;
using Arthea.Connections;
using Arthea.Environment;
using Arthea.Updates;

namespace Arthea
{
    /// <summary>
    /// The starting point
    /// </summary>
    public class Server
    {
        #region [rgn] Fields (10)

        private const int BacklogSize = 20;

        private static readonly Server instance =
            (!Persistance.XmlFileExists(Paths.ServerConfigFile))
                ? new Server()
                : Persistance.Load<Server>(Paths.ServerConfigFile);

        private readonly string about;
        private readonly string version;
        private bool databaseLog;

        private string databaseName;
        private string databasePassword;
        private string databaseServer;
        private string databaseType;
        private string databaseUser;
        private bool databaseEnabled = true;
        private ServerState serverState;
        private Socket socket;

        #endregion [rgn]

        #region [rgn] Constructors (1)

        private Server()
        {
            version = string.Format("Arthea v{0}.{1}", System.Environment.Version.Major,
                                    System.Environment.Version.Minor);

            StringBuilder buf = new StringBuilder();
            buf.AppendLine("by R. Jennings 2007");
            buf.AppendLine("See http://arthea.googlecode.com/ for more information.");

            about = buf.ToString();
        }

        #endregion [rgn]

        #region [rgn] Properties (8)

        /// <summary>
        /// Gets information about the server.
        /// </summary>
        public string About
        {
            get { return about; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [database enabled].
        /// </summary>
        /// <value><c>true</c> if [database enabled]; otherwise, <c>false</c>.</value>
        [XmlIgnore]
        public bool DatabaseEnabled
        {
            get { return databaseEnabled; }
            set { databaseEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the name of the database.
        /// </summary>
        /// <value>The name of the database.</value>
        public string DatabaseName
        {
            get { return databaseName; }
            set { databaseName = value; }
        }

        /// <summary>
        /// Gets or sets the database password.
        /// </summary>
        /// <value>The database password.</value>
        public string DatabasePassword
        {
            get { return databasePassword; }
            set { databasePassword = value; }
        }

        /// <summary>
        /// Gets or sets the database server.
        /// </summary>
        /// <value>The database server.</value>
        public string DatabaseServer
        {
            get { return databaseServer; }
            set { databaseServer = value; }
        }

        /// <summary>
        /// Gets or sets the type of the database.
        /// </summary>
        /// <value>The type of the database.</value>
        public string DatabaseType
        {
            get { return databaseType; }
            set { databaseType = value; }
        }

        /// <summary>
        /// Gets or sets the database user.
        /// </summary>
        /// <value>The database user.</value>
        public string DatabaseUser
        {
            get { return databaseUser; }
            set { databaseUser = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [database log].
        /// </summary>
        /// <value><c>true</c> if [database log]; otherwise, <c>false</c>.</value>
        public bool DatabaseLog
        {
            get { return databaseLog; }
            set { databaseLog = value; }
        }

        /// <summary>
        /// Gets or sets the run level.
        /// </summary>
        /// <value>The run level.</value>
        [XmlIgnore]
        public ServerState State
        {
            get { return serverState; }
            set
            {
                serverState = value;
                switch (value)
                {
                    case ServerState.Running:
                        Log.Info("Arthea running on port {0}.", Globals.PortNumber);
                        break;
                    case ServerState.Stopping:
                        Log.Info("Arthea is shutting down...");
                        break;
                    default:
                        Log.Info("Arthea is {0}...", value.ToString().ToLower());
                        break;
                }
            }
        }

        /// <summary>
        /// Gets the version of the server.
        /// </summary>
        public string Version
        {
            get { return version; }
        }

        /// <summary>
        /// Gets the instance.
        /// </summary>
        /// <value>The instance.</value>
        public static Server Instance
        {
            get { return instance; }
        }

        #endregion [rgn]

        #region [rgn] Methods (2)

        // [rgn] Public Methods (2)

        /// <summary>
        /// Shutdowns this instance.
        /// </summary>
        public void Shutdown()
        {
            if (socket != null)
            {
                socket.Close();

                socket = null;
            }
        }

        /// <summary>
        /// Starts the server.
        /// </summary>
        /// <param name="rebooting">if set to <c>true</c> [rebooting].</param>
        public void Start(bool rebooting)
        {
#if !MONO
            Dictionary<string, SocketInformation> recoveredConnections = null;

            if (rebooting)
            {
                TcpChannel channel = new TcpChannel();

                ChannelServices.RegisterChannel(channel, false);
                RebootRecovery recovery = (RebootRecovery)
                                          Activator.GetObject(typeof (RebootRecovery),
                                                              string.Format("tcp://localhost:{0}/RebootRecovery",
                                                                            RebootRecovery.Port));

                if (recovery == null)
                {
                    Log.Fatal("Reboot recovery failed.");
                    return;
                }

                serverState = ServerState.Rebooting;

                try
                {
                    recoveredConnections =
                        recovery.TransferConnections(Process.GetCurrentProcess().Id);

                    ChannelServices.UnregisterChannel(channel);

                    // signal we are done to close the previous process
                    recovery.Finished();

                    // wait a bit for sanity
                    Thread.Sleep(1000);
                }
                catch (Exception ex)
                {
                    Log.Bug(ex.Message);
                    Log.Error(ex.StackTrace);

                    Thread.Sleep(5000);
                    return;
                }
            }
#endif

            socket = new Socket(AddressFamily.InterNetwork,
                                SocketType.Stream, ProtocolType.Tcp);
            try
            {
                socket.Bind(new IPEndPoint(IPAddress.Any, Globals.PortNumber));
            }
            catch (Exception)
            {
                Log.Fatal("Port {0} is in use.", Globals.PortNumber);
                Thread.Sleep(5000);
                System.Environment.Exit(1);
            }

            socket.Listen(BacklogSize);

            State = ServerState.Booting;

            Lists.Continents.Load();

#if !MONO
            if (recoveredConnections != null)
            {
                Log.Info("Recovering {0} player{1}...", recoveredConnections.Count,
                         recoveredConnections.Count == 1 ? "" : "s");

                foreach (KeyValuePair<string, SocketInformation> entry in recoveredConnections)
                {
                    try
                    {
                        Connection connection = new Connection(entry.Key, entry.Value);

                        connection.Attach();
                    }
                    catch
                    {
                        Log.Error("Failed to load {0}.", entry.Key);
                    }
                }
            }
#endif

            Lists.Areas.Update();

            State = ServerState.Running;

            try
            {
                while (State == ServerState.Running)
                {
                    if (socket.Poll(1, SelectMode.SelectRead))
                    {
                        new Connection(socket.Accept());
                    }

                    Lists.Connections.ReadInput();

                    //Update world
                    UpdateManager.Update();

                    Lists.Connections.Update();

                    Lists.Connections.ProcessOutput();

                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException ex)
            {
                Log.Info(ex.ExceptionState);
            }
            finally
            {
                Cleanup();
                Shutdown();
            }
        }

        // [rgn] Private Methods (1)

        /// <summary>
        /// Cleanups this instance.
        /// </summary>
        private void Cleanup()
        {
            Globals.Save();

            Persistance.Save(Paths.ServerConfigFile, this);
        }

        #endregion [rgn]
    }

    /// <summary>
    /// Arguments class
    /// </summary>
    public class Arguments
    {
        #region [rgn] Fields (1)

        // Variables
        private readonly StringDictionary Parameters;

        #endregion [rgn]

        #region [rgn] Constructors (1)

        /// <summary>
        /// Initializes a new instance of the <see cref="Arguments"/> class.
        /// </summary>
        /// <param name="Args">The args.</param>
        public Arguments(IEnumerable<string> Args)
        {
            Parameters = new StringDictionary();
            Regex Spliter = new Regex(@"^-{1,2}|^/|=|:",
                                      RegexOptions.IgnoreCase | RegexOptions.Compiled);

            Regex Remover = new Regex(@"^['""]?(.*?)['""]?$",
                                      RegexOptions.IgnoreCase | RegexOptions.Compiled);

            string Parameter = null;
            string[] Parts;

            // Valid parameters forms:
            // {-,/,--}param{ ,=,:}((",')value(",'))
            // Examples:
            // -param1 value1 --param2 /param3:"Test-:-work"
            //   /param4=happy -param5 '--=nice=--'
            foreach (string Txt in Args)
            {
                // Look for new parameters (-,/ or --) and a
                // possible enclosed value (=,:)
                Parts = Spliter.Split(Txt, 3);

                switch (Parts.Length)
                {
                        // Found a value (for the last parameter
                        // found (space separator))
                    case 1:
                        if (Parameter != null)
                        {
                            if (!Parameters.ContainsKey(Parameter))
                            {
                                Parts[0] =
                                    Remover.Replace(Parts[0], "$1");

                                Parameters.Add(Parameter, Parts[0]);
                            }
                            Parameter = null;
                        }
                        // else Error: no parameter waiting for a value (skipped)
                        break;

                        // Found just a parameter
                    case 2:
                        // The last parameter is still waiting.
                        // With no value, set it to true.
                        if (Parameter != null)
                        {
                            if (!Parameters.ContainsKey(Parameter))
                                Parameters.Add(Parameter, "true");
                        }
                        Parameter = Parts[1];
                        break;

                        // Parameter with enclosed value
                    case 3:
                        // The last parameter is still waiting.
                        // With no value, set it to true.
                        if (Parameter != null)
                        {
                            if (!Parameters.ContainsKey(Parameter))
                                Parameters.Add(Parameter, "true");
                        }

                        Parameter = Parts[1];

                        // Remove possible enclosing characters (",')
                        if (!Parameters.ContainsKey(Parameter))
                        {
                            Parts[2] = Remover.Replace(Parts[2], "$1");
                            Parameters.Add(Parameter, Parts[2]);
                        }

                        Parameter = null;
                        break;
                }
            }
            // In case a parameter is still waiting
            if (Parameter != null)
            {
                if (!Parameters.ContainsKey(Parameter))
                    Parameters.Add(Parameter, "true");
            }
        }

        #endregion [rgn]

        #region [rgn] Properties (1)

        /// <summary>
        /// Gets the <see cref="System.String"/> with the specified param.
        /// </summary>
        /// <value>the parameter value</value>
        public string this[string Param]
        {
            get { return (Parameters[Param]); }
        }

        #endregion [rgn]
    }
}