using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using Microsoft.Win32;
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using System.Threading.Tasks;

namespace Shadowsocks.View
{
    public partial class ConfigForm : Form
    {
        private ShadowsocksController controller;

        // this is a copy of configuration that we are working on
        private Configuration _modifiedConfiguration;
        private int _lastSelectedIndex = -1;

        public ConfigForm(ShadowsocksController controller)
        {
            this.Font = SystemFonts.MessageBoxFont;
            InitializeComponent();

            // a dirty hack
            this.ServersListBox.Dock = DockStyle.Fill;
            this.tableLayoutPanel5.Dock = DockStyle.Fill;
            this.PerformLayout();

            UpdateTexts();
            this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon());

            this.controller = controller;
            controller.ConfigChanged += controller_ConfigChanged;

            LoadCurrentConfiguration();
        }

        private void UpdateTexts()
        {
            AddButton.Text = I18N.GetString("&Add");
            DeleteButton.Text = I18N.GetString("&Delete");
            DuplicateButton.Text = I18N.GetString("Dupli&cate");
            IPLabel.Text = I18N.GetString("Server Addr");
            ServerPortLabel.Text = I18N.GetString("Server Port");
            PasswordLabel.Text = I18N.GetString("Password");
            ShowPasswdCheckBox.Text = I18N.GetString("Show Password");
            EncryptionLabel.Text = I18N.GetString("Encryption");
            PluginLabel.Text = I18N.GetString("Plugin Program");
            PluginOptionsLabel.Text = I18N.GetString("Plugin Options");
            PluginArgumentsLabel.Text = I18N.GetString("Plugin Arguments");
            ProxyPortLabel.Text = I18N.GetString("Proxy Port");
            PortableModeCheckBox.Text = I18N.GetString("Portable Mode");
            toolTip1.SetToolTip(this.PortableModeCheckBox, I18N.GetString("Restart required"));
            RemarksLabel.Text = I18N.GetString("Remarks");
            TimeoutLabel.Text = I18N.GetString("Timeout(Sec)");
            ServerGroupBox.Text = I18N.GetString("Server");
            OKButton.Text = I18N.GetString("OK");
            MyCancelButton.Text = I18N.GetString("Cancel");
            MoveUpButton.Text = I18N.GetString("Move &Up");
            MoveDownButton.Text = I18N.GetString("Move D&own");
            this.Text = I18N.GetString("Edit Servers");
        }

        private void controller_ConfigChanged(object sender, EventArgs e)
        {
            LoadCurrentConfiguration();
        }

        private bool ValidateAndSaveSelectedServerDetails()
        {
            try
            {
                if (_lastSelectedIndex == -1 || _lastSelectedIndex >= _modifiedConfiguration.configs.Count)
                {
                    return true;
                }
                Server server = GetServerDetailsFromUI();
                if (server == null)
                {
                    return false;
                }
                Configuration.CheckServer(server);
                _modifiedConfiguration.configs[_lastSelectedIndex] = server;

                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return false;
        }

        private Server GetServerDetailsFromUI()
        {
            Server server = new Server();
            if (Uri.CheckHostName(server.server = IPTextBox.Text.Trim()) == UriHostNameType.Unknown)
            {
                MessageBox.Show(I18N.GetString("Invalid server address"));
                IPTextBox.Focus();
                return null;
            }
            if (!int.TryParse(ServerPortTextBox.Text, out server.server_port))
            {
                MessageBox.Show(I18N.GetString("Illegal port number format"));
                ServerPortTextBox.Focus();
                return null;
            }
            server.password = PasswordTextBox.Text;
            server.method = EncryptionSelect.Text;
            server.plugin = PluginTextBox.Text;
            server.plugin_opts = PluginOptionsTextBox.Text;
            server.plugin_args = PluginArgumentsTextBox.Text;
            server.remarks = RemarksTextBox.Text;
            if (!int.TryParse(TimeoutTextBox.Text, out server.timeout))
            {
                MessageBox.Show(I18N.GetString("Illegal timeout format"));
                TimeoutTextBox.Focus();
                return null;
            }
            return server;
        }

        private void LoadSelectedServerDetails()
        {
            if (ServersListBox.SelectedIndex >= 0 && ServersListBox.SelectedIndex < _modifiedConfiguration.configs.Count)
            {
                Server server = _modifiedConfiguration.configs[ServersListBox.SelectedIndex];
                SetServerDetailsToUI(server);
            }
        }

        private void SetServerDetailsToUI(Server server)
        {
            IPTextBox.Text = server.server;
            ServerPortTextBox.Text = server.server_port.ToString();
            PasswordTextBox.Text = server.password;
            EncryptionSelect.Text = server.method ?? "aes-256-cfb";
            PluginTextBox.Text = server.plugin;
            PluginOptionsTextBox.Text = server.plugin_opts;
            PluginArgumentsTextBox.Text = server.plugin_args;
            RemarksTextBox.Text = server.remarks;
            TimeoutTextBox.Text = server.timeout.ToString();
        }

        private void LoadServerNameListToUI(Configuration configuration)
        {
            ServersListBox.Items.Clear();
            foreach (Server server in configuration.configs)
            {
                ServersListBox.Items.Add(server.FriendlyName());
            }
        }

        private void LoadCurrentConfiguration()
        {
            _modifiedConfiguration = controller.GetConfigurationCopy();
            LoadServerNameListToUI(_modifiedConfiguration);

            _lastSelectedIndex = _modifiedConfiguration.index;
            if (_lastSelectedIndex < 0 || _lastSelectedIndex >= ServersListBox.Items.Count)
            {
                _lastSelectedIndex = 0;
            }

            ServersListBox.SelectedIndex = _lastSelectedIndex;
            UpdateButtons();
            LoadSelectedServerDetails();
            ProxyPortTextBox.Text = _modifiedConfiguration.localPort.ToString();
            PortableModeCheckBox.Checked = _modifiedConfiguration.portableMode;
        }

        private bool SaveValidConfiguration()
        {
            if (!ValidateAndSaveSelectedServerDetails())
            {
                return false;
            }
            if (_modifiedConfiguration.configs.Count == 0)
            {
                MessageBox.Show(I18N.GetString("Please add at least one server"));
                return false;
            }

            int localPort = int.Parse(ProxyPortTextBox.Text);
            Configuration.CheckLocalPort(localPort);
            _modifiedConfiguration.localPort = localPort;

            _modifiedConfiguration.portableMode = PortableModeCheckBox.Checked;

            controller.SaveServers(_modifiedConfiguration.configs, _modifiedConfiguration.localPort, _modifiedConfiguration.portableMode);
            // SelectedIndex remains valid
            // We handled this in event handlers, e.g. Add/DeleteButton, SelectedIndexChanged
            // and move operations
            controller.SelectServerIndex(ServersListBox.SelectedIndex);
            return true;
        }

        private void ConfigForm_KeyDown(object sender, KeyEventArgs e)
        {
            // Sometimes the users may hit enter key by mistake, and the form will close without saving entries.

            if (e.KeyCode == Keys.Enter)
            {
                SaveValidConfiguration();
            }
        }

        private void ServersListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!ServersListBox.CanSelect)
            {
                return;
            }
            if (_lastSelectedIndex == ServersListBox.SelectedIndex)
            {
                // we are moving back to oldSelectedIndex or doing a force move
                return;
            }
            if (!ValidateAndSaveSelectedServerDetails())
            {
                // why this won't cause stack overflow?
                ServersListBox.SelectedIndex = _lastSelectedIndex;
                return;
            }
            if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count)
            {
                ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName();
            }
            UpdateButtons();
            LoadSelectedServerDetails();
            _lastSelectedIndex = ServersListBox.SelectedIndex;
        }

        private void AddButton_Click(object sender, EventArgs e)
        {
            if (!ValidateAndSaveSelectedServerDetails())
            {
                return;
            }
            Server server = Configuration.GetDefaultServer();
            _modifiedConfiguration.configs.Add(server);
            LoadServerNameListToUI(_modifiedConfiguration);
            ServersListBox.SelectedIndex = _modifiedConfiguration.configs.Count - 1;
            _lastSelectedIndex = ServersListBox.SelectedIndex;
        }

        private void DuplicateButton_Click(object sender, EventArgs e)
        {
            if (!ValidateAndSaveSelectedServerDetails())
            {
                return;
            }
            Server currServer = _modifiedConfiguration.configs[_lastSelectedIndex];
            var currIndex = _modifiedConfiguration.configs.IndexOf(currServer);
            _modifiedConfiguration.configs.Insert(currIndex + 1, currServer);
            LoadServerNameListToUI(_modifiedConfiguration);
            ServersListBox.SelectedIndex = currIndex + 1;
            _lastSelectedIndex = ServersListBox.SelectedIndex;
        }

        private void DeleteButton_Click(object sender, EventArgs e)
        {
            _lastSelectedIndex = ServersListBox.SelectedIndex;
            if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count)
            {
                _modifiedConfiguration.configs.RemoveAt(_lastSelectedIndex);
            }
            if (_lastSelectedIndex >= _modifiedConfiguration.configs.Count)
            {
                // can be -1
                _lastSelectedIndex = _modifiedConfiguration.configs.Count - 1;
            }
            ServersListBox.SelectedIndex = _lastSelectedIndex;
            LoadServerNameListToUI(_modifiedConfiguration);
            ServersListBox.SelectedIndex = _lastSelectedIndex;
            LoadSelectedServerDetails();
        }

        private void OKButton_Click(object sender, EventArgs e)
        {
            if (SaveValidConfiguration())
            {
                this.Close();
            }
        }

        private void CancelButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void ConfigForm_Shown(object sender, EventArgs e)
        {
            IPTextBox.Focus();
        }

        private void ConfigForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            controller.ConfigChanged -= controller_ConfigChanged;
        }

        private void MoveConfigItem(int step)
        {
            int index = ServersListBox.SelectedIndex;
            Server server = _modifiedConfiguration.configs[index];
            object item = ServersListBox.Items[index];

            _modifiedConfiguration.configs.Remove(server);
            _modifiedConfiguration.configs.Insert(index + step, server);
            _modifiedConfiguration.index += step;

            ServersListBox.BeginUpdate();
            ServersListBox.Enabled = false;
            _lastSelectedIndex = index + step;
            ServersListBox.Items.Remove(item);
            ServersListBox.Items.Insert(index + step, item);
            ServersListBox.Enabled = true;
            ServersListBox.SelectedIndex = index + step;
            ServersListBox.EndUpdate();

            UpdateButtons();
        }

        private void UpdateButtons()
        {
            DeleteButton.Enabled = (ServersListBox.Items.Count > 0);
            MoveUpButton.Enabled = (ServersListBox.SelectedIndex > 0);
            MoveDownButton.Enabled = (ServersListBox.SelectedIndex < ServersListBox.Items.Count - 1);
        }

        private void MoveUpButton_Click(object sender, EventArgs e)
        {
            if (!ValidateAndSaveSelectedServerDetails())
            {
                return;
            }
            if (ServersListBox.SelectedIndex > 0)
            {
                MoveConfigItem(-1);  // -1 means move backward
            }
        }

        private void MoveDownButton_Click(object sender, EventArgs e)
        {
            if (!ValidateAndSaveSelectedServerDetails())
            {
                return;
            }
            if (ServersListBox.SelectedIndex < ServersListBox.Items.Count - 1)
            {
                MoveConfigItem(+1);  // +1 means move forward
            }
        }

        private void ShowPasswdCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            this.PasswordTextBox.UseSystemPasswordChar = !this.ShowPasswdCheckBox.Checked;
        }
    }
}
