using System.Diagnostics;
using System.Net.Sockets;
using Sunfish.Config;
using Sunfish.Host;
using Sunfish.Net;

namespace Sunfish.WinForms;

/// <summary>
/// Ventana principal con el aspecto de la 2.0: pequena y vertical. Arriba, menu raiz y puerto;
/// en medio, los shares con su icono de carpeta (se arrastran carpetas para compartirlas); abajo,
/// botones con imagen. Direcciones y registro estan en la ventana de informacion (i). "Ok"
/// manda la ventana al area de notificacion sin parar el servidor.
/// </summary>
internal sealed class MainForm : Form
{
    private const int ItemHeight = 32;
    private const int TopHeight = 58;
    private const int ClientHeight = 384;

    private readonly SunfishHost _host;
    private readonly List<string> _pendingFolders;
    private readonly ToolTip _tips = new();
    private readonly CheckBox _rootMenu = new() { AutoSize = true, Margin = new Padding(3, 5, 6, 0) };
    private readonly CheckBox _listen = new() { AutoSize = true, Margin = new Padding(3, 5, 2, 0) };
    private readonly NumericUpDown _port = new() { Minimum = 1, Maximum = 65535, Width = 62, Margin = new Padding(0, 3, 0, 0) };
    private readonly CheckBox _ftp = new() { AutoSize = true, Margin = new Padding(3, 5, 2, 0) };
    private readonly NumericUpDown _ftpPort = new() { Minimum = 1, Maximum = 65535, Width = 62, Margin = new Padding(0, 3, 0, 0) };
    private readonly ListBox _shares = new()
    {
        Location = new Point(0, TopHeight), DrawMode = DrawMode.OwnerDrawFixed, ItemHeight = UiImages.Px(ItemHeight),
        IntegralHeight = false, AllowDrop = true, BorderStyle = BorderStyle.FixedSingle,
        Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
    };
    private readonly Button _add;
    private readonly Button _remove;
    private readonly Button _edit;
    private readonly Button _users;
    private readonly Button _explore;
    private readonly Button _info;
    private readonly Button _ok = new() { UseVisualStyleBackColor = true, Size = new Size(70, 26), Anchor = AnchorStyles.Bottom | AnchorStyles.Right };
    private readonly ContextMenuStrip _menu = new() { ImageScalingSize = new Size(UiImages.Px(16), UiImages.Px(16)) };
    private readonly NotifyIcon _tray = new();
    private readonly Font _small;
    private InfoForm? _infoForm;
    private bool _updating;

    public MainForm(SunfishHost host, List<string> pendingFolders)
    {
        _host = host;
        _pendingFolders = pendingFolders;
        _small = new Font(Font.FontFamily, Font.Size - 1f);
        _add = UiImages.IconButton("plus", Texts.Add, _tips);
        _remove = UiImages.IconButton("minus", Texts.Remove, _tips);
        _edit = UiImages.IconButton("pencil", Texts.Edit, _tips);
        _users = UiImages.IconButton("users", Texts.Users, _tips);
        _explore = UiImages.IconButton("share", Texts.Explore, _tips);
        _info = UiImages.IconButton("info", Texts.Info, _tips);

        SuspendLayout();
        AutoScaleDimensions = new SizeF(96F, 96F);
        AutoScaleMode = AutoScaleMode.Dpi;
        ClientSize = new Size(292, ClientHeight);
        MinimumSize = new Size(260, 260);
        Text = "Sunfish";
        Icon = LoadIcon();
        StartPosition = FormStartPosition.CenterScreen;
        MaximizeBox = false;

        _rootMenu.Text = Texts.RootMenu;
        _listen.Text = Texts.ListenOnPort;
        _ftp.Text = Texts.FtpOnPort;
        _tips.SetToolTip(_listen, Texts.ListenTip);
        _tips.SetToolTip(_ftp, Texts.FtpTip);
        _shares.Size = new Size(292, ClientHeight - TopHeight - 36);
        _ok.Text = Texts.Hide;
        _tips.SetToolTip(_ok, Texts.HideTip);

        int y = ClientHeight - 31;
        _add.Location = new Point(6, y);
        _remove.Location = new Point(35, y);
        _edit.Location = new Point(64, y);
        _users.Location = new Point(100, y);
        _explore.Location = new Point(129, y);
        _info.Location = new Point(292 - 70 - 6 - 32, y);
        _ok.Location = new Point(292 - 70 - 6, y);
        foreach (var b in new[] { _add, _remove, _edit, _users, _explore })
            b.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
        _info.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;

        // Cabecera: menu raiz y, en columna, HTTP y FTP con sus puertos alineados. Las columnas se
        // ajustan solas al largo de los textos en cada idioma.
        var top = new TableLayoutPanel
        {
            Location = new Point(2, 2), Size = new Size(288, TopHeight - 4), ColumnCount = 3, RowCount = 2,
            Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
        };
        top.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        top.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        top.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        top.Controls.Add(_rootMenu, 0, 0);
        top.Controls.Add(_listen, 1, 0);
        top.Controls.Add(_port, 2, 0);
        top.Controls.Add(_ftp, 1, 1);
        top.Controls.Add(_ftpPort, 2, 1);
        Controls.AddRange([top, _shares, _add, _remove, _edit, _users, _explore, _info, _ok]);
        ResumeLayout(false);
        PerformLayout();

        _rootMenu.CheckedChanged += async (_, _) => await ChangeRootMenuAsync();
        _listen.CheckedChanged += async (_, _) => await ToggleServerAsync();
        _port.ValueChanged += async (_, _) => await ChangePortAsync();
        _ftp.CheckedChanged += async (_, _) => await ToggleFtpAsync();
        _ftpPort.ValueChanged += async (_, _) => await ChangeFtpPortAsync();
        _shares.DrawItem += DrawShare;
        _shares.DoubleClick += (_, _) => EditSelected();
        _shares.SelectedIndexChanged += (_, _) => UpdateButtons();
        _shares.MouseDown += (_, e) =>
        {
            if (e.Button == MouseButtons.Right)
                _shares.SelectedIndex = _shares.IndexFromPoint(e.Location);
        };
        _shares.KeyDown += (_, e) =>
        {
            if (e.KeyCode == Keys.Delete) RemoveSelected();
            if (e.KeyCode == Keys.Enter) EditSelected();
            if (e.KeyCode == Keys.Insert) AddShare(null);
            if (e.KeyCode == Keys.Space) _ = ToggleSelectedAsync();
        };
        _shares.ContextMenuStrip = _menu;
        _menu.Opening += (_, _) => BuildMenu();
        _add.Click += (_, _) => AddShare(null);
        _remove.Click += (_, _) => RemoveSelected();
        _edit.Click += (_, _) => EditSelected();
        _users.Click += (_, _) => ShowUsers();
        _explore.Click += (_, _) => UiPlacement.ShowCentered(new ExplorerForm { Icon = Icon }, this);
        _info.Click += (_, _) => ShowInfo();
        _ok.Click += (_, _) => WindowState = FormWindowState.Minimized;

        AllowDrop = true;
        foreach (Control target in new Control[] { this, _shares })
        {
            target.DragEnter += (_, e) => e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true ? DragDropEffects.Copy : DragDropEffects.None;
            target.DragDrop += (_, e) =>
            {
                if (e.Data?.GetData(DataFormats.FileDrop) is string[] paths)
                    foreach (var path in paths)
                        AddShare(Directory.Exists(path) ? path : Path.GetDirectoryName(path));
            };
        }

        BuildTray();
        _host.Changed += () => SafeInvoke(RefreshAll);
    }

    private static Icon? LoadIcon()
    {
        using var stream = typeof(MainForm).Assembly.GetManifestResourceStream("sunfish.ico");
        return stream is null ? null : new Icon(stream);
    }

    private void BuildTray()
    {
        _tray.Icon = Icon;
        _tray.Text = "Sunfish";
        var menu = new ContextMenuStrip();
        menu.Items.Add(Texts.Restore, null, (_, _) => RestoreFromTray());
        menu.Items.Add(Texts.Exit, null, (_, _) => Close());
        _tray.ContextMenuStrip = menu;
        _tray.DoubleClick += (_, _) => RestoreFromTray();
        Resize += (_, _) =>
        {
            if (WindowState == FormWindowState.Minimized)
            {
                Hide();
                _tray.Visible = true;
            }
        };
    }

    private void RestoreFromTray()
    {
        Show();
        WindowState = FormWindowState.Normal;
        _tray.Visible = false;
        Activate();
    }

    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        try
        {
            _host.Start();
        }
        catch (SocketException ex)
        {
            _host.Log.Error(Strings.CannotListen(_host.Config.Server.HttpPort, ex.Message));
            MessageBox.Show(this, Texts.CannotStart(ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
        RefreshAll();
        foreach (var folder in _pendingFolders)
            AddShare(folder);
        _pendingFolders.Clear();
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        _tray.Visible = false;
        _tray.Dispose();
        base.OnFormClosed(e);
    }

    /// <summary>Otra instancia (o arrastrar sobre el .exe con la ventana abierta) trae carpetas.</summary>
    public void ReceiveFromOtherInstance(List<string> folders)
    {
        RestoreFromTray();
        foreach (var folder in folders)
            AddShare(folder);
    }

    // ------------------------------------------------------------------ estado

    private bool IsQuick => _host.Config.FilePath is null;

    private void RefreshAll()
    {
        _updating = true;
        try
        {
            Text = IsQuick ? Texts.QuickTitle : "Sunfish";
            _rootMenu.Checked = _host.Config.Server.RootMenu;
            // Cada casilla es el interruptor del servicio; en rojo si deberia escuchar y no puede
            // (puerto ocupado).
            var server = _host.Config.Server;
            _listen.Checked = server.Http;
            _listen.ForeColor = server.Http && !_host.HttpRunning ? Color.Firebrick : SystemColors.ControlText;
            _port.Value = Math.Clamp(server.HttpPort, 1, 65535);
            _ftp.Checked = server.Ftp;
            _ftp.ForeColor = server.Ftp && !_host.FtpRunning ? Color.Firebrick : SystemColors.ControlText;
            _ftpPort.Value = Math.Clamp(server.FtpPort, 1, 65535);
            string? selected = SelectedShare()?.Name;
            _shares.BeginUpdate();
            _shares.Items.Clear();
            foreach (var s in _host.Config.Shares)
                _shares.Items.Add(s.Name);
            _shares.SelectedIndex = selected is null ? -1 : _shares.Items.IndexOf(selected);
            _shares.EndUpdate();
            _tray.Text = "Sunfish" + (_host.HttpRunning ? $" http:{_host.HttpPort}" : "") + (_host.FtpRunning ? $" ftp:{_host.FtpPort}" : "");
            UpdateButtons();
        }
        finally
        {
            _updating = false;
        }
    }

    private void UpdateButtons()
    {
        bool any = SelectedShare() is not null;
        _remove.Enabled = any;
        _edit.Enabled = any;
    }

    /// <summary>Como en la 2.0: icono de carpeta (gris si esta desactivado), nombre y debajo, en pequeno, la location.</summary>
    private void DrawShare(object? sender, DrawItemEventArgs e)
    {
        e.DrawBackground();
        if (e.Index < 0 || e.Index >= _shares.Items.Count)
            return;
        var share = _host.Config.Shares.FirstOrDefault(s => s.Name == (string)_shares.Items[e.Index]);
        if (share is null)
            return;
        bool selected = (e.State & DrawItemState.Selected) != 0;
        var g = e.Graphics;
        int icon = UiImages.Px(24);
        int top = e.Bounds.Top + (e.Bounds.Height - icon) / 2;
        g.DrawImage(UiImages.Get(share.Enabled ? "foldericon" : "foldericond", 24), e.Bounds.Left + UiImages.Px(3), top, icon, icon);
        int x = e.Bounds.Left + icon + UiImages.Px(8);
        var main = selected ? SystemColors.HighlightText : share.Enabled ? SystemColors.WindowText : SystemColors.GrayText;
        var sub = selected ? SystemColors.HighlightText : SystemColors.GrayText;
        int lineHeight = Font.Height;
        TextRenderer.DrawText(g, share.Name, Font, new Point(x, e.Bounds.Top + UiImages.Px(1)), main);
        string detail = share.Location + "   " + Permissions(share) + (share.Users.Count > 0 ? "   " + string.Join(", ", share.Users) : "");
        TextRenderer.DrawText(g, detail, _small, new Point(x + UiImages.Px(4), e.Bounds.Top + lineHeight), sub);
        if (share.Upload)
        {
            int badge = UiImages.Px(12);
            g.DrawImage(UiImages.Get("upload", 12), e.Bounds.Left + UiImages.Px(1), e.Bounds.Bottom - badge - UiImages.Px(1), badge, badge);
        }
        e.DrawFocusRectangle();
    }

    private static string Permissions(ShareConfig s) =>
        (s.Listing ? "L" : "·") + (s.Upload ? "U" : "·") + (s.Overwrite ? "O" : "·") +
        (s.Rename ? "R" : "·") + (s.Delete ? "D" : "·") + (s.Zip ? "Z" : "·");

    private void SafeInvoke(Action action)
    {
        if (IsDisposed || !IsHandleCreated)
            return;
        try
        {
            BeginInvoke(action);
        }
        catch (InvalidOperationException) { }
    }

    // ------------------------------------------------------------------ acciones

    private async Task ToggleServerAsync()
    {
        if (_updating || _listen.Checked == _host.Config.Server.Http)
            return;
        bool on = _listen.Checked;
        await ChangeAsync(c => c.Server.Http = on);
    }

    private async Task ToggleFtpAsync()
    {
        if (_updating || _ftp.Checked == _host.Config.Server.Ftp)
            return;
        bool on = _ftp.Checked;
        await ChangeAsync(c => c.Server.Ftp = on);
    }

    private async Task ChangeFtpPortAsync()
    {
        if (_updating || (int)_ftpPort.Value == _host.Config.Server.FtpPort)
            return;
        int port = (int)_ftpPort.Value;
        await ChangeAsync(c => c.Server.FtpPort = port);
    }

    private async Task ChangePortAsync()
    {
        if (_updating || (int)_port.Value == _host.Config.Server.HttpPort)
            return;
        int port = (int)_port.Value;
        await ChangeAsync(c => c.Server.HttpPort = port);
    }

    private async Task ChangeRootMenuAsync()
    {
        if (_updating || _rootMenu.Checked == _host.Config.Server.RootMenu)
            return;
        bool value = _rootMenu.Checked;
        await ChangeAsync(c => c.Server.RootMenu = value);
    }

    private ShareConfig? SelectedShare() =>
        _shares.SelectedItem is string name ? _host.Config.Shares.FirstOrDefault(s => s.Name == name) : null;

    private async Task ToggleSelectedAsync()
    {
        if (SelectedShare() is not { } share)
            return;
        var copy = share.Clone();
        copy.Enabled = !share.Enabled;
        if (ShareSettings.Validate(_host.Config, copy, share.Name) is { } problem)
        {
            MessageBox.Show(this, problem, Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
            return;
        }
        await ChangeAsync(c => c.Shares.First(s => s.Name == share.Name).Enabled = copy.Enabled);
    }

    private void AddShare(string? folder)
    {
        var share = new ShareConfig();
        if (folder is not null && Directory.Exists(folder))
        {
            string full = Path.GetFullPath(folder);
            string name = Path.GetFileName(Path.TrimEndingDirectorySeparator(full));
            if (name.Length == 0)
                name = "share";
            share.Name = ShareSettings.SuggestName(_host.Config, name);
            share.Location = ShareSettings.SuggestLocation(_host.Config, share.Name);
            share.Path = full;
        }
        using var dialog = new ShareDialog(share, _host.Config, null);
        if (dialog.ShowDialog(this) == DialogResult.OK)
            _ = ChangeAsync(c => c.Shares.Add(dialog.Result));
    }

    private void EditSelected()
    {
        if (SelectedShare() is not { } share)
            return;
        using var dialog = new ShareDialog(share.Clone(), _host.Config, share.Name);
        if (dialog.ShowDialog(this) == DialogResult.OK)
        {
            _ = ChangeAsync(c =>
            {
                int index = c.Shares.FindIndex(s => s.Name == share.Name);
                if (index >= 0)
                    c.Shares[index] = dialog.Result;
            });
        }
    }

    private void RemoveSelected()
    {
        if (SelectedShare() is not { } share)
            return;
        if (MessageBox.Show(this, Strings.AskDeleteShare(share.Name), Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            _ = ChangeAsync(c => c.Shares.RemoveAll(s => s.Name == share.Name));
    }

    private void ShowUsers()
    {
        using var dialog = new UsersDialog(_host.Config.Clone());
        if (dialog.ShowDialog(this) == DialogResult.OK && dialog.Changed)
            _ = ChangeAsync(c =>
            {
                c.Users.Clear();
                c.Users.AddRange(dialog.Config.Users);
                foreach (var s in c.Shares)
                    s.Users.RemoveAll(u => !c.Users.Any(x => x.Name.Equals(u, StringComparison.OrdinalIgnoreCase)));
            });
    }

    private void ShowInfo()
    {
        if (_infoForm is null || _infoForm.IsDisposed)
        {
            _infoForm = new InfoForm(_host) { Icon = Icon };
            UiPlacement.ShowCentered(_infoForm, this);
        }
        else
        {
            _infoForm.Activate();
        }
    }

    private void BuildMenu()
    {
        _menu.Items.Clear();
        if (SelectedShare() is not { } share)
        {
            _menu.Items.Add(Texts.Add.Replace("&", ""), UiImages.Get("plus"), (_, _) => AddShare(null));
            return;
        }
        var addresses = LocalAddresses.Get();
        if (addresses.Count > 0 && (_host.HttpRunning || _host.FtpRunning))
        {
            if (_host.HttpRunning)
            {
                string first = LocalAddresses.HttpUrl(addresses[0].Address, _host.HttpPort, share);
                _menu.Items.Add(Texts.OpenInBrowser, UiImages.Get("share"), (_, _) => Open(first));
            }
            var copy = new ToolStripMenuItem(Texts.CopyUrl);
            foreach (var address in addresses)
            {
                var urls = new List<string>();
                if (_host.HttpRunning)
                    urls.Add(LocalAddresses.HttpUrl(address.Address, _host.HttpPort, share));
                if (_host.FtpRunning && share.Ftp)
                    urls.Add(LocalAddresses.FtpUrl(address.Address, _host.FtpPort, share));
                foreach (var url in urls)
                    copy.DropDownItems.Add($"{url}   ({address.Interface})", null, (_, _) => Clipboard.SetText(url));
            }
            _menu.Items.Add(copy);
        }
        _menu.Items.Add(Texts.OpenFolder, UiImages.Get("foldericon"), (_, _) => Open(share.Path));
        _menu.Items.Add(new ToolStripSeparator());
        var edit = new ToolStripMenuItem(Texts.Edit.Replace("&", ""), UiImages.Get("pencil"), (_, _) => EditSelected());
        edit.Font = new Font(edit.Font, FontStyle.Bold);
        _menu.Items.Add(edit);
        _menu.Items.Add(share.Enabled ? Texts.Disable : Texts.Enable, UiImages.Get(share.Enabled ? "foldericond" : "foldericon"), async (_, _) => await ToggleSelectedAsync());
        _menu.Items.Add(Texts.Remove.Replace("&", ""), UiImages.Get("delete"), (_, _) => RemoveSelected());
    }

    internal static void Open(string? target)
    {
        if (string.IsNullOrEmpty(target))
            return;
        try
        {
            Process.Start(new ProcessStartInfo(target) { UseShellExecute = true });
        }
        catch (Exception e) when (e is System.ComponentModel.Win32Exception or InvalidOperationException) { }
    }

    /// <summary>Cambia una copia de la configuracion, la guarda (si hay fichero) y la aplica.</summary>
    private async Task ChangeAsync(Action<SunfishConfig> change)
    {
        var copy = _host.Config.Clone();
        change(copy);
        bool ok = IsQuick ? await _host.ApplyAsync(copy) : await _host.SaveAndApplyAsync(copy);
        if (!ok)
            MessageBox.Show(this, Texts.CannotStart(Strings.CannotListen(copy.Server.HttpPort, "")), Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
        RefreshAll();
    }
}
