WingMan – Rev 9

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Server;
using WingMan.Communication;
using WingMan.Lobby;
using WingMan.MouseKey;
using WingMan.Properties;
using WingMan.Utilities;

namespace WingMan
{
    public partial class WingManForm : Form
    {
        public WingManForm()
        {
            InitializeComponent();

            FormTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            FormCancellationTokenSource = new CancellationTokenSource();

            MqttCommunication = new MqttCommunication(FormTaskScheduler, FormCancellationTokenSource.Token);
            MqttCommunication.OnClientAuthenticationFailed += OnMqttClientAuthenticationFailed;
            MqttCommunication.OnClientConnectionFailed += OnMqttClientConnectionFailed;
            MqttCommunication.OnClientDisconnected += OnMqttClientDisconnected;
            MqttCommunication.OnClientConnected += OnMqttClientConnected;
            MqttCommunication.OnServerAuthenticationFailed += OnMqttServerAuthenticationFailed;
            MqttCommunication.OnServerStopped += OnMqttServerStopped;
            MqttCommunication.OnServerStarted += OnMqttServerStarted;
            MqttCommunication.OnServerClientConnected += OnMqttServerClientConnected;
            MqttCommunication.OnServerClientDisconnected += OnMqttServerClientDisconnected;

            LocalMouseKeyBindings = new MouseKeyBindings(new List<MouseKeyBinding>());
            RemoteMouseKeyBindings = new RemoteMouseKeyBindings(new List<RemoteMouseKeyBinding>());

            LocalListBoxBindingSource = new BindingSource
            {
                DataSource = LocalMouseKeyBindings.Bindings
            };
            LocalBindingsListBox.DisplayMember = "DisplayName";
            LocalBindingsListBox.ValueMember = "Keys";
            LocalBindingsListBox.DataSource = LocalListBoxBindingSource;

            MouseKeyBindingsExchange = new MouseKeyBindingsExchange
            {
                ExchangeBindings = new List<MouseKeyBindingExchange>()
            };

            RemoteBindingsComboBoxSource = new BindingSource
            {
                DataSource = MouseKeyBindingsExchange.ExchangeBindings
            };
            RemoteBindingsComboBox.DisplayMember = "Nick";
            RemoteBindingsComboBox.ValueMember = "MouseKeyBindings";
            RemoteBindingsComboBox.DataSource = RemoteBindingsComboBoxSource;

            // Start lobby message synchronizer.
            LobbyMessageSynchronizer = new LobbyMessageSynchronizer(MqttCommunication, FormTaskScheduler,
                FormCancellationTokenSource.Token);
            LobbyMessageSynchronizer.OnLobbyMessageReceived += OnLobbyMessageReceived;

            // Start mouse key bindings synchronizer.
            MouseKeyBindingsSynchronizer = new MouseKeyBindingsSynchronizer(LocalMouseKeyBindings, MqttCommunication,
                FormTaskScheduler, FormCancellationTokenSource.Token);
            MouseKeyBindingsSynchronizer.OnMouseKeyBindingsSynchronized += OnMouseKeyBindingsSynchronized;

            // Start key binding simulator.
        }

        private static CancellationTokenSource FormCancellationTokenSource { get; set; }

        private static TaskScheduler FormTaskScheduler { get; set; }

        private static IKeyboardMouseEvents MouseKeyApplicationHook { get; set; }

        private List<string> MouseKeyCombo { get; set; }

        private MouseKeyBindings LocalMouseKeyBindings { get; }

        private RemoteMouseKeyBindings RemoteMouseKeyBindings { get; }

        private BindingSource LocalListBoxBindingSource { get; }

        private BindingSource RemoteBindingsComboBoxSource { get; }

        private MouseKeyBindingsExchange MouseKeyBindingsExchange { get; }

        public MqttCommunication MqttCommunication { get; set; }

        public LobbyMessageSynchronizer LobbyMessageSynchronizer { get; set; }

        public MouseKeyBindingsSynchronizer MouseKeyBindingsSynchronizer { get; set; }

        private async Task SaveLocalMouseKeyBindings()
        {
            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    MouseKeyBindings.XmlSerializer.Serialize(memoryStream, LocalMouseKeyBindings);

                    memoryStream.Position = 0L;

                    using (var fileStream = new FileStream("LocalMouseKeyBindings.xml", FileMode.Create))
                    {
                        await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
                    }
                }
            }
            catch (Exception)
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_saving_local_bindings}{Environment.NewLine}");
            }
        }

        private void OnMqttServerClientDisconnected(object sender, MqttClientDisconnectedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Client_disconnected}{Environment.NewLine}");
        }

        private void OnMqttServerClientConnected(object sender, MqttClientConnectedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Client_connected}{Environment.NewLine}");
        }

        private void OnMqttServerStarted(object sender, EventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Server_started}{Environment.NewLine}");
        }

        private void OnMqttServerStopped(object sender, EventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Server_stopped}{Environment.NewLine}");
        }

        private void OnMqttClientConnected(object sender, MQTTnet.Client.MqttClientConnectedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Client_connected}{Environment.NewLine}");
        }

        private void OnMqttClientDisconnected(object sender, MQTTnet.Client.MqttClientDisconnectedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Client_disconnected}{Environment.NewLine}");
        }

        private void OnMqttClientConnectionFailed(object sender, MqttManagedProcessFailedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Client_connection_failed}{Environment.NewLine}");
        }

        private void OnMqttServerAuthenticationFailed(object sender, EventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Failed_to_authenticate_client}{Environment.NewLine}");
        }

        private void OnMqttClientAuthenticationFailed(object sender, EventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Server_authentication_failed}{Environment.NewLine}");
        }

        /// <summary>
        ///     Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && components != null)
            {
                FormCancellationTokenSource.Dispose();
                FormCancellationTokenSource = null;

                components.Dispose();
            }

            base.Dispose(disposing);
        }

        private void OnMouseKeyBindingsSynchronized(object sender, MouseKeyBindingsSynchronizedEventArgs e)
        {
            ActivityTextBox.AppendText(
                $"{Strings.Synchronized_bindings_with_client} : {e.Bindings.Nick} : {e.Bindings.MouseKeyBindings.Count}{Environment.NewLine}");

            var exchangeBindings = MouseKeyBindingsExchange.ExchangeBindings.FirstOrDefault(exchangeBinding =>
                string.Equals(exchangeBinding.Nick, e.Bindings.Nick, StringComparison.Ordinal));

            // If the nick does not exist then add it.
            if (exchangeBindings == null)
            {
                MouseKeyBindingsExchange.ExchangeBindings.Add(e.Bindings);
                RemoteBindingsComboBoxSource.ResetBindings(false);
                UpdateRemoteListBoxItems();
                return;
            }

            // If the bindings for the nick have not changed then do not update.
            if (exchangeBindings.MouseKeyBindings.SequenceEqual(e.Bindings.MouseKeyBindings))
            {
                RemoteBindingsComboBoxSource.ResetBindings(false);
                UpdateRemoteListBoxItems();
                return;
            }

            // Update the bindings.
            exchangeBindings.MouseKeyBindings = e.Bindings.MouseKeyBindings;
            RemoteBindingsComboBoxSource.ResetBindings(false);
            UpdateRemoteListBoxItems();
        }

        private void UpdateRemoteListBoxItems()
        {
            var exchangeBinding = (List<MouseKeyBinding>) RemoteBindingsComboBox.SelectedValue;
            if (exchangeBinding == null)
                return;

            RemoteBindingsListBox.Items.Clear();
            RemoteBindingsListBox.DisplayMember = "Name";
            RemoteBindingsListBox.ValueMember = "Name";
            var i = exchangeBinding.Select(binding => (object) binding.Name).ToArray();
            if (i.Length == 0)
                return;

            RemoteBindingsListBox.Items.AddRange(i);
        }

        private void OnLobbyMessageReceived(object sender, LobbyMessageReceivedEventArgs e)
        {
            LobbyTextBox.AppendText($"{e.Nick} : {e.Message}{Environment.NewLine}");
        }

        private void AddressTextBoxClick(object sender, EventArgs e)
        {
            Address.BackColor = Color.Empty;
        }

        private void PortTextBoxClick(object sender, EventArgs e)
        {
            Port.BackColor = Color.Empty;
        }

        private async void HostButtonClickAsync(object sender, EventArgs e)
        {
            // Stop the MQTT server if it is running.
            if (MqttCommunication.Running)
            {
                await MqttCommunication.Stop().ConfigureAwait(false);
                HostButton.BackColor = Color.Empty;

                // Enable controls.
                ConnectButton.Enabled = true;
                Address.Enabled = true;
                Port.Enabled = true;
                Nick.Enabled = true;

                return;
            }

            if (!ValidateAddressPort(out var ipAddress, out var port, out var nick, out var password))
                return;

            // Start the MQTT server.
            if (!await MqttCommunication.Start(MqttCommunicationType.Server, ipAddress, port, nick, password)
                .ConfigureAwait(false))
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_starting_server}{Environment.NewLine}");
                return;
            }

            HostButton.BackColor = Color.Aquamarine;

            // Disable controls
            ConnectButton.Enabled = false;
            Address.Enabled = false;
            Port.Enabled = false;
            Nick.Enabled = false;
        }

        private bool ValidateAddressPort(out IPAddress address, out int port, out string nick, out string password)
        {
            address = IPAddress.Any;
            port = 0;
            nick = string.Empty;
            password = string.Empty;

            if (string.IsNullOrEmpty(Address.Text) &&
                string.IsNullOrEmpty(Port.Text) &&
                string.IsNullOrEmpty(Nick.Text) &&
                string.IsNullOrEmpty(Password.Text))
            {
                Address.BackColor = Color.LightPink;
                Port.BackColor = Color.LightPink;
                Nick.BackColor = Color.LightPink;
                Password.BackColor = Color.LightPink;
                return false;
            }

            if (!IPAddress.TryParse(Address.Text, out address))
            {
                Address.BackColor = Color.LightPink;
                return false;
            }

            if (!uint.TryParse(Port.Text, out var uPort))
            {
                Port.BackColor = Color.LightPink;
                return false;
            }

            port = (int) uPort;

            if (string.IsNullOrEmpty(Nick.Text))
            {
                Nick.BackColor = Color.LightPink;
                return false;
            }

            nick = Nick.Text;

            if (string.IsNullOrEmpty(Password.Text))
            {
                Password.BackColor = Color.LightPink;
                return false;
            }

            password = AES.LinearFeedbackShiftPassword(Password.Text);

            Address.BackColor = Color.Empty;
            Port.BackColor = Color.Empty;
            Nick.BackColor = Color.Empty;
            Password.BackColor = Color.Empty;

            return true;
        }

        private async void ConnectButtonClickAsync(object sender, EventArgs e)
        {
            if (MqttCommunication.Running)
            {
                await MqttCommunication.Stop().ConfigureAwait(false);
                ConnectButton.Text = Strings.Connect;
                ConnectButton.BackColor = Color.Empty;

                Address.Enabled = true;
                Port.Enabled = true;
                Nick.Enabled = true;
                HostButton.Enabled = true;
                return;
            }

            if (!ValidateAddressPort(out var ipAddress, out var port, out var nick, out var password))
                return;

            if (!await MqttCommunication.Start(MqttCommunicationType.Client, ipAddress, port, nick, password)
                .ConfigureAwait(false))
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_starting_client}{Environment.NewLine}");
                return;
            }

            ConnectButton.Text = Strings.Disconnect;
            ConnectButton.BackColor = Color.Aquamarine;

            HostButton.Enabled = false;
            Address.Enabled = false;
            Port.Enabled = false;
            Nick.Enabled = false;
        }

        private async void LobbySayTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Enter)
                return;

            await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text).ConfigureAwait(false);

            LobbySayTextBox.Text = string.Empty;
        }

        private void HelmAddButtonClick(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(LocalNameTextBox.Text))
            {
                LocalNameTextBox.BackColor = Color.LightPink;
                return;
            }

            ShowOverlayPanel();

            MouseKeyCombo = new List<string>();

            MouseKeyApplicationHook = Hook.AppEvents();
            MouseKeyApplicationHook.MouseDown += LocalMouseKeyHookOnMouseDown;
            MouseKeyApplicationHook.KeyUp += LocalMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown += LocalMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.MouseUp += LocalMouseKeyHookOnMouseUp;
        }

        private void ShowOverlayPanel()
        {
            OverlayPanel.BringToFront();
            OverlayPanel.Visible = true;
            OverlayPanel.Invalidate();
        }

        private async void LocalMouseKeyHookOnKeyUp(object sender, KeyEventArgs e)
        {
            LocalMouseKeyBindings.Bindings.Add(new MouseKeyBinding(LocalNameTextBox.Text, MouseKeyCombo));

            LocalListBoxBindingSource.ResetBindings(false);

            MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp;

            MouseKeyApplicationHook.Dispose();

            LocalNameTextBox.Text = string.Empty;
            HideOverlayPanel();

            await SaveLocalMouseKeyBindings().ConfigureAwait(false);
        }

        private void HideOverlayPanel()
        {
            OverlayPanel.SendToBack();
            OverlayPanel.Visible = false;
            OverlayPanel.Invalidate();
        }

        private async void LocalMouseKeyHookOnMouseUp(object sender, MouseEventArgs e)
        {
            LocalMouseKeyBindings.Bindings.Add(new MouseKeyBinding(LocalNameTextBox.Text, MouseKeyCombo));

            LocalListBoxBindingSource.ResetBindings(false);

            MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp;

            MouseKeyApplicationHook.Dispose();

            LocalNameTextBox.Text = string.Empty;
            HideOverlayPanel();

            await SaveLocalMouseKeyBindings().ConfigureAwait(false);
        }


        private void LocalMouseKeyHookOnMouseDown(object sender, MouseEventArgs e)
        {
            MouseKeyCombo.Add(e.Button.ToDisplayName());
        }

        private void LocalMouseKeyHookOnKeyDown(object sender, KeyEventArgs e)
        {
            e.SuppressKeyPress = true;

            MouseKeyCombo.Add(e.KeyCode.ToDisplayName());
        }

        private void HelmNameTextBoxClick(object sender, EventArgs e)
        {
            LocalNameTextBox.BackColor = Color.Empty;
        }

        private async void HelmRemoveButtonClick(object sender, EventArgs e)
        {
            var helmBinding = (MouseKeyBinding) LocalBindingsListBox.SelectedItem;
            if (helmBinding == null)
                return;

            LocalMouseKeyBindings.Bindings.Remove(helmBinding);
            LocalListBoxBindingSource.ResetBindings(false);

            await SaveLocalMouseKeyBindings().ConfigureAwait(false);
        }

        private async void LobbySayButtonClick(object sender, EventArgs e)
        {
            await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text).ConfigureAwait(false);

            LobbySayTextBox.Text = string.Empty;
        }

        private void RemoteBindingsComboBoxSelectionChangeCompleted(object sender, EventArgs e)
        {
            UpdateRemoteListBoxItems();
        }

        private async void WingManFormOnLoad(object sender, EventArgs e)
        {
            await LoadLocalMouseKeyBindings();
            await LoadRemoteMouseKeyBindings();
        }

        private async Task LoadLocalMouseKeyBindings()
        {
            try
            {
                using (var fileStream = new FileStream("LocalMouseKeyBindings.xml", FileMode.Open))
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);

                        memoryStream.Position = 0L;

                        var loadedBindings =
                            (MouseKeyBindings) MouseKeyBindings.XmlSerializer.Deserialize(memoryStream);

                        foreach (var binding in loadedBindings.Bindings) LocalMouseKeyBindings.Bindings.Add(binding);

                        LocalListBoxBindingSource.ResetBindings(false);
                    }
                }
            }
            catch (Exception)
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_loading_local_bindings}{Environment.NewLine}");
            }
        }

        private void RemoteBindingsBindButtonClicked(object sender, EventArgs e)
        {
            ShowOverlayPanel();

            MouseKeyCombo = new List<string>();

            MouseKeyApplicationHook = Hook.AppEvents();
            MouseKeyApplicationHook.MouseDown += RemoteMouseKeyHookOnMouseDown;
            MouseKeyApplicationHook.KeyUp += RemoteMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown += RemoteMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.MouseUp += RemoteMouseKeyHookOnMouseUp;
        }

        private void RemoteMouseKeyHookOnKeyDown(object sender, KeyEventArgs e)
        {
            e.SuppressKeyPress = true;

            MouseKeyCombo.Add(e.KeyCode.ToDisplayName());
        }

        private void RemoteMouseKeyHookOnMouseDown(object sender, MouseEventArgs e)
        {
            MouseKeyCombo.Add(e.Button.ToDisplayName());
        }

        private async void RemoteMouseKeyHookOnMouseUp(object sender, MouseEventArgs e)
        {
            RemoteMouseKeyBindings.Bindings.Add(new RemoteMouseKeyBinding(RemoteBindingsComboBox.Text,
                (string) RemoteBindingsListBox.SelectedItem, MouseKeyCombo));

            MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp;

            MouseKeyApplicationHook.Dispose();

            RemoteBindingsBindToBox.Text = string.Join(" + ", MouseKeyCombo);
            HideOverlayPanel();

            await SaveRemoteMouseKeyBindings().ConfigureAwait(false);
        }

        private async void RemoteMouseKeyHookOnKeyUp(object sender, KeyEventArgs e)
        {
            RemoteMouseKeyBindings.Bindings.Add(new RemoteMouseKeyBinding(RemoteBindingsComboBox.Text,
                (string) RemoteBindingsListBox.SelectedItem, MouseKeyCombo));

            MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp;
            MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown;
            MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp;

            MouseKeyApplicationHook.Dispose();

            RemoteBindingsBindToBox.Text = string.Join(" + ", MouseKeyCombo);
            HideOverlayPanel();

            await SaveRemoteMouseKeyBindings().ConfigureAwait(false);
        }

        private async Task SaveRemoteMouseKeyBindings()
        {
            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    RemoteMouseKeyBindings.XmlSerializer.Serialize(memoryStream, RemoteMouseKeyBindings);

                    memoryStream.Position = 0L;

                    using (var fileStream = new FileStream("RemoteMouseKeyBindings.xml", FileMode.Create))
                    {
                        await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
                    }
                }
            }
            catch (Exception)
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_saving_remote_bindings}{Environment.NewLine}");
            }
        }

        private async Task LoadRemoteMouseKeyBindings()
        {
            try
            {
                using (var fileStream = new FileStream("RemoteMouseKeyBindings.xml", FileMode.Open))
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);

                        memoryStream.Position = 0L;

                        var loadedBindings =
                            (RemoteMouseKeyBindings) RemoteMouseKeyBindings.XmlSerializer.Deserialize(memoryStream);

                        foreach (var binding in loadedBindings.Bindings) RemoteMouseKeyBindings.Bindings.Add(binding);
                    }
                }
            }
            catch (Exception)
            {
                ActivityTextBox.AppendText(
                    $"{Strings.Failed_loading_remote_bindings}{Environment.NewLine}");
            }
        }
    }
}