using System;
using System.IO;
using System.Data;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using IMLibrary.NetProtocol;

namespace  IMLibrary.FTPLibrary
{
	#region Event delegates

	/// <summary>
	/// Represents the method that will handle the AuthUser event for FTP_Server.
	/// </summary>
	/// <param name="sender">The source of the event. </param>
	/// <param name="e">A AuthUser_EventArgs that contains the event data.</param>
	public delegate void AuthUserEventHandler(object sender,AuthUser_EventArgs e);


	/// <summary>
	/// Represents the method that will handle the filsystem rerlated events for FTP_Server.
	/// </summary>
	public delegate void FileSysEntryEventHandler(object sender,FileSysEntry_EventArgs e);
    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="fileName"></param>
    public delegate void FileSizeHandler(object sender, string  fileName);

    #endregion

    /// <summary>
    /// FTP Server component.
    /// </summary>
    public class FTP_Server  : System.ComponentModel.Component
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		private TcpListener FTP_Listener   = null;		
		private Hashtable   m_SessionTable = null;
				
		private string m_IPAddress          = "ALL";  // Holds IP Address, which to listen incoming calls.
		private int    m_port               = 21;     // Holds port number, which to listen incoming calls.
		private int    m_MaxThreads         = 100;    // Holds maximum allowed Worker Threads (Users).
		private bool   m_enabled            = false;  // If true listens incoming calls.
		private bool   m_LogCmds            = false;  // If true, writes POP3 commands to log file.
		private int    m_SessionIdleTimeOut = 800000; // Holds session idle timeout.
		private int    m_CommandIdleTimeOut = 60000;  // Holds command ilde timeout.
		private int    m_MaxBadCommands     = 30;     // Holds maximum bad commands allowed to session.

		#region Event declarations

		/// <summary>
		/// Occurs when new computer connected to FTP server.
		/// </summary>
		public event ValidateIPHandler ValidateIPAddress = null;

		/// <summary>
		/// Occurs when connected user tryes to authenticate.
		/// </summary>
		public event AuthUserEventHandler AuthUser = null;

		/// <summary>
		/// Occurs when server needs directory info (directories,files in deirectory).
		/// </summary>
		public event FileSysEntryEventHandler GetDirInfo = null;

		/// <summary>
		/// Occurs when server needs to validatee directory.
		/// </summary>
		public event FileSysEntryEventHandler DirExists = null;

		/// <summary>
		/// Occurs when server needs needs to create directory.
		/// </summary>
		public event FileSysEntryEventHandler CreateDir = null;

		/// <summary>
		/// Occurs when server needs needs to delete directory.
		/// </summary>
		public event FileSysEntryEventHandler DeleteDir = null;

		/// <summary>
		/// Occurs when server needs needs validate file.
		/// </summary>
		public event FileSysEntryEventHandler FileExists = null;

		/// <summary>
		/// Occurs when server needs needs to store file.
		/// </summary>
		public event FileSysEntryEventHandler StoreFile = null;

		/// <summary>
		/// Occurs when server needs needs to get file.
		/// </summary>
		public event FileSysEntryEventHandler GetFile = null;

        /// <summary>
        /// 
        /// </summary>
        public event FileSysEntryEventHandler GetFileSize = null;


        /// <summary>
        /// Occurs when server needs needs to delete file.
        /// </summary>
        public event FileSysEntryEventHandler DeleteFile = null;

		/// <summary>
		/// Occurs when server needs needs to rname directory or file.
		/// </summary>
		public event FileSysEntryEventHandler RenameDirFile = null;

		/// <summary>
		/// Occurs when server has system error(Unknown error).
		/// </summary>
		public event IMLibrary.NetProtocol.ErrorEventHandler SysError = null;

		/// <summary>
		/// Occurs when POP3 session has finished and session log is available.
		/// </summary>
		public event LogEventHandler SessionLog = null;

		#endregion


		#region Constructors

		/// <summary>
		/// 
		/// </summary>
		/// <param name="container"></param>
		public FTP_Server(System.ComponentModel.IContainer container)
		{			
			// Required for Windows.Forms Class Composition Designer support
			container.Add(this);
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		/// <summary>
		/// 
		/// </summary>
		public FTP_Server()
		{			
			// Required for Windows.Forms Class Composition Designer support
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		#endregion

		#region function Dispose

		/// <summary>
		/// Clean up any resources being used and STOPs POP3 server.
		/// </summary>
		public new void Dispose()
		{
			base.Dispose();

			Stop();				
		}

		#endregion
		
		#region Component Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion


		#region function Start

		/// <summary>
		/// Starts  Server.
		/// </summary>
		private void Start()
		{
			try{
				if(!m_enabled && !this.DesignMode){
					m_SessionTable = new Hashtable();

					Thread server = new Thread(new ThreadStart(Run));
                    server.Start();
				}
			}
			catch(Exception x){
				OnSysError(x,new System.Diagnostics.StackTrace());
			}
		}

		#endregion

		#region function Stop

		/// <summary>
		/// Stops  Server.
		/// </summary>
		private void Stop()
		{
			try{	
				if(FTP_Listener != null){
					FTP_Listener.Stop();
				}
			}
			catch(Exception x){
				OnSysError(x,new System.Diagnostics.StackTrace());
			}	
		}

		#endregion


		#region function Run

		/// <summary>
		/// Starts server message loop.
		/// </summary>
		private void Run()
		{		
			try
			{				
				// check which ip's to listen (all or assigned)
				if(m_IPAddress.ToLower().IndexOf("all") > -1){
					FTP_Listener = new TcpListener(IPAddress.Any,m_port);
				}
				else{
					FTP_Listener = new TcpListener(IPAddress.Parse(m_IPAddress),m_port);
				}

				// Start listening
				FTP_Listener.Start();

				while(true){
					// Check if maximum allowed thread count isn't exceeded
					if(m_SessionTable.Count < m_MaxThreads){

						// Thread is sleeping, until a client connects
						Socket clientSocket = FTP_Listener.AcceptSocket();

						string sessionID = clientSocket.GetHashCode().ToString();

						//****
						_LogWriter  logWriter = new _LogWriter(this.SessionLog);
						FTP_Session session   = new FTP_Session(clientSocket,this,sessionID,logWriter);

						Thread clientThread = new Thread(new ThreadStart(session.StartProcessing));

						// Add session to session list
						AddSession(sessionID,session,logWriter);

						// Start proccessing
						clientThread.Start();									
					}
					else{
						Thread.Sleep(100);
					}
				}
			}
			catch(ThreadInterruptedException e){
				string dummy = e.Message;     // Neede for to remove compile warning
				Thread.CurrentThread.Abort();
			}
			catch(Exception x){
				if(x.Message != "A blocking operation was interrupted by a call to WSACancelBlockingCall"){
					OnSysError(x,new System.Diagnostics.StackTrace());
				}
			}
		}

		#endregion


		#region Session handling stuff

		#region function AddSession

		/// <summary>
		/// Adds session.
		/// </summary>
		/// <param name="sessionID">Session ID.</param>
		/// <param name="session">Session object.</param>
		/// <param name="logWriter">Log writer.</param>
		internal void AddSession(string sessionID,FTP_Session session,_LogWriter logWriter)
		{
			m_SessionTable.Add(sessionID,session);

			if(m_LogCmds){
				logWriter.AddEntry("//----- Sys: 'Session:'" + sessionID + " added" + DateTime.Now);				
			}
		}

		#endregion

		#region function RemoveSession

		/// <summary>
		/// Removes session.
		/// </summary>
		/// <param name="session">Session which to remove.</param>
		/// <param name="logWriter">Log writer.</param>
		internal void RemoveSession(FTP_Session session,_LogWriter logWriter)
		{
			lock(m_SessionTable){
				if(!m_SessionTable.Contains(session.SessionID)){
					OnSysError(new Exception("Session '" + session.SessionID + "' doesn't exist."),new System.Diagnostics.StackTrace());
					return;
				}
				m_SessionTable.Remove(session.SessionID);
			
				// Raise session end event
		//		OnSessionEnd(session);
			}

			if(m_LogCmds){
				logWriter.AddEntry("//----- Sys: 'Session:'" + session.SessionID + " removed" + DateTime.Now);
			}
		}

		#endregion
		
		#endregion


		#region Properties implementation

		/// <summary>
		/// Gets or sets whick IP address to listen.
		/// </summary>
		[		
		Description("IP Address to Listen FTP requests"),
		DefaultValue("ALL"),
		]
		public string IpAddress 
		{
			get{ return m_IPAddress; }

			set{ m_IPAddress = value; }
		}


		/// <summary>
		/// Gets or sets which port to listen.
		/// </summary>
		[		
		Description("Port to use for FTP"),
		DefaultValue(21),
		]
		public int Port 
		{
			get{ return m_port;	}

			set{ m_port = value; }
		}


		/// <summary>
		/// Gets or sets maximum session threads.
		/// </summary>
		[		
		Description("Maximum Allowed threads"),
		DefaultValue(100),
		]
		public int Threads 
		{
			get{ return m_MaxThreads; }

			set{ m_MaxThreads = value; }
		}


		/// <summary>
		/// Runs or stops server.
		/// </summary>
		[		
		Description("Use this property to run and stop FTP Server"),
		DefaultValue(false),
		]
		public bool Enabled 
		{
			get{ return m_enabled; }

			set{				
				if(value != m_enabled){
					if(value){
						Start();
					}
					else{
						Stop();
					}

					m_enabled = value;
				}
			}
		}
		
		/// <summary>
		/// Gets or sets if to log commands.
		/// </summary>
		public bool LogCommands
		{
			get{ return m_LogCmds; }

			set{ m_LogCmds = value; }
		}

		/// <summary>
		/// Session idle timeout.
		/// </summary>
		public int SessionIdleTimeOut 
		{
			get{ return m_SessionIdleTimeOut; }

			set{ m_SessionIdleTimeOut = value; }
		}

		/// <summary>
		/// Command idle timeout.
		/// </summary>
		public int CommandIdleTimeOut 
		{
			get{ return m_CommandIdleTimeOut; }

			set{ m_CommandIdleTimeOut = value; }
		}

		/// <summary>
		/// Gets or sets maximum bad commands allowed to session.
		/// </summary>
		public int MaxBadCommands
		{
			get{ return m_MaxBadCommands; }

			set{ m_MaxBadCommands = value; }
		}

        #endregion

        #region Events Implementation

        internal virtual long OnGetFileSize(string fileName)
        {
            FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(fileName, "");
            if (this.GetFileSize != null)
            {
                this.GetFileSize(this, oArg);
            }
            return oArg.Length;
        }

        #region function OnValidate_IpAddress

        /// <summary>
        /// Raises event ValidateIP.
        /// </summary>
        /// <param name="endpoint">Connected host EndPoint.</param>
        /// <returns></returns>
        internal virtual bool OnValidate_IpAddress(EndPoint endpoint) 
		{			
			ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(endpoint);
			if(this.ValidateIPAddress != null){
				this.ValidateIPAddress(this, oArg);
			}

			return oArg.Validated;						
		}

		#endregion

		#region function OnAuthUser

		/// <summary>
		/// Authenticates user.
		/// </summary>
		/// <param name="session">Reference to current pop3 session.</param>
		/// <param name="userName">User name.</param>
		/// <param name="passwData"></param>
		/// <param name="data"></param>
		/// <param name="authType"></param>
		/// <returns></returns>
		internal virtual bool OnAuthUser(FTP_Session session,string userName,string passwData,string data,AuthType authType) 
		{				
			AuthUser_EventArgs oArg = new AuthUser_EventArgs(session,userName,passwData,data,authType);
			if(this.AuthUser != null){
				this.AuthUser(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion


		#region method OnGetDirInfo

		internal FileSysEntry_EventArgs OnGetDirInfo(string dir)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(dir,"");
			if(this.GetDirInfo != null){
				this.GetDirInfo(this,oArg);
			}
			return oArg;
		}

		#endregion

		#region method OnDirExists

		internal bool OnDirExists(string dir)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(dir,"");
			if(this.DirExists != null){
				this.DirExists(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion

		#region method OnCreateDir

		internal bool OnCreateDir(string dir)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(dir,"");
			if(this.CreateDir != null){
				this.CreateDir(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion

		#region method OnDeleteDir

		internal bool OnDeleteDir(string dir)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(dir,"");
			if(this.DeleteDir != null){
				this.DeleteDir(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion
		
		#region method OnRenameDirFile

		internal bool OnRenameDirFile(string from,string to)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(from,to);
			if(this.RenameDirFile != null){
				this.RenameDirFile(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion


		#region method OnFileExists

		internal bool OnFileExists(string file)
		{
			// Remove last /
			file = file.Substring(0,file.Length - 1);

			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(file,"");
			if(this.FileExists != null){
				this.FileExists(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion

		#region method OnGetFile

		internal Stream OnGetFile(string file)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(file,"");
			if(this.GetFile != null){
				this.GetFile(this,oArg);
			}
			
			return oArg.FileStream;
		}

		#endregion

		#region method OnStoreFile

		internal Stream OnStoreFile(string file)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(file,"");
			if(this.StoreFile != null){
				this.StoreFile(this,oArg);
			}
			
			return oArg.FileStream;
		}

		#endregion

		#region method OnDeleteFile

		internal bool OnDeleteFile(string file)
		{
			FileSysEntry_EventArgs oArg = new FileSysEntry_EventArgs(file,"");
			if(this.DeleteFile != null){
				this.DeleteFile(this,oArg);
			}
			
			return oArg.Validated;
		}

		#endregion


		#region function OnSysError

		/// <summary>
		/// Raises SysError event.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="stackTrace"></param>
		internal void OnSysError(Exception x,StackTrace stackTrace)
		{
			if(this.SysError != null){
				this.SysError(this,new Error_EventArgs(x,stackTrace));
			}
		}

		#endregion
	
		#endregion
	}
}
