using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Bitvise;
using Bitvise.FlowSshNet;


namespace FlowSshNet_Sftp
{

    // ExitCodes

    enum ExitCodes
    {
        Success,
        UsageError,
        SessionError, // including connection error
        FatalError
    }

    
    // CmdLineParams

    class CmdLineParams
    {
        public bool m_bOk;
        public string m_sUserName;
        public string m_sPassword;
        public uint m_nPort;
        public string m_sHost;

        public bool m_bDownload;
        public string m_sLocalPath;
        public string m_sRemotePath;

        public CmdLineParams(string[] args)
        {
            bool syntaxError = false;
            for (int i = 0; i < args.Length && !syntaxError; ++i)
            {
                     if (args[i].StartsWith("-l=" )) m_sUserName = args[i].Substring(3);
                else if (args[i].StartsWith("-pw=")) m_sPassword = args[i].Substring(4);
                else if (args[i].StartsWith("-P=" )) m_nPort     = uint.Parse(args[i].Substring(3));
                else
                {
                    string ws = args[i];
                    int atpos = ws.IndexOf("@");
                    bool bat = (atpos != -1);

                    if (bat)
                    {
                        m_sUserName = ws.Substring(0, atpos);
                        ws = ws.Substring(atpos + 1);
                    }

                    int colon_pos = ws.IndexOf(":");
                    bool bcol = (colon_pos != -1);
                    if (bat && !bcol)
                        syntaxError = true;
                    else
                    {
                        // Absolute Windows paths have colon at 2nd position.
                        if (bat || (bcol && colon_pos != 1))
                        {
                            m_sHost = ws.Substring(0, colon_pos);
                            m_sRemotePath = ws.Substring(colon_pos + 1);
                            if (m_sLocalPath == null)
                                m_bDownload = true;
                        }
                        else
                        {
                            m_sLocalPath = ws;
                            if (m_sRemotePath == null)
                                m_bDownload = false;
                        }
                    }
                }
            }

            if (syntaxError || m_sLocalPath == null || m_sRemotePath == null)
            {
                Console.WriteLine("FlowSshNet sample SFTP client.");
                Console.WriteLine("");
                Console.WriteLine("Usage: FlowSshNet_Sftp [Options] [User@]Host:SourceFile TargetFile");
                Console.WriteLine("       FlowSshNet_Sftp [Options] SourceFile [User@]Host:TargetFile");
                Console.WriteLine("");
                Console.WriteLine("Options:");
                Console.WriteLine("        -l=user");
                Console.WriteLine("        -pw=password");
                Console.WriteLine("        -P=port");
                m_bOk = false;
            }
            else m_bOk = true;
        }
    }
    

    // MyClient

    class MyClient : ClientBase
    {
        public MyClient()
        {
            this.OnHostKey += new HostKeyEventHandler(this.OnMyHostKey);
        }

        public void SetClientSettings(CmdLineParams cmdpar)
	    {
            if (cmdpar.m_sUserName != null) SetUserName(cmdpar.m_sUserName);
            if (cmdpar.m_sPassword != null) SetPassword(cmdpar.m_sPassword);
            if (cmdpar.m_nPort > 0)         SetPort(cmdpar.m_nPort);
            if (cmdpar.m_sHost != null)     SetHost(cmdpar.m_sHost);
	    }

        private bool OnMyHostKey(object sender, PublicKey publicKey)
        {
            Console.WriteLine("Received the following host key:");
            Console.WriteLine("  MD5 Fingerprint: {0}", publicKey.GetMd5());
            Console.WriteLine("  Bubble-Babble: {0}", publicKey.GetBubbleBabble());
            Console.WriteLine("  SHA-256: {0}", publicKey.GetSha256());
            Console.Write("Type 'yes' to accept the key, any other input to exit: ");
            return Console.ReadLine() == "yes";
        }
    }


    // MyTransfer - implements logging of error and success

    class MyTransfer : TransferHandler
    {
        public MyTransfer()
        {
            this.OnDone += new DoneEventHandler(this.OnMyDone);
            this.OnError += new TransferErrorEventHandler(this.OnMyError);
        }

        private void OnMyDone(object sender)
        {
            if (Success) 
                Console.WriteLine("File transfer completed.");
        }

        private void OnMyError(object sender, TransferErr error)
        {
            string errMsg = (error.ErrMsg.Length > 0) ? error.ErrMsg : "<no error message>";

	        if ((uint) error.TransferOp < 100) // codeless errors
                Console.WriteLine("File transfer failed: {0}", errMsg);
            else if ((uint) error.TransferOp < 200) // SFTP errors
	        {
                switch (error.TransferOp)
		        {
		        case TransferOp.MakeRemoteDir:	 Console.Write("Create remote directory"); break;
                case TransferOp.OpenRemoteFile:  Console.Write("Opening remote file"); break;
                case TransferOp.ReadRemoteFile:  Console.Write("Reading remote file"); break;
                case TransferOp.WriteRemoteFile: Console.Write("Writing remote file"); break;
                default: Console.Write("Transfer"); break;
		        }
		        Console.WriteLine(" failed with SFTP error {0}: {1}", error.ErrCode, errMsg);
	        }
            else if ((uint)error.TransferOp < 300) // WinAPI errors
	        {
                switch (error.TransferOp)
		        {
                    case TransferOp.MakeLocalDir:   Console.Write("Create local directory"); break;
                    case TransferOp.OpenLocalFile:  Console.Write("Opening local file"); break;
                    case TransferOp.ReadLocalFile:  Console.Write("Reading local file"); break;
                    case TransferOp.WriteLocalFile: Console.Write("Writing local file"); break;
                    default: Console.Write("Transfer"); break;
		        }
                Console.WriteLine(" failed with Windows error {0}: {1}", error.ErrCode, errMsg);
	        }
	        else // FlowSshC_ResumeErrCode
                Console.WriteLine("Resuming file failed: {0}", errMsg);
        }
    }


    // Program

    class FlowSshNet_Sftp
    {
        static void OnUncaughtExceptionInEvent(object sender, bool fatal, System.Exception e)
        {
            Console.WriteLine("Error: " + e.ToString());
            System.Environment.Exit((int)ExitCodes.FatalError);
        }

        static int Main(string[] args)
        {
            CmdLineParams cmdpar = new CmdLineParams(args);
            if (!cmdpar.m_bOk)
                return (int)ExitCodes.UsageError;

            try 
            {
                // Register delegate for uncaught exceptions in user-defined events.
                // Example: If there is an uncaught exception in MyClient.OnMyHostKey, 
                //          then this is reported in OnUncaughtExceptionInEvent.
                SshNet.OnExceptionInEvent += new ExceptionInEventEventHandler(OnUncaughtExceptionInEvent);

                // For use in deployed applications where Bitvise SSH Client might not be installed, 
                // provide an activation code using SetActCode to ensure that FlowSsh does not  
                // display an evaluation dialog. On computers with Bitvise SSH Client, use of 
                // FlowSsh is permitted under the same terms as the Client; in this case, there 
                // will be no evaluation dialog, and SetActCode does not have to be called.
                //
                //SshNet.SetActCode("Enter Your Activation Code Here");

                // create client

                MyClient client = new MyClient();
                // CHANGE APPLICATION NAME IN PRODUCTION CODE!
		        client.SetAppName("FlowSshNet_Sftp 1.0");
                client.SetClientSettings(cmdpar);

                // connect client

                ProgressHandler progress = new ProgressHandler();
                client.Connect(progress);
                progress.WaitDone();

                if (!progress.Success)
                {	// Alternatively we could use a ProgressHandler::OnError
                    // event handler and do the logging there.
                    uint step = progress.GetTaskSpecificStep();
                    string auxInfo = progress.GetAuxInfo();
                    Console.WriteLine("{0}", ProgressHandler.DescribeConnectError(step, auxInfo));
                    return (int)ExitCodes.SessionError;
	            }

                // open sftp channel

	            ClientSftpChannel sftpChannel = new ClientSftpChannel(client);
	            sftpChannel.Open(progress);
	            progress.WaitDone();

	            if (!progress.Success)
                {	// Alternatively we could use a ProgressHandler::OnError
                    // event handler and do the logging there.
                    uint step = progress.GetTaskSpecificStep();
                    string auxInfo = progress.GetAuxInfo();
                    Console.WriteLine("{0}", ProgressHandler.DescribeSftpChannelOpenError(step, auxInfo));
                    return (int)ExitCodes.SessionError;
	            }

                // transfer file

	            MyTransfer transfer = new MyTransfer();
	            if (cmdpar.m_bDownload)
	            {
		            Console.WriteLine("Downloading {0} to {1}", cmdpar.m_sRemotePath, cmdpar.m_sLocalPath);
		            sftpChannel.Download(cmdpar.m_sRemotePath, cmdpar.m_sLocalPath, TransferFlags.Binary, transfer);
	            }
	            else
	            {
		            Console.WriteLine("Uploading {0} to {1}", cmdpar.m_sLocalPath, cmdpar.m_sRemotePath);
		            sftpChannel.Upload(cmdpar.m_sLocalPath, cmdpar.m_sRemotePath, TransferFlags.Binary, transfer);
	            }
	            transfer.WaitDone();

                // In contrast to the standard ProgressHandler used above we derive our own TransferHandler.
                // Success is logged in its MyTransfer::OnMyDone event handler.
                // Errors are logged in its MyTransfer::OnMyError event handler.
                if (!transfer.Success)
                    return (int)ExitCodes.SessionError;
            }
            catch (System.Exception e) // includes Bitvise.FlowSshNet.Exception
            {                
                Console.WriteLine("{0}", e.Message);
                return (int)ExitCodes.FatalError;
            }
            finally
            {
                SshNet.Shutdown();
            }

            return (int)ExitCodes.Success;
        } // Program.Main
    } // Program
}