using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using System.Diagnostics;
using System.Collections.Generic;
using System.Globalization;
using Renci.SshNet.Sftp.Responses;
using Renci.SshNet.Sftp.Requests;
using Renci.SshNet.Messages.Connection;
namespace Renci.SshNet.Sftp
{
    /// 
    /// Base class for SSH subsystem implementations
    /// 
    public abstract class SubsystemSession : IDisposable
    {
        private Session _session;
        private string _subsystemName;
        private ChannelSession _channel;
        private Exception _exception;
        private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
        private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
        /// 
        /// Specifies a timeout to wait for operation to complete
        /// 
        protected TimeSpan _operationTimeout;
        /// 
        /// Occurs when an error occurred.
        /// 
        public event EventHandler ErrorOccurred;
        /// 
        /// Occurs when session has been disconnected form the server.
        /// 
        public event EventHandler Disconnected;
        /// 
        /// Gets the channel number.
        /// 
        protected uint ChannelNumber { get; private set; }
        protected Encoding Encoding { get; private set; }
        /// 
        /// Initializes a new instance of the SubsystemSession class.
        /// 
        /// The session.
        /// Name of the subsystem.
        /// The operation timeout.
        /// session
        ///  or  is null.
        public SubsystemSession(Session session, string subsystemName, TimeSpan operationTimeout, Encoding encoding)
        {
            if (session == null)
                throw new ArgumentNullException("session");
            if (subsystemName == null)
                throw new ArgumentNullException("subsystemName");
            this._session = session;
            this._subsystemName = subsystemName;
            this._operationTimeout = operationTimeout;
            this.Encoding = encoding;
        }
        /// 
        /// Connects subsystem on SSH channel.
        /// 
        public void Connect()
        {
            this._channel = this._session.CreateChannel();
            this._session.ErrorOccured += Session_ErrorOccured;
            this._session.Disconnected += Session_Disconnected;
            this._channel.DataReceived += Channel_DataReceived;
            this._channel.Closed += Channel_Closed;
            this._channel.Open();
            this.ChannelNumber = this._channel.RemoteChannelNumber;
            this._channel.SendSubsystemRequest(_subsystemName);
            this.OnChannelOpen();
        }
        /// 
        /// Disconnects subsystem channel.
        /// 
        public void Disconnect()
        {
            this._channel.SendEof();
            this._channel.Close();
        }
        /// 
        /// Sends data to the subsystem.
        /// 
        /// The data to be sent.
        public void SendData(byte[] data)
        {
            this._channel.SendData(data);
        }
        /// 
        /// Called when channel is open.
        /// 
        protected abstract void OnChannelOpen();
        /// 
        /// Called when data is received.
        /// 
        /// The data type code.
        /// The data.
        protected abstract void OnDataReceived(uint dataTypeCode, byte[] data);
        /// 
        /// Raises the error.
        /// 
        /// The error.
        protected void RaiseError(Exception error)
        {
            this._exception = error;
            this._errorOccuredWaitHandle.Set();
            if (this.ErrorOccurred != null)
            {
                this.ErrorOccurred(this, new ExceptionEventArgs(error));
            }
        }
        private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e)
        {
            this.OnDataReceived(e.DataTypeCode, e.Data);
        }
        private void Channel_Closed(object sender, Common.ChannelEventArgs e)
        {
            this._channelClosedWaitHandle.Set();
        }
        internal void WaitHandle(WaitHandle waitHandle, TimeSpan operationTimeout)
        {
            var waitHandles = new WaitHandle[]
                {
                    this._errorOccuredWaitHandle,
                    this._channelClosedWaitHandle,
                    waitHandle,
                };
            switch (EventWaitHandle.WaitAny(waitHandles, operationTimeout))
            {
                case 0:
                    throw this._exception;
                case 1:
                    throw new SshException("Channel was closed.");
                case System.Threading.WaitHandle.WaitTimeout:
                    throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Operation has timed out."));
                default:
                    break;
            }
        }
        private void Session_Disconnected(object sender, EventArgs e)
        {
            if (this.Disconnected != null)
            {
                this.Disconnected(this, new EventArgs());
            }
            this.RaiseError(new SshException("Connection was lost"));
        }
        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
        {
            this.RaiseError(e.Exception);
        }
        #region IDisposable Members
        private bool _isDisposed = false;
        /// 
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._isDisposed)
            {
                if (this._channel != null)
                {
                    this._channel.DataReceived -= Channel_DataReceived;
                    this._channel.Dispose();
                    this._channel = null;
                }
                this._session.ErrorOccured -= Session_ErrorOccured;
                this._session.Disconnected -= Session_Disconnected;
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if (this._errorOccuredWaitHandle != null)
                    {
                        this._errorOccuredWaitHandle.Dispose();
                        this._errorOccuredWaitHandle = null;
                    }
                    if (this._channelClosedWaitHandle != null)
                    {
                        this._channelClosedWaitHandle.Dispose();
                        this._channelClosedWaitHandle = null;
                    }
                }
                // Note disposing has been done.
                _isDisposed = true;
            }
        }
        /// 
        /// Finalizes an instance of the  class.
        /// 
        ~SubsystemSession()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
        #endregion
    }
}