using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
using System.Xml;

namespace UEUserGuide
{
    public partial class UEUserGuideFrm : Form
    {
        ///////////////////////////////////////////////////////////////////////
        #region Delegates

        public delegate void UpdateUICallBack(string trigger, IList<string> paramsList);

        #endregion Delegates

        ///////////////////////////////////////////////////////////////////////
        #region Internal fields

        ///////////////////////////////////////////////////////////////////////
        #region Application settings

        /// <summary>
        /// IP address.
        /// </summary>
        private string _address;

        /// <summary>
        /// IP port.
        /// </summary>
        private int _port = -1;

        /// <summary>
        /// IP address.
        /// </summary>
        private Uri _messagesFilePath;

        #endregion Application settings

        ///////////////////////////////////////////////////////////////////////
        #region Network part

        /// <summary>
        /// Server instance.
        /// </summary>
        private NetworkHelper _server = null;

        /// <summary>
        /// Event set to terminate the background thread.
        /// </summary>
        System.Threading.ManualResetEvent _terminated;

        #endregion Network part

        /// User guides messages values list.
        /// </summary>
        private IDictionary<string, MessageParms> _parms;

        /// <summary>
        /// Event set when user action is achieved.
        /// </summary>
        private System.Threading.ManualResetEvent _userActionCompleted;

        private DialogResult _userResponse;

        #endregion Internal fields

        ///////////////////////////////////////////////////////////////////////
        #region ctors

        /// <summary>
        /// Default ctor.
        /// </summary>
        public UEUserGuideFrm()
        {
            // Initialize logger.
            Logger.Instance.Initialize("UEUserGuide");

            _userActionCompleted = new System.Threading.ManualResetEvent(false);

            InitializeComponent();
        }

        #endregion ctors

        private void UEUserGuideFrm_Load(object sender, EventArgs e)
        {
            // Set UI state.
            ResetUI();

            // Load application settings.
            try
            {
                Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");
                _address = appSettings.Settings["IpAddress"].Value;
                _port = int.Parse(appSettings.Settings["IpPort"].Value);
                _messagesFilePath = new Uri(appSettings.Settings["MessagesFile"].Value);
                Text = appSettings.Settings["Title"].Value;
                // Sanity check.
                if (!System.IO.File.Exists(_messagesFilePath.LocalPath))
                {
                    throw new System.IO.FileNotFoundException(_messagesFilePath.LocalPath);
                }
            }
            catch (Exception x)
            {
                MessageBox.Show(
                    string.Format("Failed to load application settings.\r\n{0}", x), 
                    this.Text, 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                Close();
                return;
            }

            // Load message database.
            ReloadParameters();
            if ((_parms == null) || (_parms.Count == 0))
            {
                MessageBox.Show("Invalid content of 'UserGuidesMessages.xml' file", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                Close();
                return;
            }

            // Start TCP/IP listener.
            try
            {
                _server = new NetworkHelper();
                if (!_server.Initialize(_address, _port))
                {
                    MessageBox.Show("Failed to initialize networl communications", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    Close();
                    return;
                }
                _serverParms.Text = string.Format("Listen on {0}:{1}", _address, _port);
                _server.Start();
            }
            catch (Exception x)
            {
                MessageBox.Show(
                    string.Format("Failed to start listener, please check IP address/port number information in application cconfiguration file.\r\n{0}", x), 
                    this.Text, 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                Close();
                return;
            }

            _terminated = new System.Threading.ManualResetEvent(false);
            _processNetworkClient.RunWorkerAsync();

            _btReload.Visible = true;

            _messagesFile.Text = _messagesFilePath.LocalPath;
        }

        private void _btReload_Click(object sender, EventArgs e)
        {
            ReloadParameters();
            ResetUI();
        }

        private void _btOK_Click(object sender, EventArgs e)
        {
            _btOK.Enabled = false;
            _btFailed.Enabled = false;
            _userResponse = DialogResult.OK;
            _userActionCompleted.Set();
        }

        private void _btYes_Click(object sender, EventArgs e)
        {
            _btYes.Enabled = false;
            _btNo.Enabled = false;
            _userResponse = DialogResult.Yes;
            _userActionCompleted.Set();
        }

        private void _btNo_Click(object sender, EventArgs e)
        {
            _btYes.Enabled = false;
            _btNo.Enabled = false;
            _userResponse = DialogResult.No;
            _userActionCompleted.Set();
        }

        private void _btFailed_Click(object sender, EventArgs e)
        {
            _btOK.Enabled = false;
            _btFailed.Enabled = false;
            _userResponse = DialogResult.Cancel;
            _userActionCompleted.Set();
        }

        private void _btResetUI_Click(object sender, EventArgs e)
        {
            ResetUI();
        }

        private void _btQuit_Click(object sender, EventArgs e)
        {
            _userActionCompleted.Set();

            // Stop TCP/IP listener.
            if (_server != null)
            {
                _server.Stop();
                _server = null;
            }

            // Terminate application.
            Close();
        }


        /// <summary>
        /// Method called to relaod User guides messages.
        /// </summary>
        public void ReloadParameters()
        {
            XmlDocument parmsDoc = new XmlDocument();
            parmsDoc.Load(_messagesFilePath.LocalPath);
            // Get the strategy to use.
            XmlNodeList nodes = parmsDoc.SelectNodes("descendant::UserGuideMessages/Strategies").Item(0).ChildNodes;
            string strategy = null;
            foreach (XmlNode node in nodes)
            {
                if ((node.Attributes != null) && (node.Attributes.Count != 0) && !string.IsNullOrEmpty(node.Attributes[0].Value))
                {
                    strategy = node.Attributes[0].Value;
                    break;
                }
            }
            if (!string.IsNullOrEmpty(strategy))
            {
                // Apply the strategy.
                nodes = parmsDoc.SelectNodes(string.Format("descendant::UserGuideMessages/{0}", strategy)).Item(0).ChildNodes;
                if ((nodes == null) || (nodes.Count == 0))
                {
                    // The response is empty.
                    _parms = null;
                }
                else
                {
                    _parms = new Dictionary<string, MessageParms>();
                    foreach (XmlNode item in nodes)
                    {
                        if ((item.NodeType == XmlNodeType.Comment) || (item.Attributes == null) || (item.Attributes.Count != 2))
                        {
                            // TODO: Add logs.
                            continue;
                        }
                        _parms.Add(item.Name, new MessageParms(item.Attributes.GetNamedItem("Text").Value.Replace("\\n", System.Environment.NewLine).Replace("\\t", "    "), item.Attributes.GetNamedItem("Actions").Value));
                    }
                    if (_parms.Count == 0)
                    {
                        _parms = null;
                    }
                }
            }
            else
            {
                _parms = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _processNetworkClient_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                // Enter the listening loop.
                while (!_terminated.WaitOne(5, false))
                {
                    if (Logger.Instance.IsDebugLevelSet) Logger.Instance.DebugLogger("_processNetworkClient_DoWork: Waiting for a connection...");
                    // Perform a blocking call to accept requests.
                    NetworkHelper client = _server.AcceptClient();
                    System.Threading.ThreadPool.QueueUserWorkItem(ProcessNewConnection, new ProcessNewConnectionArgs(client));
                }
            }
            catch (System.Net.Sockets.SocketException s)
            {
                Logger.Instance.ErrorLogger("_processNetworkClient_DoWork: {0}", s);
            }

            e.Result = (object)true;
            if (Logger.Instance.IsInfoLevelSet) Logger.Instance.InfoLogger("_processNetworkClient_DoWork: Terminated.");
        }

        /// <summary>
        /// Process the new accepted connection.
        /// </summary>
        /// <param name="parms">Connection instance.</param>
        private void ProcessNewConnection(object parms)
        {
            // Sanity check.
            if (!(parms is ProcessNewConnectionArgs))
            {
                Logger.Instance.ErrorLogger("ProcessNewConnection: Wrong parameters.");
                return;
            }
            ProcessNewConnectionArgs args = parms as ProcessNewConnectionArgs;

            if (Logger.Instance.IsDebugLevelSet) Logger.Instance.DebugLogger("ProcessNewConnection: Connected.");

            try
            {
                // Buffer for reading data.
                string readBytes;
                // Read data.
                int length = args.Client.Receive(out readBytes);
                byte[] buffer = System.Text.Encoding.ASCII.GetBytes(readBytes.ToCharArray(), 0, length);
                if (length == -1)
                {
                    // Exit loop.
                    Logger.Instance.ErrorLogger("ProcessNewConnection: Receive datas failed.");
                }
                else if (length != 0)
                {
                    if (Logger.Instance.IsDebugLevelSet) Logger.Instance.DebugLogger("ProcessNewConnection: Receive datas ({0}).", length);
                    // Convert byte into string.
                    int index = 0;
                    // Get ID
                    int id = BitConverter.ToInt16(buffer, index);
                    index += sizeof(short);
                    // Decode full length.
                    int totalLength = BitConverter.ToInt16(buffer, index);
                    index += sizeof(int);
                    // Decode the trigger command.
                    length = BitConverter.ToInt32(buffer, index); // Decode length;
                    index += sizeof(int);
                    string trigger = Encoding.ASCII.GetString(buffer, index, length); // Decode datas.
                    index += trigger.Length;
                    // Decode the parameters.
                    int paramsNum = BitConverter.ToInt32(buffer, index); // Decode length;
                    index += sizeof(int);
                    // Read the parameters.
                    IList<string> parmsList = new List<string>();
                    for (int i = 0; i < paramsNum; i++)
                    {
                        length = BitConverter.ToInt32(buffer, index); // Decode length;
                        index += sizeof(int);
                        string str = Encoding.ASCII.GetString(buffer, index, length); // Decode datas.
                        index += str.Length;
                        parmsList.Add(str);
                    }
                    if (!string.IsNullOrEmpty(trigger))
                    {
                        // Update UI in multi-threaded mode.
                        IAsyncResult result = this.BeginInvoke(new UpdateUICallBack(UpdateUI), trigger, parmsList);
                        // Wait for the WaitHandle to become signaled.
                        result.AsyncWaitHandle.WaitOne();
                        // Wait for user action done.
                        _userActionCompleted.WaitOne(30000);
                    }
                    else
                    {
                        _userResponse = DialogResult.Cancel;
                    }
                    // Send response message
                    byte[] response = new byte[1] { (byte)_userResponse };
                    args.Client.Send(BitConverter.ToString(response));
                    // Wait for disconnection
                    args.Client.Receive(out buffer);
                }
            }
            catch (Exception x)
            {
                Logger.Instance.ErrorLogger("ProcessNewConnection: {0}", x);
            }
            finally
            {
                // Shutdown and end connection.
                if (Logger.Instance.IsDebugLevelSet) Logger.Instance.DebugLogger("ProcessNewConnection: Close connection.");
                args.Client.Close();
            }
        }

        private void UpdateUI(string trigger, IList<string> paramsList)
        {
            _message.Focus();
            _userActionCompleted.Reset();
            string action = "OkCancel";
            if (_parms.ContainsKey(trigger))
            {
                string message = _parms[trigger].Text;
                if (!((paramsList.Count == 0) || ((paramsList.Count == 1) && (paramsList[0] == "*"))))
                {
                    message += "\n\tParameters:\n";
                    foreach (string item in paramsList)
                    {
                        message += string.Format("\t\t{0}\n", item);
                    }
                }
                _message.Text = message;
                action = _parms[trigger].Action;
            }
            else
            {
                _message.Text = trigger;
            }
            switch (action)
            {
                case "OkCancel":
                    _btOK.Enabled = true;
                    _btOK.Visible = true;
                    _btFailed.Visible = true;
                    _btFailed.Enabled = true;
                    _btYes.Visible = false;
                    _btNo.Visible = false;
                    break;
                case "YesNo":
                    _btOK.Visible = false;
                    _btFailed.Visible = false;
                    _btYes.Visible = true;
                    _btYes.Enabled = true;
                    _btNo.Visible = true;
                    _btNo.Enabled = true;
                    break;
                default:
                    ResetUI();
                    break;
            }
        }

        private void ResetUI()
        {
            _message.Text = "";
            _message.Lines = null;
            _btOK.Visible = false;
            _btFailed.Visible = false;
            _btYes.Visible = false;
            _btNo.Visible = false;
            _btOK.Enabled = true;
            _btFailed.Enabled = true;
            _btYes.Enabled = true;
            _btNo.Enabled = true;
        }
    }

    ///////////////////////////////////////////////////////////////////////
    #region Class helpers part

    /// <summary>
    /// This class describes the user guides messages associated to each trigger.
    /// </summary>
    class MessageParms
    {
        ///////////////////////////////////////////////////////////////////////
        #region Internal part

        /// <summary>
        /// Parameter type.
        /// </summary>
        private string _text;

        /// <summary>
        /// Parameter value.
        /// </summary>
        private string _action;

        #endregion Internal part

        ///////////////////////////////////////////////////////////////////////
        #region Accessors part

        /// <summary>
        /// Gets the parameter type.
        /// </summary>
        public string Text { get { return _text; } }

        /// <summary>
        /// Gets the parameter value.
        /// </summary>
        public string Action { get { return _action; } }

        #endregion Accessors part

        ///////////////////////////////////////////////////////////////////////
        #region Ctors part

        /// <summary>
        /// Default ctor.
        /// </summary>
        public MessageParms()
        {
            _text = null;
            _action = null;
        }

        /// <summary>
        /// Builder ctor.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="action"></param>
        public MessageParms(string text, string action)
        {
            _text = text;
            _action = action;
        }

        #endregion Ctors part
    }

    /// <summary>
    /// Internal class helper.
    /// </summary>
    class ProcessNewConnectionArgs
    {
        /// <summary>
        /// New accepted connection reference.
        /// </summary>
        private NetworkHelper _client;
        /// <summary>
        /// Gets the new accepted connection reference.
        /// </summary>
        public NetworkHelper Client { get { return _client; } }

        /// <summary>
        /// Builder ctor.
        /// </summary>
        /// <param name="client">New accepted connection reference.</param>
        public ProcessNewConnectionArgs(NetworkHelper client)
        {
            _client = client;
        }
    }

    #endregion Class helpers part

}
