using System;
using System.Collections.Generic;
using System.IO;
using System.Collections.ObjectModel;
using System.Text;
namespace Renci.SshNet
{
    /// 
    /// Provides client connection to SSH server.
    /// 
    public class SshClient : BaseClient
    {
        /// 
        /// Holds the list of forwarded ports
        /// 
        private List _forwardedPorts = new List();
        /// 
        /// 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 .
        public SshClient(string host, int port, string username, string password)
            : this(new PasswordConnectionInfo(host, port, username, password))
        {
        }
        /// 
        /// 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 .
        public SshClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
        {
        }
        /// 
        /// 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 forwarded port to the list.
        /// 
        /// Type of forwarded port to add
        /// The bound host.
        /// The bound port.
        /// The connected host.
        /// The connected port.
        /// 
        /// Forwarded port
        /// 
        ///  or  is null.
        ///  or  is invalid.
        ///  or  is not within  and .
        /// Client is not connected.
        public T AddForwardedPort(string boundHost, uint boundPort, string connectedHost, uint connectedPort) where T : ForwardedPort, new()
        {            
            if (boundHost == null)
                throw new ArgumentNullException("boundHost");
            if (connectedHost == null)
                throw new ArgumentNullException("connectedHost");
            if (!boundHost.IsValidHost())
                throw new ArgumentException("boundHost");
            if (!boundPort.IsValidPort())
                throw new ArgumentOutOfRangeException("boundPort");
            if (!connectedHost.IsValidHost())
                throw new ArgumentException("connectedHost");
            if (!connectedPort.IsValidPort())
                throw new ArgumentOutOfRangeException("connectedPort");
            //  Ensure that connection is established.
            this.EnsureConnection();
            T port = new T();
            port.Session = this.Session;
            port.BoundHost = boundHost;
            port.BoundPort = boundPort;
            port.Host = connectedHost;
            port.Port = connectedPort;
            this._forwardedPorts.Add(port);
            return port;
        }
        /// 
        /// Adds forwarded port to the list bound to "localhost".
        /// 
        /// Type of forwarded port to add
        /// The bound port.
        /// The connected host.
        /// The connected port.
        /// 
        ///  is null.
        /// ,  or  is invalid.
        ///  or  is not within  and .
        /// Client is not connected.
        public T AddForwardedPort(uint boundPort, string connectedHost, uint connectedPort) where T : ForwardedPort, new()
        {            
            return this.AddForwardedPort("localhost", boundPort, connectedHost, connectedPort);
        }
        /// 
        /// 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();
            this._forwardedPorts.Remove(port);
        }
        /// 
        /// Creates the command to be executed.
        /// 
        /// The command text.
        ///  object.
        public SshCommand CreateCommand(string commandText)
        {
            return this.CreateCommand(commandText, Renci.SshNet.Common.ASCIIEncoding.Current);
        }
        /// 
        /// Creates the command to be executed with specified encoding.
        /// 
        /// The command text.
        /// The encoding to use for results.
        ///  object which uses specified encoding.
        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.
        /// 
        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.
        /// 
        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.
        /// 
        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.
        /// 
        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.
        /// 
        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();
            var inputStream = new MemoryStream();
            var writer = new StreamWriter(inputStream, encoding);
            writer.Write(input);
            writer.Flush();
            inputStream.Seek(0, SeekOrigin.Begin);
            return this.CreateShell(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.
        /// 
        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.
        /// 
        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);
        }
    }
}