using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;


namespace AR.FTPClient
{
	public partial class FTPClient : IFTPClient
	{
		public string errorMessage { get; set; }

		private int bufferSize = 2048;
		private int timeout = 20000;

		#region "Contstruct"

		public FTPClient() : this("127.0.0.1", "Anonymous", "") { }

		public FTPClient(string Host, string UserID, string UserPass) :
			this(Host, UserID, UserPass, 21)
		{ }

		public FTPClient(string Host, string UserID, string UserPass, bool passive) :
			this(Host, UserID, UserPass, 21, passive)
		{ }

		public FTPClient(string Host, string UserID, string UserPass, int port) :
			this(Host, UserID, UserPass, port, false)
		{ }

		public FTPClient(string host, string userid, string userpass, int port, bool passive)
		{
			Host = host;
			UserID = userid;
			UserPassword = userpass;
			Port = port;
			PassiveMode = passive;
			this.TextEncoding = System.Text.Encoding.UTF8;
		}
		#endregion

		public event EventHandler<MessageEventArgs> Message;
		public event EventHandler<ListProgressEventArgs> ListPrgress;

		#region "Properties"

		/// <summary>
		/// 접속하려는 FTP서버의 IP혹은 도메인 주소를 입력하세요
		/// 기본값 : 127.0.0.1
		/// </summary>
		public string Host { get; set; }
		/// <summary>
		/// FTP의 사용자 ID
		/// 기본값 : Anonymous
		/// </summary>
		public string UserID { get; set; }
		/// <summary>
		/// FTP 사용자 ID의 암호
		/// 기본값 : 없음
		/// </summary>
		public string UserPassword { get; set; }
		/// <summary>
		/// FTP 접속 포트
		/// 기본값 : 21
		/// </summary>
		public int Port { get; set; }
		/// <summary>
		/// FTP 접속 방식 (능동형/수동형)
		/// </summary>
		public bool PassiveMode { get; set; }

		/// <summary>
		/// 문자수신시 사용할 인코딩 방식 
		/// </summary>
		public System.Text.Encoding TextEncoding { get; set; }

		#endregion
		public void Dispose()
		{

		}
		/// <summary>
		/// 파일을 다운로드 합니다.
		/// </summary>
		/// <param name="remoteFile">원격위치의 전체 경로</param>
		/// <param name="localFile">로컬위치의 전체 경로</param>
		public Boolean Download(string remoteFile, string localFile, bool overwrite = false)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;

			//overwrite funcion - 190622 - chi
			errorMessage = string.Empty;
			if (overwrite == false && System.IO.File.Exists(localFile))
			{
				errorMessage = "Target file alreay exists";
				return false;
			}
			else if (overwrite == true && System.IO.File.Exists(localFile))
			{
				try
				{
					System.IO.File.Delete(localFile);
				}
				catch (Exception ex)
				{
					errorMessage = ex.Message;
					return false;
				}
			}



			bool retval = true;
			try
			{
				long Receive = 0;
				var url = CombineUrl(remoteFile);

				ftpRequest = (FtpWebRequest)FtpWebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(this.UserID, this.UserPassword);
				ftpRequest.UseBinary = true;
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpStream = ftpResponse.GetResponseStream();
				FileStream localFileStream = new FileStream(localFile, FileMode.Create);
				byte[] byteBuffer = new byte[bufferSize];
				int bytesRead = ftpStream.Read(byteBuffer, 0, bufferSize);
				Receive += bytesRead;
				if (ListPrgress != null)
					ListPrgress(this, new ListProgressEventArgs(Receive));
				try
				{
					while (bytesRead > 0)
					{
						localFileStream.Write(byteBuffer, 0, bytesRead);
						bytesRead = ftpStream.Read(byteBuffer, 0, bufferSize);
						Receive += bytesRead;
						if (ListPrgress != null)
							ListPrgress(this, new ListProgressEventArgs(Receive));
					}
				}
				catch (Exception ex) { retval = false; Console.WriteLine(ex.ToString()); }
				localFileStream.Close();
				ftpStream.Close();
				ftpResponse.Close();
				ftpRequest = null;
			}
			catch (Exception ex) { retval = false; this.errorMessage = ex.Message; Console.WriteLine(ex.ToString()); }
			return retval;
		}

		/// <summary>
		/// 파일을 업로드 합니다.
		/// </summary>
		/// <param name="remoteFile">원격위치의 전체 경로</param>
		/// <param name="localFile">로컬위치의 전체 경로</param>
		/// <returns></returns>
		public Boolean Upload(string remoteFile, string localFile)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(remoteFile);
				ftpRequest = (FtpWebRequest)FtpWebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UseBinary = true;
				ftpRequest.UsePassive = PassiveMode;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.UploadFile;
				ftpStream = ftpRequest.GetRequestStream();
				FileStream localFileStream = new FileStream(localFile, FileMode.Open);
				byte[] byteBuffer = new byte[bufferSize];
				int bytesSent = localFileStream.Read(byteBuffer, 0, bufferSize);
				Boolean bError = false;
				try
				{
					System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
					wat.Restart();
					while (bytesSent != 0)
					{
						ftpStream.Write(byteBuffer, 0, bytesSent);
						bytesSent = localFileStream.Read(byteBuffer, 0, bufferSize);
					}

				}
				catch (Exception ex)
				{
					bError = true;
				}
				localFileStream.Close();
				ftpStream.Close();
				ftpRequest = null;
				if (bError) return false;
				else return true;
			}
			catch (Exception ex)
			{
				this.errorMessage = ex.Message;
				return false;
			}
		}


		/// <summary>
		/// 원격파일을 삭제 합니다.
		/// </summary>
		/// <param name="remotefile"></param>
		public Boolean Delete(string remotefile)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(remotefile);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UseBinary = true;
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.DeleteFile;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpResponse.Close();
				ftpRequest = null;
				return true;
			}
			catch (Exception ex) { this.errorMessage = ex.Message; return false; }
		}

		/// <summary>
		/// 원격파일의 이름을 변경 합니다.
		/// </summary>
		/// <param name="currentFileNameAndPath"></param>
		/// <param name="newFileName"></param>
		public Boolean rename(string currentFileNameAndPath, string newFileName)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(currentFileNameAndPath);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.UseBinary = true;
				ftpRequest.KeepAlive = true;
				ftpRequest.Method = WebRequestMethods.Ftp.Rename;
				ftpRequest.RenameTo = newFileName;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpResponse.Close();
				ftpRequest = null;
				return true;
			}
			catch (Exception ex) { this.errorMessage = ex.Message; return false; }
		}

		/// <summary>
		/// 원격위치에 폴더를 생성 합니다.
		/// 트리구조로 폴더를 생성하지 않습니다. 여러개의 폴더를 생성하려면 각각 호출 해야 합니다.
		/// </summary>
		/// <param name="newDirectory"></param>
		/// <returns></returns>
		public bool createDirectory(string newDirectory)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(newDirectory, false);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.UseBinary = true;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.MakeDirectory;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpResponse.Close();
				ftpRequest = null;
				return true;
			}
			catch (Exception ex) { this.errorMessage = ex.Message; return false; }
		}

		/// <summary>
		/// 원격위치의 폴더를 삭제합니다. 폴더 삭제 전 대상 폴더는 비어있어야 합니다.
		/// </summary>
		/// <param name="Directory"></param>
		/// <returns></returns>
		public bool RemoveDirectory(string Directory)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(Directory, false);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.UseBinary = true;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.RemoveDirectory;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpResponse.Close();
				ftpRequest = null;
				return true;
			}
			catch (Exception ex) { this.errorMessage = ex.Message; return false; }
		}

		/// <summary>
		/// 파일의 생성일자 반환
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public string getFileCreatedDateTime(string fileName)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(fileName);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.UseBinary = true;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.GetDateTimestamp;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpStream = ftpResponse.GetResponseStream();
				StreamReader ftpReader = new StreamReader(ftpStream, this.TextEncoding);
				string fileInfo = null;
				try { fileInfo = ftpReader.ReadToEnd(); }
				catch (Exception ex) { Console.WriteLine(ex.ToString()); }
				ftpReader.Close();
				ftpStream.Close();
				ftpResponse.Close();
				ftpRequest = null;
				return fileInfo;
			}
			catch (Exception ex) { this.errorMessage = ex.Message; Console.WriteLine(ex.ToString()); }
			return "";
		}


		/// <summary>
		/// 파일의 크기를 반환
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public string getFileSize(string fileName)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			try
			{
				var url = CombineUrl(fileName);
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UseBinary = true;
				ftpRequest.UsePassive = this.PassiveMode;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.Method = WebRequestMethods.Ftp.GetFileSize;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpStream = ftpResponse.GetResponseStream();
				StreamReader ftpReader = new StreamReader(ftpStream, this.TextEncoding);
				string fileInfo = null;
				try { while (ftpReader.Peek() != -1) { fileInfo = ftpReader.ReadToEnd(); } }
				catch (Exception ex) { Console.WriteLine(ex.ToString()); }
				ftpReader.Close();
				ftpStream.Close();
				ftpResponse.Close();
				ftpRequest = null;
				return fileInfo;
			}
			catch (Exception ex) { errorMessage = ex.Message; Console.WriteLine(ex.ToString()); }
			return "";
		}


		/// <summary>
		/// 폴더와 파일의 이름만 반환 합니다.
		/// </summary>
		/// <param name="directory"></param>
		/// <returns></returns>
		public string[] directoryListSimple(string directory)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			errorMessage = string.Empty;
			try
			{
				var url = CombineUrl(directory, false);
				if (url.EndsWith("/") == false) url += "/";
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UseBinary = true;
				ftpRequest.UsePassive = PassiveMode;
				ftpRequest.KeepAlive = true;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.ReadWriteTimeout = 30000;
				ftpRequest.Timeout = 30000;
				ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory;

				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpStream = ftpResponse.GetResponseStream();
				StreamReader ftpReader = new StreamReader(ftpStream, this.TextEncoding);
				string directoryRaw = null;
				try
				{
					while (ftpReader.Peek() != -1)
					{
						var dataLIne = ftpReader.ReadLine();
						if (dataLIne.Trim() != "")
						{
							if (directoryRaw != null && directoryRaw != "") directoryRaw += "|";
							directoryRaw += dataLIne;
						}
					}
				}
				catch (Exception ex) { errorMessage += "\n" + ex.Message; }
				ftpReader.Close();
				ftpStream.Close();
				ftpResponse.Close();
				ftpRequest = null;
				try { string[] directoryList = directoryRaw.Split("|".ToCharArray()); return directoryList; }
				catch (Exception ex) { errorMessage += "\n" + ex.Message; }
			}
			catch (Exception ex) { errorMessage = ex.Message; Console.WriteLine(ex.ToString()); }
			return new string[] { "" };
		}


		/// <summary>
		/// 폴더 및 파일의 정보를 상세하게 표시합니다.
		/// </summary>
		/// <param name="directory"></param>
		/// <returns></returns>
		public FTPdirectory ListDirectoryDetail(string directory)
		{
			FtpWebRequest ftpRequest = null;
			FtpWebResponse ftpResponse = null;
			Stream ftpStream = null;
			errorMessage = string.Empty;
			try
			{
				var url = CombineUrl(directory, false);

				if (url.EndsWith("/") == false) url += "/";
				ftpRequest = (FtpWebRequest)WebRequest.Create(url);
				ftpRequest.Credentials = new NetworkCredential(UserID, UserPassword);
				ftpRequest.UsePassive = true;
				ftpRequest.UseBinary = true;
				ftpRequest.KeepAlive = false;
				ftpRequest.ReadWriteTimeout = this.timeout;
				ftpRequest.ReadWriteTimeout = 30000;
				ftpRequest.Timeout = 30000;
				ftpRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
				ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
				ftpStream = ftpResponse.GetResponseStream();

				StreamReader ftpReader = new StreamReader(ftpStream, this.TextEncoding);
				string directoryRaw = null;

				try { while (ftpReader.Peek() != -1) { directoryRaw += ftpReader.ReadLine() + "\r"; } }
				catch (Exception ex) { errorMessage += "\n" + ex.Message; }
				ftpReader.Close();
				ftpStream.Close();
				ftpResponse.Close();
				ftpRequest = null;
				directoryRaw = directoryRaw.Replace("\r\n", "\r").TrimEnd((char)0x0D);
				return new FTPdirectory(directoryRaw, directory);
			}
			catch (Exception ex) { errorMessage += "\n" + ex.Message; }
			return null;
		}

		#region "utillity"

		/// <summary>
		/// Url을 Host와 결합하여 생성
		/// </summary>
		/// <param name="file">경로명</param>
		/// <param name="isfile">파일인지 여부</param>
		/// <returns></returns>
		string CombineUrl(string file, bool isfile = true)
		{
			file = file.Replace("\\", "/");
			string url = this.Host;
			if (this.Host.ToLower().StartsWith("ftp://") == false) url = "ftp://" + url;
			if (url.Substring(5).LastIndexOf(':') == -1)
				url += ":" + this.Port.ToString();

			if (this.Host.EndsWith("/")) url = this.Host.Substring(0, Host.Length - 1);

			if (file.StartsWith("/"))
				url = url + System.Web.HttpUtility.UrlPathEncode(file);
			else
				url = url + "/" + System.Web.HttpUtility.UrlPathEncode(file);

			url = url.Replace("#", "%23");

			if (isfile)
				return url;
			else
				return url + "/";
		}
		public string PathCombine(string path, string add)
		{
			var newpath = string.Empty;
			if (path.EndsWith("/")) newpath = path + add;
			else newpath = path + "/" + add;
			if (!newpath.EndsWith("/")) newpath += '/';
			return newpath;

		}
		public string PathFileCombine(string path, string add)
		{
			var newpath = string.Empty;
			if (path.EndsWith("/")) newpath = path + add;
			else newpath = path + "/" + add;
			if (newpath.EndsWith("/")) newpath = newpath.Substring(0, newpath.Length - 1);
			return newpath;

		}
		public string getParent(string path)
		{
			if (path == "/") return path;
			else if (path == "") return "/";
			else
			{
				//서브폴더를 찾아서 처리해준다.
				if (path.IndexOf('/') == -1) return "/";
				else
				{
					if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
					var slashindex = path.LastIndexOf('/');
					var parent = path.Substring(0, slashindex);
					if (!parent.EndsWith("/")) parent += '/';
					return parent;
				}
			}
		}

		public void RaiseMessage(Boolean isError, string message)
		{
			if (isError) errorMessage = message; //170920
			if (Message != null) Message(this, new MessageEventArgs(isError, message));
		}
		#endregion
	}
}
