//  
//   Rebex Sample Code License
// 
//   Copyright 2025, REBEX CR s.r.o.
//   All rights reserved.
//   https://www.rebex.net/
// 
//   Permission to use, copy, modify, and/or distribute this software for any
//   purpose with or without fee is hereby granted.
// 
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//   OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Text;

using Rebex;
using Rebex.Net;
using Rebex.Net.Servers;
using Rebex.Samples;

namespace Rebex.Samples
{

    /// <summary>
    /// Main form. Starts the server and shows its UI.
    /// </summary>
    public partial class MainForm : Form
    {
        // sample user credentials
        private string _username = "user01";
        private string _password = "password";

        // sample user
        private FileServerUser _user01;

        // sample user's virtual root
        private string _userVirtualRoot = ".";

        // server port and 
        private int _port = 8022;
        private FileServer _server;

        // RichTextBox-based ILogWriter
        private RichTextBoxLogWriter _serverLog;

        // server state
        private bool _running;
        private bool _firstTimeStart = true;

        private const string MessageBoxTitle = "Simple File Server (SFTP, SCP, SSH).";

        /// <summary>
        /// The MainForm constructor.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = true;

            folderBrowserDialog.Description = string.Format("Select the directory that you want to use as the virtual root for '{0}'.", _username);
            _userVirtualRoot = Path.GetFullPath(_userVirtualRoot);
            this.tbVirtualRoot.Text = _userVirtualRoot;

            // create RichTextBox-based log writer
            _serverLog = new RichTextBoxLogWriter(tbLog, tbLog.MaxLength, LogLevel.Debug); // LogLevel controls the log verbosity

            // set up the server
            try
            {
                _server = new FileServer();
                _server.LogWriter = _serverLog;

                // add private key
                SshPrivateKey rsaKey = ServerKey.GetServerPrivateKey();
                _server.Keys.Add(rsaKey);

                // add a sample user
                _user01 = new FileServerUser(_username, _password, _userVirtualRoot);
                _server.Users.Add(_user01);

                DisplayWelcome();
                btnChangeVirtualRoot.Enabled = true;
                btnStartStop.Enabled = true;
                btnBrowseRoot.Enabled = true;
            }
            catch (Exception ex)
            {
                WriteToLog(ex);
            }
        }

        private void BindToPort(int port)
        {
            // allow SFTP subsystem
            _server.Bind(port, FileServerProtocol.Sftp);

            // allow shell-like SCP subsystem
            _server.Bind(port, FileServerProtocol.Scp);
        }

        private int GetPort()
        {
            int result;

            if (!int.TryParse(tbPort.Text, out result))
                throw new FormatException("The port is not a valid port number.");

            return result;
        }

        /// <summary>
        /// Start the server listeners.
        /// </summary>
        private void StartServer()
        {
            if (_firstTimeStart)
            {
                _port = GetPort();
                BindToPort(_port);
                _firstTimeStart = false;
            }
            else
            {
                // check whether the port has changed
                int newPort = GetPort();
                if (_port != newPort)
                {
                    // unbind old port
                    _server.Unbind(_port);

                    // bind to new port
                    BindToPort(newPort);
                    _port = newPort;
                }
            }

            _server.Start();
            _running = true;
            this.btnStartStop.Text = "&Stop";
            this.lblServerInfo.Text = string.Format("Running. Listening at port {0}.", _port);

            DisplayConnectionInfo();
        }

        /// <summary>
        /// Stops listening for new connections to the server. (Active connections will be maintained).
        /// </summary>
        private void StopServer()
        {
            _server.Stop();
            _running = false;
            this.btnStartStop.Text = "&Start";
            this.lblServerInfo.Text = "Server has been stopped.";
        }

        /// <summary>
        /// Handles the Start/Stop button click.
        /// </summary>
        private void btnStartStop_Click(object sender, EventArgs e)
        {
            try
            {
                if (!_running)
                {
                    StartServer();
                }
                else
                {
                    StopServer();
                }
            }
            catch (Exception ex)
            {
                WriteToLog(ex);
            }
        }

        /// <summary>
        /// Stops the server when the form is closed.
        /// </summary>
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (_running)
                StopServer();
        }

        /// <summary>
        /// Handles the virtual root change for the sample user.
        /// </summary>
        /// <param name="newPath">The path selected by user.</param>
        private void ChangeVirtualRoot(string newPath)
        {
            if (!Directory.Exists(newPath))
            {
                MessageBox.Show(string.Format("The path could not be found.", newPath), MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
                tbVirtualRoot.Text = _userVirtualRoot;
                return;
            }

            newPath = Path.GetFullPath(newPath);

            if (newPath != _userVirtualRoot)
            {
                // update paths
                tbVirtualRoot.Text = newPath;
                _userVirtualRoot = newPath;

                // remove the sample user with the old virtual root path
                _server.Users.Remove(_user01);

                // add a new sample user with the new virtual root path
                _user01 = new FileServerUser(_username, _password, _userVirtualRoot);
                _server.Users.Add(_user01);


                if (_running)
                    MessageBox.Show("The change will apply to new sessions.", MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
                else
                    MessageBox.Show("The virtual root has been changed.", MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show(string.Format("The path is the current virtual directory. No change will be performed.", newPath), MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        
        /// <summary>
        /// Handles the Browse button click.
        /// </summary>
        private void btnBrowseRoot_Click(object sender, EventArgs e)
        {
            DialogResult result = folderBrowserDialog.ShowDialog();
            if (result == DialogResult.OK)
                ChangeVirtualRoot(folderBrowserDialog.SelectedPath);
        }

        /// <summary>
        /// Handles the Change button click.
        /// </summary>
        private void btnChangeVirtualRoot_Click(object sender, EventArgs e)
        {
            ChangeVirtualRoot(tbVirtualRoot.Text);
        }

        /// <summary>
        /// Writes an exception to the log.
        /// </summary>
        /// <param name="x">The exception.</param>
        public void WriteToLog(Exception x)
        {
            _serverLog.WriteToLog("* " + x.ToString(), RichTextBoxLogWriter.COLORERROR);
        }

        /// <summary>
        /// Displays welcome message.
        /// </summary>
        private void DisplayWelcome()
        {
            _serverLog.WriteToLog("Welcome to Rebex File Server! To connect to the server, use an SFTP, SCP or SSH client.\n", Color.DarkGreen);
        }

        /// <summary>
        /// Displays information on how to connect to the server (IP address, port, username, password).
        /// </summary>
        private void DisplayConnectionInfo()
        {
            // use DNS to get the host's IP addresses
            string host = Dns.GetHostName();
            IPHostEntry ip = Dns.GetHostEntry(host);
            StringBuilder ipAddresses = new StringBuilder();
            foreach (IPAddress ipAddress in ip.AddressList)
            {
                if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    ipAddresses.AppendFormat("  {0}:{1}\n", ipAddress, _port);
            }

            // display server IP addresses, port, username and password
            _serverLog.WriteToLog(Color.DarkBlue, "\nAccepting connections at:\n{0}\nUsername: {1}\nPassword: {2}\n", ipAddresses, _username, _password);
        }
    }
}
