// 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.Protocol.Sftp;
using bareFTP.Preferences;

namespace bareFTP.Protocol.Sftp
{
	public class SftpConnection : IProtocol
	{
		// Read config
		Config conf;
		
		Session session;
		
		Channel channel;
		ChannelSftp c;
		IDialogHost dhost;
		bool xferinprogress = false;
		private ConnectionProperties conn_props;
		private string cwd;
		
		public SftpConnection(ConnectionProperties conn_props)
		{
			this.conn_props = conn_props;
		}
		
		public bool Connected {
			get 
			{
				try
				{
					return c.isConnected();
				}
				catch
				{
					return false;
				}
			}
		}
		
		public bool TransferInProgress {
			get {
				return xferinprogress;
			}
		}
		
		public virtual ConnectionProperties ConnProperties
		{
			get { return conn_props; }
			set { conn_props = value; }
		}
		
		public void Init (IDialogHost host, Config config)
		{
			dhost = host;
			this.conf = config;
		}
		
		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));
			try
			{
				session = new Session(conf);
				session.LogTextEmitted += new EventHandler(OnClientFeedback);
				session.username = user;
				session.host = remoteHost;
				session.port = port;
				session.setPassword(password);
				
				if(dhost != null)
					session.DialogHost = dhost;
				session.setTimeout(conf.NetworkTimeout * 10);
				
				session.connect();
				channel=session.openChannel("sftp");
				channel.LogTextEmitted += new EventHandler(OnClientFeedback);
				channel.connect();
				c=(ChannelSftp)channel;
				
			}
			catch(Exception e)
			{
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				session.disconnect();
				c = null;
				session = null;
			}
		}

		public void Close ()
		{
			LogText(string.Format("Closing connection.."));
			c.disconnect();
			session.disconnect();			
		}
		
		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 string SendCommand (string command)
		{
			throw new NotImplementedException();
		}
		
		public virtual bool IsDir(string cwd, string dir)
		{
			try
			{
				c.cd(dir);
			}
			catch
			{
				return false;
			}
			c.cd(cwd);
			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 ()
		{
			string path2=".";
			
			try
			{
				return c.ls(path2);
			}
			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))
					{
						xferinprogress = true;
						c.myput(file, file.Action, fs);
						fs.Close();
					}					
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
				finally
				{
					xferinprogress = false;
				}
			}

			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
				{
					xferinprogress = true;
					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))
					{
						c.myget(file, file.Action, fs);
						fs.Close();
					}
				}
				catch(Exception e) 
				{
					file.Status = DownloadStatus.Failed;
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
				}
				finally
				{
					xferinprogress = false;
				}
				
			}

			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));
				c.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));
				c.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));
				c.cd(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 = c.pwd();
				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));
				c.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));
				c.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));
				c.chmod(Convert.ToInt32(mode,8), filename);
			}
			catch(SftpException e)
			{				
				OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Error, e.Message));
			}
		}

		public void Abort ()
		{
			throw new NotImplementedException();
		}
		
		public virtual string CurrentDirectory
		{
			get { return cwd; }
		}
		
		public event EventHandler LostConnection;
		
		public event EventHandler LogTextEmitted;
		public virtual void OnLogTextEmitted(LogTextEmittedArgs e)
		{
			LogTextEmitted(this, e);
		}

	}
}
