using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Threading;
using bareFTP.Protocol.Sftp.Streams;

namespace bareFTP.Protocol.Sftp
{
	/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
	/*
	Copyright (c) 2002,2003,2004,2005,2006 ymnk, JCraft,Inc. All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	1. Redistributions of source code must retain the above copyright notice,
	this list of conditions and the following disclaimer.

	2. Redistributions in binary form must reproduce the above copyright
	notice, this list of conditions and the following disclaimer in
	the documentation and/or other materials provided with the distribution.

	3. The names of the authors may not be used to endorse or promote products
	derived from this software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
	INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
	OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
	EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	*/

	/// <summary>
	/// Based on JSch-0.1.30
	/// </summary>
	public class ChannelSftp : ChannelSession
	{
		private static  byte SSH_FXP_INIT=               1;
		//private static  byte SSH_FXP_VERSION=            2;
		private static  byte SSH_FXP_OPEN=               3;
		private static  byte SSH_FXP_CLOSE=              4;
		private static  byte SSH_FXP_READ=               5;
		private static  byte SSH_FXP_WRITE=              6;
		private static  byte SSH_FXP_LSTAT=              7;
		//private static  byte SSH_FXP_FSTAT=              8;
		private static  byte SSH_FXP_SETSTAT=            9;
		//private static  byte SSH_FXP_FSETSTAT=          10;
		private static  byte SSH_FXP_OPENDIR=           11;
		private static  byte SSH_FXP_READDIR=           12;
		private static  byte SSH_FXP_REMOVE=            13;
		private static  byte SSH_FXP_MKDIR=             14;
		private static  byte SSH_FXP_RMDIR=             15;
		private static  byte SSH_FXP_REALPATH=          16;
		private static  byte SSH_FXP_STAT=              17;
		private static  byte SSH_FXP_RENAME=            18;
		private static  byte SSH_FXP_READLINK=          19;
		private static  byte SSH_FXP_STATUS=           101;
		private static  byte SSH_FXP_HANDLE=           102;
		private static  byte SSH_FXP_DATA=             103;
		private static  byte SSH_FXP_NAME=             104;
		private static  byte SSH_FXP_ATTRS=            105;
		//private static  byte SSH_FXP_EXTENDED=         (byte)200;
		//private static  byte SSH_FXP_EXTENDED_REPLY=   (byte)201;

		// pflags
		private static  int SSH_FXF_READ=           0x00000001;
		private static  int SSH_FXF_WRITE=          0x00000002;
		//private static  int SSH_FXF_APPEND=         0x00000004;
		private static  int SSH_FXF_CREAT=          0x00000008;
		private static  int SSH_FXF_TRUNC=          0x00000010;
		//private static  int SSH_FXF_EXCL=           0x00000020;

		//private static  int SSH_FILEXFER_ATTR_SIZE=         0x00000001;
		//private static  int SSH_FILEXFER_ATTR_UIDGID=       0x00000002;
		//private static  int SSH_FILEXFER_ATTR_PERMISSIONS=  0x00000004;
		//private static  int SSH_FILEXFER_ATTR_ACMODTIME=    0x00000008;
		//private static  uint SSH_FILEXFER_ATTR_EXTENDED=     0x80000000;

		public static  int SSH_FX_OK=                            0;
		public static  int SSH_FX_EOF=                           1;
		public static  int SSH_FX_NO_SUCH_FILE=                  2;
		public static  int SSH_FX_PERMISSION_DENIED=             3;
		public static  int SSH_FX_FAILURE=                       4;
		public static  int SSH_FX_BAD_MESSAGE=                   5;
		public static  int SSH_FX_NO_CONNECTION=                 6;
		public static  int SSH_FX_CONNECTION_LOST=               7;
		public static  int SSH_FX_OP_UNSUPPORTED=                8;
		/*
		SSH_FX_OK
		Indicates successful completion of the operation.
		SSH_FX_EOF
		indicates end-of-file condition; for SSH_FX_READ it means that no
		more data is available in the file, and for SSH_FX_READDIR it
		indicates that no more files are contained in the directory.
		SSH_FX_NO_SUCH_FILE
		is returned when a reference is made to a file which should exist
		but doesn't.
		SSH_FX_PERMISSION_DENIED
		is returned when the authenticated user does not have sufficient
		permissions to perform the operation.
		SSH_FX_FAILURE
		is a generic catch-all error message; it should be returned if an
		error occurs for which there is no more specific error code
		defined.
		SSH_FX_BAD_MESSAGE
		may be returned if a badly formatted packet or protocol
		incompatibility is detected.
		SSH_FX_NO_CONNECTION
		is a pseudo-error which indicates that the client has no
		connection to the server (it can only be generated locally by the
		client, and MUST NOT be returned by servers).
		SSH_FX_CONNECTION_LOST
		is a pseudo-error which indicates that the connection to the
		server has been lost (it can only be generated locally by the
		client, and MUST NOT be returned by servers).
		SSH_FX_OP_UNSUPPORTED
		indicates that an attempt was made to perform an operation which
		is not supported for the server (it may be generated locally by
		the client if e.g.  the version number exchange indicates that a
		required feature is not supported by the server, or it may be
		returned by the server if the server does not implement an
		operation).
		*/
		private static  int MAX_MSG_LENGTH = 256* 1024;

		public static  int OVERWRITE=0;
		public static  int RESUME=1;
		public static  int APPEND=2;

		internal int seq=1;
		private int[] ackid=new int[1];
		private Buffer buf;
		private Packet packet;//=new Packet(buf);

		private string _version="3";
		private int server_version=3;

		/*
		10. Changes from previous protocol versions
		The SSH File Transfer Protocol has changed over time, before it's
		standardization.  The following is a description of the incompatible
		changes between different versions.
		10.1 Changes between versions 3 and 2
		o  The SSH_FXP_READLINK and SSH_FXP_SYMLINK messages were added.
		o  The SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY messages were added.
		o  The SSH_FXP_STATUS message was changed to include fields `error
		message' and `language tag'.
		10.2 Changes between versions 2 and 1
		o  The SSH_FXP_RENAME message was added.
		10.3 Changes between versions 1 and 0
		o  Implementation changes, no actual protocol changes.
		*/

		private static string file_separator = System.IO.Path.DirectorySeparatorChar.ToString();
		//private static  char file_separatorc = System.IO.Path.DirectorySeparatorChar;

		private string cwd;
		private string home;
		private string lcwd;

		internal ChannelSftp()
		{
			packet=new Packet(buf);
		}

		public override void start() 
		{ 
			
			PipedOutputStream pos=new PipedOutputStream();
			io.setOutputStream(pos);
			PipedInputStream pis=new MyPipedInputStream(pos, 32*1024);
			io.setInputStream(pis);
			
			Request request=new RequestSftp();
			request.request(session, this);

			buf=new Buffer(rmpsize);
			packet=new Packet(buf);
			
			int length;
			byte[] str;

			// send SSH_FXP_INIT
			sendINIT();

			// receive SSH_FXP_VERSION
			Header _header=new Header();
			_header=header(buf, _header);
			length=_header.length;
			if(length > MAX_MSG_LENGTH)
			{
				throw new SftpException(SSH_FX_FAILURE, "Received message is too long: " + length);
			}
			//type=_header.type;             // 2 -> SSH_FXP_VERSION
			server_version=_header.rid;
			skip(length);
			
			OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Info, "Server version: "+server_version ));
			
			// send SSH_FXP_REALPATH
			sendREALPATH(System.Text.Encoding.UTF8.GetBytes("."));

			// receive SSH_FXP_NAME
			_header=header(buf, _header);
			length=_header.length;
			//type=_header.type;            // 104 -> SSH_FXP_NAME
			buf.rewind();
			fill(buf.buffer, 0, length);
			buf.getInt();              // count
			
			str=buf.getString();         // filename
			
			home=cwd= System.Text.Encoding.UTF8.GetString(str);
			str=buf.getString();         // logname
			
			lcwd = System.IO.Path.GetFullPath(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
			OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Info, "Local path is " + lcwd));
		}

		public void quit(){ disconnect();}
		public void exit(){ disconnect();}
		
		public void lcd(string path) 
		{ 
			path=localAbsolutePath(path);
			if(System.IO.Directory.Exists(path))
			{
				try
				{
					path=System.IO.Path.GetFullPath(path);
				}
				catch(System.Exception){}
				lcwd=path;
				return;
			}
			throw new SftpException(SSH_FX_NO_SUCH_FILE, "No such directory");
		}

		/*
		cd /tmp
		c->s REALPATH
		s->c NAME
		c->s STAT
		s->c ATTR
		*/
		public void cd(string path)
		{
			try
			{
				path = remoteAbsolutePath(path);
				
				sendREALPATH(System.Text.Encoding.UTF8.GetBytes(path));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=101 && type!=104)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				int i;
				if(type==101)
				{
					i=buf.getInt();
					throwStatusError(buf, i);
				}
				i=buf.getInt();
				byte[] str=buf.getString();
				if(str!=null && str[0]!='/')
				{
					str = System.Text.Encoding.UTF8.GetBytes((cwd+"/"+ System.Text.Encoding.UTF8.GetString(str)));
				}
				str=buf.getString();         // logname
				i=buf.getInt();              // attrs

				string newpwd= System.Text.Encoding.UTF8.GetString(str);
				SftpATTRS attr=_stat(newpwd);
				if((attr.getFlags()&SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS)==0)
				{
					throw new SftpException(SSH_FX_FAILURE, 
					                        "Can't change directory: "+path);
				}
				if(!attr.isDir())
				{
					throw new SftpException(SSH_FX_FAILURE, 
					                        "Can't change directory: "+path);
				}
				cwd=newpwd;
			}
			catch(System.Exception e)
			{
				//System.Console.WriteLine("Hm.." + e.Message);
				//System.Console.WriteLine("Hm.." + e.StackTrace);
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}

		/*
		put foo
		c->s OPEN
		s->c HANDLE
		c->s WRITE
		s->c STATUS
		c->s CLOSE
		s->c STATUS
		*/
		
		public void myput(bareFTP.Protocol.XferFile file, bareFTP.Protocol.FileAction action, System.IO.FileStream src)
		{
			try
			{
				string dst = file.Path.FileNameRemoteAbs;
				long skip=0;
				
				if(action == bareFTP.Protocol.FileAction.Resume || action == bareFTP.Protocol.FileAction.Append)
   				{
   					try
   					{
   						SftpATTRS attr= _stat(dst);
   						skip=attr.getSize();
   					}
   					catch(System.Exception)
   					{
   						//System.err.println(eee);
   					}
   				}
   				if(action == bareFTP.Protocol.FileAction.Resume && skip>0)
				{
					src.Position = skip;
   					if(src.Position<skip)
   					{
   						throw new SftpException(SSH_FX_FAILURE, "failed to resume for "+dst);
   					}
   				}
   				if(action == bareFTP.Protocol.FileAction.Overwrite)
				{ 
					sendOPENW(System.Text.Encoding.UTF8.GetBytes(dst)); 
				}
				else
				{ 
					sendOPENA(System.Text.Encoding.UTF8.GetBytes(dst)); 
				}

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE)
				{
					throw new SftpException(SSH_FX_FAILURE, "invalid type="+type);
				}
				if(type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					throwStatusError(buf, i);
				}
				byte[] handle=buf.getString();         // filename
				byte[] data=null;

				bool dontcopy=true;

				if(!dontcopy)
				{
					data=new byte[buf.buffer.Length
					              -(5+13+21+handle.Length
					                +32 +20 // padding and mac
					               )
						];
				}

				long offset=0;
				
				if(action == bareFTP.Protocol.FileAction.Resume || action == bareFTP.Protocol.FileAction.Append)
   				{
					offset+=skip;
				}
				
				int startid=seq;
				int _ackid=seq;
				int ackcount=0;
				
				while(true)
				{
					int nread=0;
					int s=0;
					int datalen=0;
					int count=0;

					if(!dontcopy)
					{
						datalen=data.Length-s;
					}
					else
					{
						data=buf.buffer;
						s=5+13+21+handle.Length;
						datalen=buf.buffer.Length -s
						        -32 -20; // padding and mac
					}
					
					
					do
					{
						nread=src.Read(data, s, datalen);
						if(nread>0)
						{
							s+= nread;
							datalen -= nread;
							count+=nread;
						}
					}
					
					while(datalen>0 && nread>0);
					
					if(count<=0)
						break;

					int _i=count;
					file.Status = bareFTP.Protocol.DownloadStatus.Uploading;
					
					while(_i>0)
					{
						_i-=sendWRITE(handle, offset, data, 0, _i);
						
						if((seq-1)==startid || Util.available(io.ins)>=1024)
						{
							while(Util.available(io.ins) > 0)
							{
								if(checkStatus(ackid, _header))
								{
									_ackid=ackid[0];
									if(startid>_ackid || _ackid>seq-1)
									{
										if(_ackid==seq)
										{
											System.Console.WriteLine("ack error: startid="+startid+" seq="+seq+" _ackid="+_ackid);
										} 
										else
										{
											//throw new SftpException(SSH_FX_FAILURE, "ack error:");
											throw new SftpException(SSH_FX_FAILURE, "ack error: startid="+startid+" seq="+seq+" _ackid="+_ackid);
										}
									}
									ackcount++;
								}
								else
								{
									break;
								}
							}
						}
					}
					offset+=count;
					file.TransferedBytes = offset;
					
				}
				int _ackcount=seq-startid;
				while(_ackcount>ackcount)
				{
					if(!checkStatus(null, _header))
					{
						break;
					}
					ackcount++;
				}
				_sendCLOSE(handle, _header);
				file.Status = DownloadStatus.Finished;
				//System.err.println("start end "+startid+" "+endid);
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, e.ToString());
			}
		}
		
		private SftpATTRS _stat(string path)
		{
			try
			{
				sendSTAT(System.Text.Encoding.UTF8.GetBytes(path));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_ATTRS)
				{
					if(type==SSH_FXP_STATUS)
					{
						int i=buf.getInt();
						throwStatusError(buf, i);
					}
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				SftpATTRS attr=SftpATTRS.getATTR(buf);
				return attr;
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
			//return null;
		}

/**/
		///tamir: updated to jcsh-0.1.30
		public void myget(bareFTP.Protocol.XferFile src, bareFTP.Protocol.FileAction action, System.IO.FileStream dst) 
		{ 
			try
			{
				string _src = src.Path.FileNameRemoteAbs;
				sendOPENR(System.Text.Encoding.UTF8.GetBytes(_src));
				
				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				
				buf.rewind();
				
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE)
				{
					//System.Console.WriteLine("Type is "+type);
					throw new SftpException(SSH_FX_FAILURE, "Type is "+type);
				}

				if(type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					throwStatusError(buf, i);
				}
				
				byte[] handle=buf.getString();         // filename

				long offset=0;
				if(action == FileAction.Resume)
				{
					
					offset += dst.Position;
					src.TransferedBytes = offset;
				}

				int request_len=0;
				src.Status = DownloadStatus.Downloading;
				
				while(true)
				{

					request_len=buf.buffer.Length-13;
					if(server_version==0){ request_len=1024; }
					sendREAD(handle, offset, request_len);
					
					_header=header(buf, _header);
					length=_header.length;
					type=_header.type;
					
					int i;
					if(type==SSH_FXP_STATUS)
					{
						buf.rewind();
						fill(buf.buffer, 0, length);
						i=buf.getInt();   
						if(i==SSH_FX_EOF)
						{
							break;
						}
						throwStatusError(buf, i);
					}
					
					if(type!=SSH_FXP_DATA)
					{
						break;
					}

					buf.rewind();
					fill(buf.buffer, 0, 4); length-=4;
					i=buf.getInt();   // length of data 
					int foo=i;
					
					while(foo>0)
					{
						int bar=foo;
						if(bar>buf.buffer.Length)
						{
							bar=buf.buffer.Length;
						}
						i=io.ins.Read(buf.buffer, 0, bar);
						if(i<0)
						{
							break;
						}
						int data_len=i;
						dst.Write(buf.buffer, 0, data_len);

						offset+=data_len;
						src.TransferedBytes += data_len;
						foo-=data_len;
					}
					//System.out.println("length: "+length);  // length should be 0
				}
			
				dst.Flush();

				_sendCLOSE(handle, _header);
				src.Status = DownloadStatus.Finished;
			}
			catch(System.Exception e)
			{
				
				//System.Console.WriteLine("Å faen: " + e.ToString());
				//System.Console.WriteLine("Src: " + src.FileName);
				
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}

		
		public List<bareFTP.Protocol.RemoteFile> ls(string path) 
		{ //throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);

				string dir=path;
				byte[] pattern=null;
				SftpATTRS attr=null;
				if((attr=stat(dir))!=null && !attr.isDir())
				{
					int foo=path.LastIndexOf('/');
					dir=path.Substring(0, ((foo==0)?1:foo));
					pattern= System.Text.Encoding.UTF8.GetBytes(path.Substring(foo+1));
				}

				sendOPENDIR(System.Text.Encoding.UTF8.GetBytes(dir));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				if(type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					throwStatusError(buf, i);
				}

				byte[] handle = buf.getString();         // filename

				List<bareFTP.Protocol.RemoteFile> v = new List<bareFTP.Protocol.RemoteFile>();
				while(true)
				{
					sendREADDIR(handle);

					_header=header(buf, _header);
					length=_header.length;
					type=_header.type;
					if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME)
					{
						throw new SftpException(SSH_FX_FAILURE, "");
					}
					if(type==SSH_FXP_STATUS)
					{ 
						buf.rewind();
						fill(buf.buffer, 0, length);
						int i=buf.getInt();
						if(i==SSH_FX_EOF)
							break;
						throwStatusError(buf, i);
					}

					buf.rewind();
					fill(buf.buffer, 0, 4); length-=4;
					int count=buf.getInt();

					//byte[] str;
					
					buf.reset();
					while(count>0)
					{
						if(length>0)
						{
							buf.shift();
							int j=(buf.buffer.Length>(buf.index+length)) ? length : (buf.buffer.Length-buf.index);
							int i=fill(buf.buffer, buf.index, j);
							buf.index+=i;
							length-=i;
						}
						byte[] filename=buf.getString();
						buf.getString(); //str=buf.getString();
						//string longname= System.Text.Encoding.UTF8.GetString(str);

						SftpATTRS attrs=SftpATTRS.getATTR(buf);
						
						if(pattern==null || Util.glob(pattern, filename))
						{
							bareFTP.Protocol.RemoteFile _rf = new bareFTP.Protocol.RemoteFile();
							_rf.Filename = System.Text.Encoding.UTF8.GetString(filename);
							_rf.Group = attrs.getGId().ToString();
							_rf.Owner = attrs.getUId().ToString();
							_rf.IsDir = attrs.isDir();
							_rf.IsLink = attrs.isLink();
							_rf.LastModified = attrs.Mtime;
							_rf.Size = attrs.getSize();
							_rf.Permissions = attrs.getPermissionsString();
							
							v.Add(_rf);
						}

						count--; 
					}
				}
				_sendCLOSE(handle, _header);
				return v;
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public string readlink(string path)
		{
			// throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);
				List<string> v = glob_remote(path);
				if(v.Count != 1)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				path=(string)(v[0]);

				sendREADLINK(System.Text.Encoding.UTF8.GetBytes(path));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				int i;
				if(type==SSH_FXP_NAME)
				{
					int count=buf.getInt();       // count
					byte[] filename=null;
					//byte[] longname=null;
					for(i=0; i<count; i++)
					{
						filename=buf.getString();
						buf.getString(); // longname
						SftpATTRS.getATTR(buf);
					}
					return System.Text.Encoding.UTF8.GetString(filename);
				}

				i=buf.getInt();
				throwStatusError(buf, i);
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
			return null;
		}


		public void rename(string oldpath, string newpath)
		{
			//throws SftpException{
			if(server_version<2)
			{
				throw new SftpException(SSH_FX_FAILURE, 
				                        "The remote sshd is too old to support rename operation.");
			}
			try
			{
				oldpath=remoteAbsolutePath(oldpath);
				newpath=remoteAbsolutePath(newpath);

				List<string> v = glob_remote(oldpath);
				int vsize=v.Count;
				if(vsize!=1)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				oldpath=(string)(v[0]);

				v=glob_remote(newpath);
				vsize=v.Count;
				if(vsize>=2)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				if(vsize==1)
				{
					newpath=(string)(v[0]);
				}
				else
				{  // vsize==0
					newpath=Util.unquote(newpath);
				}

				sendRENAME(System.Text.Encoding.UTF8.GetBytes(oldpath), System.Text.Encoding.UTF8.GetBytes(newpath));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}

				int i=buf.getInt();
				if(i==SSH_FX_OK) return;
				throwStatusError(buf, i);
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		public void rm(string path)
		{
			//throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);
				List<string> v = glob_remote(path);
				int vsize=v.Count;
				Header _header=new Header();

				for(int j=0; j<vsize; j++)
				{
					path=(string)(v[j]);
					sendREMOVE(System.Text.Encoding.UTF8.GetBytes(path));

					_header=header(buf, _header);
					int length=_header.length;
					int type=_header.type;
					buf.rewind();
					fill(buf.buffer, 0, length);

					if(type!=SSH_FXP_STATUS)
					{
						throw new SftpException(SSH_FX_FAILURE, "");
					}
					int i=buf.getInt();
					if(i!=SSH_FX_OK)
					{
						throwStatusError(buf, i);
					}
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void chmod(int permissions, string path) 
		{ //throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				int vsize=v.Count;
				for(int j=0; j<vsize; j++)
				{
					path=(string)(v[j]);

					SftpATTRS attr=_stat(path);
					
					attr.setFLAGS(0);
					attr.setPERMISSIONS(permissions);
					_setStat(path, attr);
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void rmdir(string path)
		{
			//throws SftpException{
			try
			{
				
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				int vsize=v.Count;
				Header _header=new Header();

				for(int j=0; j<vsize; j++)
				{
					path=(string)(v[j]);
					sendRMDIR(System.Text.Encoding.UTF8.GetBytes(path));

					_header=header(buf, _header);
					int length=_header.length;
					int type=_header.type;
					buf.rewind();
					fill(buf.buffer, 0, length);

					if(type!=SSH_FXP_STATUS)
					{
						throw new SftpException(SSH_FX_FAILURE, "");
					}

					int i=buf.getInt();
					if(i!=SSH_FX_OK)
					{
						throwStatusError(buf, i);
					}
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}

		public void mkdir(string path)
		{
			//throws SftpException{
			//System.Console.WriteLine(path);
			try
			{
				path=remoteAbsolutePath(path);
				//System.Console.WriteLine(path);
				
				sendMKDIR(System.Text.Encoding.UTF8.GetBytes(path), null);

				Header _header=new Header();      
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}

				int i=buf.getInt();
				if(i==SSH_FX_OK) return;
				throwStatusError(buf, i);
			}
			catch(System.Exception e)
			{
				//System.Console.WriteLine("Bah.." + e.Message);
				//System.Console.WriteLine("Bah.." + e.StackTrace);
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}

		public SftpATTRS stat(string path)
		{
			//throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				if(v.Count != 1)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				path=(string)(v[0]);
				return _stat(path);
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
			//return null;
		}
		public SftpATTRS lstat(string path)
		{
			//throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				if(v.Count!=1)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				path=(string)(v[0]);

				return _lstat(path);
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		private SftpATTRS _lstat(string path)
		{
			//throws SftpException{
			try
			{
				sendLSTAT(System.Text.Encoding.UTF8.GetBytes(path));

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_ATTRS)
				{
					if(type==SSH_FXP_STATUS)
					{
						int i=buf.getInt();
						throwStatusError(buf, i);
					}
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				SftpATTRS attr=SftpATTRS.getATTR(buf);
				return attr;
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
  

		public void setStat(string path, SftpATTRS attr) 
		{ //throws SftpException{
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				int vsize=v.Count;
				for(int j=0; j<vsize; j++)
				{
					path=(string)(v[j]);
					_setStat(path, attr);
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		private void _setStat(string path, SftpATTRS attr)
		{
			//throws SftpException{
			try
			{
				sendSETSTAT(System.Text.Encoding.UTF8.GetBytes(path), attr);

				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				buf.rewind();
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				int i=buf.getInt();
				if(i!=SSH_FX_OK)
				{
					throwStatusError(buf, i);
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}

		public string pwd(){ return cwd; }
		public string lpwd(){ return lcwd; }
		public string version(){ return _version; }
		public string getHome(){ return home; }
		
		/*
		private void read(byte[] buf, int s, int l) 
		{ //throws IOException, SftpException{
			int i=0;
			while(l>0)
			{
				i=io.ins.read(buf, s, l);
				if(i<=0)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				s+=i;
				l-=i;
			}
		}
		*/
		internal bool checkStatus(int[] ackid, Header _header ) 
		{ //throws IOException, SftpException{
			_header=header(buf, _header);
			int length=_header.length;
			int type=_header.type;
			if(ackid!=null)
				ackid[0]=_header.rid;
			buf.rewind();
			fill(buf.buffer, 0, length);

			if(type!=SSH_FXP_STATUS)
			{ 
				throw new SftpException(SSH_FX_FAILURE, "");
			}
			int i=buf.getInt();
			if(i!=SSH_FX_OK)
			{
				throwStatusError(buf, i);
			}
			return true;
		}
		
		internal bool _sendCLOSE(byte[] handle, Header header)
			//throws Exception
		{
			sendCLOSE(handle);
			return checkStatus(null, header);
		}

		private void sendINIT() 
		{ //throws Exception{
			packet.reset();
			putHEAD(SSH_FXP_INIT, 5);
			buf.putInt(3);                // version 3
			session.write(packet, this, 5+4);
		}

		private void sendREALPATH(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_REALPATH, path);
		}
		private void sendSTAT(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_STAT, path);
		}
		private void sendLSTAT(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_LSTAT, path);
		}
		
		/*
		private void sendFSTAT(byte[] handle) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_FSTAT, handle);
		}
		*/
		private void sendSETSTAT(byte[] path, SftpATTRS attr) 
		{ //throws Exception{
			packet.reset();
			putHEAD(SSH_FXP_SETSTAT, 9+path.Length + attr.Length);
			buf.putInt(seq++);
			buf.putString(path);             // path
			attr.dump(buf);
			session.write(packet, this, 9+path.Length+attr.Length+4);
		}
		private void sendREMOVE(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_REMOVE, path);
		}
		private void sendMKDIR(byte[] path, SftpATTRS attr) 
		{ //throws Exception{
			packet.reset();
			putHEAD(SSH_FXP_MKDIR, 9+path.Length+(attr!=null?attr.Length:4));
			buf.putInt(seq++);
			buf.putString(path);             // path
			if(attr!=null) attr.dump(buf);
			else buf.putInt(0);
			session.write(packet, this, 9+path.Length+(attr!=null?attr.Length:4)+4);
		}
		private void sendRMDIR(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_RMDIR, path);
		}
		
		private void sendREADLINK(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_READLINK, path);
		}
		private void sendOPENDIR(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_OPENDIR, path);
		}
		private void sendREADDIR(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_READDIR, path);
		}
		private void sendRENAME(byte[] p1, byte[] p2) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_RENAME, p1, p2);
		}
		private void sendCLOSE(byte[] path) 
		{ //throws Exception{
			sendPacketPath(SSH_FXP_CLOSE, path);
		}
		private void sendOPENR(byte[] path) 
		{ //throws Exception{
			sendOPEN(path, SSH_FXF_READ);
		}
		private void sendOPENW(byte[] path) 
		{ //throws Exception{
			sendOPEN(path, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC);
		}
		private void sendOPENA(byte[] path) 
		{ //throws Exception{
			sendOPEN(path, SSH_FXF_WRITE|/*SSH_FXF_APPEND|*/SSH_FXF_CREAT);
		}
		private void sendOPEN(byte[] path, int mode) 
		{ //throws Exception{
			packet.reset();
			putHEAD(SSH_FXP_OPEN, 17+path.Length);
			buf.putInt(seq++);
			buf.putString(path);
			buf.putInt(mode);
			buf.putInt(0);           // attrs
			session.write(packet, this, 17+path.Length+4);
		}
		private void sendPacketPath(byte fxp, byte[] path) 
		{ //throws Exception{
			packet.reset();
			putHEAD(fxp, 9+path.Length);
			buf.putInt(seq++);
			buf.putString(path);             // path
			session.write(packet, this, 9+path.Length+4);
		}
		private void sendPacketPath(byte fxp, byte[] p1, byte[] p2) 
		{ //throws Exception{
			packet.reset();
			putHEAD(fxp, 13+p1.Length+p2.Length);
			buf.putInt(seq++);
			buf.putString(p1);
			buf.putString(p2);
			session.write(packet, this, 13+p1.Length+p2.Length+4);
		}

		internal int sendWRITE(byte[] handle, long offset, byte[] data, int start, int length) 
		{ 
			int _length=length;
			packet.reset();
			if(buf.buffer.Length<buf.index+13+21+handle.Length+length
				+32 +20  // padding and mac
				)
			{
				_length=buf.buffer.Length-(buf.index+13+21+handle.Length
					+32 +20  // padding and mac
					);
				//System.err.println("_length="+_length+" length="+length);
			}
			putHEAD(SSH_FXP_WRITE, 21+handle.Length+_length);       // 14
			buf.putInt(seq++);                                    //  4
			buf.putString(handle);                                  //  4+handle.length
			buf.putLong(offset);                                    //  8
			
			if(buf.buffer!=data)
			{
				buf.putString(data, start, _length);                    //  4+_length
			}
			else
			{
				buf.putInt(_length);
				buf.skip(_length);
			}
			session.write(packet, this, 21+handle.Length+_length+4);
			return _length;
		}

		private void sendREAD(byte[] handle, long offset, int length) 
		{ //throws Exception{
			packet.reset();
			putHEAD(SSH_FXP_READ, 21+handle.Length);
			buf.putInt(seq++);
			buf.putString(handle);
			buf.putLong(offset);
			buf.putInt(length);
			session.write(packet, this, 21+handle.Length+4);
		}

		private void putHEAD(byte type, int length) 
		{ //throws Exception{
			buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
			buf.putInt(recipient);
			buf.putInt(length+4);
			buf.putInt(length);
			buf.putByte(type);
		}
		private List<string> glob_remote(string _path)
		{
			//throws Exception{
			//System.err.println("glob_remote: "+_path);
			List<string> v = new List<string>();
			//byte[] path= System.Text.Encoding.UTF8.GetBytes(_path);
			
				v.Add(Util.unquote(_path)); 
				return v;
			/*
			int i=path.Length-1;
			while(i>=0)
			{
				if(path[i]=='/')break;i--;
			}
			if(i<0)
			{ 
				v.Add(Util.unquote(_path)); 
				return v;
			}
			byte[] dir;
			if(i==0)
			{
				dir=new byte[]{(byte)'/'};
			}
			else
			{ 
				dir=new byte[i];
				Util.arraycopy(path, 0, dir, 0, i);
			}
			//System.err.println("dir: "+new String(dir));
			byte[] pattern=new byte[path.Length-i-1];
			Util.arraycopy(path, i+1, pattern, 0, pattern.Length);
			//System.err.println("file: "+new String(pattern));

			sendOPENDIR(dir);

			Header _header=new Header();
			_header=header(buf, _header);
			int length=_header.length;
			int type=_header.type;
			buf.rewind();
			fill(buf.buffer, 0, length);

			if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE)
			{
				throw new SftpException(SSH_FX_FAILURE, "");
			}
			if(type==SSH_FXP_STATUS)
			{
				i=buf.getInt();
				throwStatusError(buf, i);
			}

			byte[] handle=buf.getString();         // filename

			while(true)
			{
				sendREADDIR(handle);
				_header=header(buf, _header);
				length=_header.length;
				type=_header.type;

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				if(type==SSH_FXP_STATUS)
				{ 
					buf.rewind();
					fill(buf.buffer, 0, length);
					break;
				}

				buf.rewind();
				fill(buf.buffer, 0, 4); length-=4;
				int count=buf.getInt();

				//byte[] str;
				
				buf.reset();
				while(count>0)
				{
					if(length>0)
					{
						buf.shift();
						int j=(buf.buffer.Length>(buf.index+length)) ? length : (buf.buffer.Length-buf.index);
						i=io.ins.Read(buf.buffer, buf.index, j);
						if(i<=0)break;
						buf.index+=i;
						length-=i;
					}

					byte[] filename=buf.getString();
					//System.err.println("filename: "+new String(filename));
					buf.getString(); // str
					//SftpATTRS attrs=SftpATTRS.getATTR(buf);

					if(Util.glob(pattern, filename))
					{
						v.Add(System.Text.Encoding.UTF8.GetString(dir) + "/" + System.Text.Encoding.UTF8.GetString(filename));
					}
					count--; 
				}
			}
			if(_sendCLOSE(handle, _header)) 
				return v;
			return null;
			*/
		}

		private void throwStatusError(Buffer buf, int i) 
		{ 
			if(server_version>=3)
			{
				byte[] str=buf.getString();
				//byte[] tag=buf.getString();
				throw new SftpException(i, System.Text.Encoding.UTF8.GetString(str));
			}
			else
			{
				throw new SftpException(i, "Failure");
			}
		}

		private static bool isLocalAbsolutePath(string path)
		{
			return System.IO.Path.IsPathRooted(path);
		}

		/*
		public void finalize() { //throws Throwable{
		base.finalize();
		}
		*/

		public override void disconnect()
		{
			//waitForRunningThreadFinish(10000);
			clearRunningThreads();
			base.disconnect();
		}
		private List<Thread> threadList=null;
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		protected void addRunningThread(Thread thread)
		{
			if(threadList==null)threadList=new List<Thread>();
			threadList.Add(thread);
		}
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		protected void clearRunningThreads()
		{
			if(threadList==null)return;
			foreach(Thread thread in threadList)
			{
				if(thread!=null)
					if(thread.IsAlive)
						thread.Interrupt();
			}
			threadList.Clear();
		}
		

		private int fill(byte[] buf, int s, int len) 
		{
			int i=0;
			int foo=s;
			while(len>0)
			{
				i=io.ins.Read(buf, s, len);
				if(i<=0)
				{
					throw new System.IO.IOException("inputstream is closed");
					//return (s-foo)==0 ? i : s-foo;
				}
				s+=i;
				len-=i;
			}
			return s-foo;
		}
		
		//tamir: some functions from jsch-0.1.30
		private void skip(long foo)
		{
			while(foo>0)
			{
				long bar= Util.skip(io.ins, foo);
				if(bar<=0) 
					break;
				foo-=bar;
			}
		}
	
		internal class Header
		{
			public int length;
			public int type;
			public int rid;
		}
		internal Header header(Buffer buf, Header header)
		{
			buf.rewind();
			fill(buf.buffer, 0, 9);
			header.length=buf.getInt()-5;
			header.type=buf.getByte()&0xff;
			header.rid=buf.getInt();  
			return header;
		}

		private string remoteAbsolutePath(string path)
		{
			if(path[0]=='/') return path;
			if(cwd.EndsWith("/")) return cwd+path;
			return cwd+"/"+path;
		}

		private string localAbsolutePath(string path)
		{
			if(isLocalAbsolutePath(path)) return path;
			if(lcwd.EndsWith(file_separator)) return lcwd+path;
			return lcwd+file_separator+path;
		}
		
	}
	
}
