// SftpConnection.cs
//
//  Copyright (C) 2008-2009 Christian Eide
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

using System;
using bareFTP.Protocol;
using bareFTP.Preferences;

namespace bareFTP.Protocol.Sftp
{
	public class SftpConnection : IProtocol
	{
		// Read config
		Config conf;
		
		IDialogHost dhost;
		bool xferinprogress = false;
		private ConnectionProperties conn_props;
		private string cwd;
		private SftpClient client;
		private bool havessh = false;
		private System.Threading.EventWaitHandle ewh;
		
		public SftpConnection(ConnectionProperties conn_props)
		{
			this.conn_props = conn_props;
		}
		
		public ProtocolType ProtocolType
		{
			get { return ProtocolType.SFTP; }
		}
		
		public bool Connected {
			get 
			{
				try { return client.IsConnected; }
				catch { return false; }
			}
		}
		
		public bool TransferInProgress {
			get {
				return xferinprogress;
			}
			set {
				this.xferinprogress = value;
			}
		}
		
		public virtual ConnectionProperties ConnProperties
		{
			get { return conn_props; }
			set { conn_props = value; }
		}
		
		public void Init (IDialogHost host, Config config)
		{
			dhost = host;
			conf = config;
			havessh = CheckSSH(conf.SSHProgramPath);
		}
		
		public void Open()
		{
			if(conn_props.Port == 0)
				conn_props.Port = conf.SSH_DefaultPort;
			Open(conn_props.Hostname, conn_props.Port, conn_props.User, conn_props.Password);
		}
		public void Open (string remoteHost, string user, string password)
		{
			Open(remoteHost, conf.SSH_DefaultPort, user, password);
		}

		public void Open (string remoteHost, int remotePort, string user, string password)
		{
			
			
			int port = remotePort;
			if(port < 0)
				port = conf.SSH_DefaultPort;
			
			LogText(string.Format("Connecting to {0}", remoteHost));
			
			if(!havessh)
			{
				ewh = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset);
				Gtk.Application.Invoke(delegate {	
					dhost.DisplayDialogError(ewh, Mono.Unix.Catalog.GetString("SSH not found. Check your ssh program path!"));
				});
				ewh.WaitOne();
				throw new Exception("SSH not found. Check your ssh program path!");
			}
			
			try
			{
				client = new SftpClient(dhost);
				client.LogTextEmitted += OnClientFeedback;
				client.Open(remoteHost, user, password, port);
			}
			catch(Exception e)
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void Close ()
		{
			LogText(string.Format("Closing connection.."));
			
			client.Disconnect();
			client = null;
		}
		
		private void LogText(string msg)
		{
			OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Info, msg));
		}
		
		// TODO: Fix these names...
		protected void OnClientFeedback(object sender, EventArgs e)
		{
			OnLogTextEmitted(e as LogTextEmittedArgs);
		}

		public void SendCommand (string command)
		{
			throw new NotImplementedException();
		}
		
		public virtual bool IsDir(string cwd, string dir)
		{
			try
			{
				client.chdir(dir);
			}
			catch
			{
				return false;
			}
			client.chdir(client.getCurrentDirectory());
			return true;
		}

		public System.Collections.Generic.List<string> Dir ()
		{
			System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
			foreach(RemoteFile f in XDir())
			{
				list.Add(f.Filename);
			}
			return list;
		}

		public System.Collections.Generic.List<RemoteFile> XDir ()
		{
			
			try
			{
				return client.xdir();
			}
			catch(SftpException e)
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				SetCurrentDirectory("..");
				throw e;
			}
		}

		public void SendFile (XferFile file)
		{
			
			if(file.Status == DownloadStatus.Skipped || file.Status == DownloadStatus.Aborted)
				return;
			
			if(file.IsDir)
			{
				try
				{
					MakeDir(file.Path.RelativePathRemote);
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
			}
			else
			{
				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "PUT " + file.Path.FileNameRemoteAbs));
				
				try
				{
					using(System.IO.FileStream fs = new System.IO.FileStream(file.Path.FileNameLocalAbs, System.IO.FileMode.Open))
					{
						client.myput(file, file.Action, fs);
						fs.Close();
					}					
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
			}

			if(!string.IsNullOrEmpty(file.Permissions) && conf.General_PreserveFilePermissions)
			{
				if(file.Status == DownloadStatus.Failed)
					return;
				
				int mode;
				if(Int32.TryParse(file.Permissions, out mode))
					Chmod(mode.ToString(), file.Path.FileNameRemoteAbs);
			}
			
		}

		public void GetFile (XferFile file)
		{
			if(file.Status == DownloadStatus.Skipped || file.Status == DownloadStatus.Aborted)
			{
				return;
			}
			if(file.IsDir)
			{
				try
				{
					if(!System.IO.Directory.Exists(file.Path.FileNameLocalAbs))
						System.IO.Directory.CreateDirectory(file.Path.FileNameLocalAbs);
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
				
			}
			else
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "RETR " + file.Path.FileNameRemoteAbs));
				
				try
				{
					System.IO.FileMode fmode;
					if(file.Action == FileAction.Resume || file.Action == FileAction.Append)
						fmode = System.IO.FileMode.Append;
					else
						fmode = System.IO.FileMode.Create;
					
					using(System.IO.FileStream fs = new System.IO.FileStream(file.Path.FileNameLocalAbs, fmode))
					{
						client.myget(file, file.Action, fs);
						fs.Close();
					}
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
			}

			if(!string.IsNullOrEmpty(file.Permissions) && conf.General_PreserveFilePermissions)
			{
				if(file.Status == DownloadStatus.Failed)
					return;
				
				bareFTP.Common.Utils.LocalChmod.Chmod(file.Path.FileNameLocalAbs, file.Permissions);
			}			
		}

		public void DeleteFile (string remoteFileName)
		{
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "RM " + remoteFileName));
				client.rm(remoteFileName);
			}
			catch(SftpException e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void MoveFile (string remoteFileName, string toRemotePath)
		{
			throw new NotImplementedException();
		}

		public void RenameFile (string fromRemoteFileName, string toRemoteFileName)
		{
			
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "RENAME " + fromRemoteFileName + " -> " + toRemoteFileName));
				client.rename(fromRemoteFileName,toRemoteFileName);
			}
			catch(SftpException e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
			
		}

		public void SetCurrentDirectory (string remotePath)
		{
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "CD " + remotePath));
				client.chdir(remotePath);
			}
			catch(Exception e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				throw new SftpException(e.Message);
			}
		}

		public string GetCurrentDirectory ()
		{
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "PWD"));
				string pwd = client.getCurrentDirectory();
				this.cwd = pwd;
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Info, "Remote dir is " + pwd));
				return pwd;
			}
			catch(SftpException e)
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				return null;
			}
		}

		public void MakeDir (string directoryName)
		{
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "MKDIR " + directoryName));
				client.mkdir(directoryName);
			}
			catch(SftpException e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void RemoveDir (string directoryName)
		{
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "RMDIR " + directoryName));
				client.rmdir(directoryName);
			}
			catch(SftpException e)
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void Chmod (string mode, string filename)
		{
			
			// SSH implementation needs the mode in octal
			try
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.ClientCommand, "CHMOD " + mode + " " + filename));
				client.chmod(Convert.ToInt32(mode,8), filename);
			}
			catch(SftpException e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void Abort ()
		{
			lock(client)
			{
				client.Abort();
			}
			System.Threading.Thread.Sleep(500);
			Close();
			System.Threading.Thread.Sleep(200);
			Open();
		}
		
		public virtual string CurrentDirectory
		{
			get { return cwd; }
		}
		
		public event EventHandler LostConnection;
		
		public event EventHandler LogTextEmitted;
		public virtual void OnLogTextEmitted(LogTextEmittedArgs e)
		{
			LogTextEmitted(this, e);
		}
		
		private bool CheckSSH(string path)
		{
			try
			{
				
				System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(path, "-V");
				psi.RedirectStandardError = true;
				psi.RedirectStandardOutput = true;
				psi.UseShellExecute = false;
				System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
				p.WaitForExit();
				
				string so = p.StandardOutput.ReadToEnd();
				string se = p.StandardError.ReadToEnd();
				
				if(so.ToLower().Contains("openssh") || se.ToLower().Contains("openssh"))
				{
					return true;
				}
			}
			catch(Exception ex)
			{
				Console.WriteLine(ex.Message);
			}
			
			return false;
		}
	}
}
