//  
//   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.ComponentModel;
using System.Threading.Tasks;
using Rebex.Net;

namespace Rebex.TerminalEmulation
{
    /// <summary>
    /// SSH terminal emulator control.
    /// </summary>
    public partial class SshTerminalControl : TerminalControl
    {
        private readonly Ssh _ssh;

        private string _serverName = ServerNameDefault;
        private int _serverPort = ServerPortDefault;
        private string _userName = UserNameDefault;
        private string _userPassword = UserPasswordDefault;
        private SshPrivateKey _userPrivateKey;

        // default values for both designer and properties
        private const string ServerNameDefault = "";
        private const int ServerPortDefault = Ssh.DefaultPort;
        private const string UserNameDefault = "";
        private const string UserPasswordDefault = "";

        /// <summary>
        /// Gets or sets the network proxy to use to access a remote server.
        /// </summary>
        /// <value>The <see cref="Proxy"/> to use to access a remote server.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Proxy Proxy
        {
            get { return _ssh.Proxy; }
            set { _ssh.Proxy = value; }
        }

        /// <summary>
        /// Gets or sets the server address - either a hostname or a dotted string IP address.
        /// </summary>
        /// <value>Server address.</value>
        [Description("Gets or sets the server address - either a hostname or a dotted string IP address.")]
        [DefaultValue(ServerNameDefault)]
        public string ServerName
        {
            get { return _serverName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                _serverName = value;
            }
        }

        /// <summary>
        /// Gets or sets the server port. Default value is 22.
        /// </summary>
        /// <value>Server port.</value>
        [DefaultValue(ServerPortDefault)]
        [Description("Gets or sets the server port. Default value is 22.")]
        public int ServerPort
        {
            get { return _serverPort; }
            set { _serverPort = value; }
        }

        /// <summary>
        /// Gets or sets the SSH security parameters.
        /// </summary>
        /// <value>SSH security parameters.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SshParameters ServerParameters
        {
            get { return _ssh.Settings.SshParameters; }
            set { _ssh.Settings.SshParameters = value; }
        }

        /// <summary>
        /// Gets or sets the username to be used to authenticate to the server.
        /// </summary>
        /// <value>The username.</value>
        [DefaultValue(UserNameDefault)]
        [Description("Gets or sets the username to be used to authenticate to the server.")]
        public string UserName
        {
            get { return _userName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                _userName = value;
            }
        }

        /// <summary>
        /// Gets or sets the password to be used to authenticate to the server.
        /// </summary>
        /// <value>The password.</value>
        [Description("Gets or sets the password to be used to authenticate to the server.")]
        [DefaultValue(UserPasswordDefault)]
        public string UserPassword
        {
            get { return _userPassword; }
            set { _userPassword = value; }
        }

        /// <summary>
        /// RSA or DSA private key for key-based authentication.
        /// </summary>
        /// <value>RSA or DSA private key.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SshPrivateKey UserPrivateKey
        {
            get { return _userPrivateKey; }
            set { _userPrivateKey = value; }
        }

        /// <summary>
        /// Gets the server public key fingerprint.
        /// </summary>
        /// <value>Server fingerprint.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string Fingerprint
        {
            get { return _ssh.Fingerprint; }
        }

        /// <summary>
        /// Gets or sets the text associated with this control.  
        /// </summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets the logger used by this object.
        /// </summary>
        /// <value>Logger.</value>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ILogWriter LogWriter
        {
            get
            {
                return _ssh.LogWriter;
            }
            set
            {
                _ssh.LogWriter = value;
            }
        }


        /// <summary>
        /// Occurs when connected to the server.
        /// </summary>
        public event EventHandler Connected;

        /// <summary>
        /// Raises the <see cref="Connected"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        protected virtual void OnConnected(EventArgs e)
        {
            EventHandler h = Connected;
            if (h != null)
                h(this, e);
        }

        /// <summary>
        /// Raises the <see cref="TerminalControl.Disconnected"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        protected override void OnDisconnected(EventArgs e)
        {
            base.OnDisconnected(e);
            _ssh.Disconnect();
        }

        /// <summary>
        /// Creates a new instance of SSH terminal control.
        /// </summary>
        public SshTerminalControl()
        {
            _ssh = new Ssh();
        }

        /// <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)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                _ssh.Dispose();
            }
        }

        /// <summary>
        /// Connects to the SSH server specified by <see cref="ServerName"/> and <see cref="ServerPort"/> properties
        /// and authenticates using the credential specified by <see cref="UserName"/>, <see cref="UserPassword"/> and <see cref="UserPrivateKey"/> properties.
        /// </summary>
        public void Connect()
        {
            if (_ssh.IsConnected)
                throw new SshException("Already connected.");

            string serverName = _serverName.Trim();
            if (serverName.Length == 0)
                throw new SshException("Empty host name.");

            if (_serverPort < 1 || _serverPort > 65535)
                throw new SshException("Port out of range.");

            ConnectSync(serverName, _serverPort);
        }

        /// <summary>
        /// Begins asynchronous Connect operation.
        /// Connects to the SSH server specified by <see cref="ServerName" /> and <see cref="ServerPort" /> properties
        /// and authenticates using the credential specified by <see cref="UserName" />, <see cref="UserPassword" /> and <see cref="UserPrivateKey" /> properties.
        /// </summary>
        public Task ConnectAsync()
        {
            if (_ssh.IsConnected)
                throw new SshException("Already connected.");

            string serverName = _serverName.Trim();
            if (serverName.Length == 0)
                throw new SshException("Empty host name.");

            if (_serverPort < 1 || _serverPort > 65535)
                throw new SshException("Port out of range.");

            return Task.Run(() => ConnectSync(serverName, _serverPort));
        }

        private void ConnectSync(string serverName, int _serverPort)
        {
            _ssh.Connect(serverName, _serverPort);

            bool ok = false;
            try
            {
                _ssh.Login(_userName, _userPassword, _userPrivateKey);
                ok = true;
            }
            finally
            {
                if (!ok)
                    _ssh.Disconnect();
            }

            Bind(_ssh);

            if (InvokeRequired)
                Invoke(new Action<EventArgs>(OnConnected), EventArgs.Empty);
            else
                OnConnected(EventArgs.Empty);
        }

        /// <summary>
        /// Disconnects from the SSH server.
        /// </summary>
        public void Disconnect()
        {
            Unbind();

            if (_ssh.IsConnected)
            {
                _ssh.Disconnect();
                OnDisconnected(EventArgs.Empty);
            }
        }
    }
}
