using System;
using System.Collections.Generic;
using System.IO;
using System.Collections.ObjectModel;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using Renci.SshNet.Common;
namespace Renci.SshNet
{
    /// 
    /// Provides client connection to SSH server.
    /// 
    public class SshClient : BaseClient
    {
        /// 
        /// Holds the list of forwarded ports
        /// 
        private List _forwardedPorts = new List();
        /// 
        /// If true, causes the connectionInfo object to be disposed.
        /// 
        private bool _disposeConnectionInfo;
        private Stream _inputStream;
        /// 
        /// Gets the list of forwarded ports.
        /// 
        public IEnumerable ForwardedPorts
        {
            get
            {
                return this._forwardedPorts.AsReadOnly();
            }
        }
        #region Constructors
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The connection info.
        ///  is null.
        public SshClient(ConnectionInfo connectionInfo)
            : base(connectionInfo)
        {
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// Connection host.
        /// Connection port.
        /// Authentication username.
        /// Authentication password.
        ///  is null.
        ///  is invalid, or  is null or contains whitespace characters.
        ///  is not within  and .
        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
        public SshClient(string host, int port, string username, string password)
            : this(new PasswordConnectionInfo(host, port, username, password))
        {
            this._disposeConnectionInfo = true;
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// Connection host.
        /// Authentication username.
        /// Authentication password.
        ///  is null.
        ///  is invalid, or  is null or contains whitespace characters.
        public SshClient(string host, string username, string password)
            : this(host, 22, username, password)
        {
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// Connection host.
        /// Connection port.
        /// Authentication username.
        /// Authentication private key file(s) .
        ///  is null.
        ///  is invalid, -or-  is null or contains whitespace characters.
        ///  is not within  and .
        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
        public SshClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
        {
            this._disposeConnectionInfo = true;
        }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// Connection host.
        /// Authentication username.
        /// Authentication private key file(s) .
        ///  is null.
        ///  is invalid, -or-  is null or contains whitespace characters.
        public SshClient(string host, string username, params PrivateKeyFile[] keyFiles)
            : this(host, 22, username, keyFiles)
        {
        }
        #endregion
        /// 
        /// Called when client is disconnecting from the server.
        /// 
        protected override void OnDisconnecting()
        {
            base.OnDisconnecting();
            foreach (var port in this._forwardedPorts)
            {
                port.Stop();
            }
        }
        /// 
        /// Adds the forwarded port.
        /// 
        /// The port.
        /// Forwarded port is already added to a different client.
        ///  is null.
        /// Client is not connected.
        public void AddForwardedPort(ForwardedPort port)
        {
            if (port == null)
                throw new ArgumentNullException("port");
            //  Ensure that connection is established.
            this.EnsureConnection();
            if (port.Session != null && port.Session != this.Session)
                throw new InvalidOperationException("Forwarded port is already added to a different client.");
            port.Session = this.Session;
            this._forwardedPorts.Add(port);
        }
        /// 
        /// Stops and removes the forwarded port from the list.
        /// 
        /// Forwarded port.
        ///  is null.
        public void RemoveForwardedPort(ForwardedPort port)
        {
            if (port == null)
                throw new ArgumentNullException("port");
            //  Stop port forwarding before removing it
            port.Stop();
            port.Session = null;
            this._forwardedPorts.Remove(port);
        }
        /// 
        /// Creates the command to be executed.
        /// 
        /// The command text.
        ///  object.
        /// Client is not connected.
        public SshCommand CreateCommand(string commandText)
        {
            return this.CreateCommand(commandText, Encoding.UTF8);
        }
        /// 
        /// Creates the command to be executed with specified encoding.
        /// 
        /// The command text.
        /// The encoding to use for results.
        ///  object which uses specified encoding.
        /// Client is not connected.
        ///  or  is null.
        public SshCommand CreateCommand(string commandText, Encoding encoding)
        {
            //  Ensure that connection is established.
            this.EnsureConnection();
            return new SshCommand(this.Session, commandText, encoding);
        }
        /// 
        /// Creates and executes the command.
        /// 
        /// The command text.
        /// Returns an instance of  with execution results.
        /// This method internally uses asynchronous calls.
        /// CommandText property is empty.
        /// Invalid Operation - An existing channel was used to execute this command.
        /// Asynchronous operation is already in progress.
        /// Client is not connected.
        ///  is null.
        public SshCommand RunCommand(string commandText)
        {
            var cmd = this.CreateCommand(commandText);
            cmd.Execute();
            return cmd;
        }
        /// 
        /// Creates the shell.
        /// 
        /// The input.
        /// The output.
        /// The extended output.
        /// Name of the terminal.
        /// The columns.
        /// The rows.
        /// The width.
        /// The height.
        /// The terminal mode.
        /// Size of the internal read buffer.
        /// Returns a representation of a  object.
        public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, string terminalMode, int bufferSize)
        {
            //  Ensure that connection is established.
            this.EnsureConnection();
            return new Shell(this.Session, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, bufferSize);
        }
        /// 
        /// Creates the shell.
        /// 
        /// The input.
        /// The output.
        /// The extended output.
        /// Name of the terminal.
        /// The columns.
        /// The rows.
        /// The width.
        /// The height.
        /// The terminal mode.
        /// Returns a representation of a  object.
        public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, string terminalMode)
        {
            return this.CreateShell(input, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, 1024);
        }
        /// 
        /// Creates the shell.
        /// 
        /// The input.
        /// The output.
        /// The extended output.
        /// Returns a representation of a  object.
        public Shell CreateShell(Stream input, Stream output, Stream extendedOutput)
        {
            return this.CreateShell(input, output, extendedOutput, string.Empty, 0, 0, 0, 0, string.Empty, 1024);
        }
        /// 
        /// Creates the shell.
        /// 
        /// The encoding to use to send the input.
        /// The input.
        /// The output.
        /// The extended output.
        /// Name of the terminal.
        /// The columns.
        /// The rows.
        /// The width.
        /// The height.
        /// The terminal mode.
        /// Size of the internal read buffer.
        /// Returns a representation of a  object.
        public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, string terminalMode, int bufferSize)
        {
            //  Ensure that connection is established.
            this.EnsureConnection();
            this._inputStream = new MemoryStream();
            var writer = new StreamWriter(this._inputStream, encoding);
            writer.Write(input);
            writer.Flush();
            this._inputStream.Seek(0, SeekOrigin.Begin);
            return this.CreateShell(this._inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, bufferSize);
        }
        /// 
        /// Creates the shell.
        /// 
        /// The encoding.
        /// The input.
        /// The output.
        /// The extended output.
        /// Name of the terminal.
        /// The columns.
        /// The rows.
        /// The width.
        /// The height.
        /// The terminal mode.
        /// Returns a representation of a  object.
        public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, string terminalMode)
        {
            return this.CreateShell(encoding, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, 1024);
        }
        /// 
        /// Creates the shell.
        /// 
        /// The encoding.
        /// The input.
        /// The output.
        /// The extended output.
        /// Returns a representation of a  object.
        public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput)
        {
            return this.CreateShell(encoding, input, output, extendedOutput, string.Empty, 0, 0, 0, 0, string.Empty, 1024);
        }
        /// 
        /// Creates the shell stream.
        /// 
        /// Name of the terminal.
        /// The columns.
        /// The rows.
        /// The width.
        /// The height.
        /// Size of the buffer.
        /// The terminal mode values.
        /// 
        public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, params KeyValuePair[] terminalModeValues)
        {
            //  Ensure that connection is established.
            this.EnsureConnection();
            return new ShellStream(this.Session, terminalName, columns, rows, width, height, bufferSize, terminalModeValues);
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged ResourceMessages.
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (this._disposeConnectionInfo)
                ((IDisposable)this.ConnectionInfo).Dispose();
            if (this._inputStream != null)
            {
                this._inputStream.Dispose();
                this._inputStream = null;
            }
        }
    }
}