//  
//   Rebex Sample Code License
// 
//   Copyright 2025, REBEX CR s.r.o.
//   All rights reserved.
//   https://www.rebex.net/
// 
//   Permission to use, copy, modify, and/or distribute this software for any
//   purpose with or without fee is hereby granted.
// 
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//   OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.IO;
using Rebex.Net;

namespace Rebex.Samples
{
    /// <summary>
    /// Summary description for MainForm.
    /// </summary>
    public class MainForm : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label lblRemote;
        private System.Windows.Forms.Label lblLocal;
        private System.Windows.Forms.TextBox txtRemote;
        private System.Windows.Forms.Button cmdDownload;
        private System.Windows.Forms.Button cmdUpload;
        private System.Windows.Forms.ProgressBar pbProgress;
        private StatusStrip statusStrip;
        private ToolStripStatusLabel statusLabel;
        private System.Windows.Forms.Button cmdAbort;
        private System.Windows.Forms.TextBox txtLocal;
        private System.Windows.Forms.TextBox txtPort;
        private System.Windows.Forms.TextBox txtHostname;
        private System.Windows.Forms.TextBox txtUsername;
        private System.Windows.Forms.TextBox txtPassword;
        private System.Windows.Forms.Label lblHostname;
        private System.Windows.Forms.Label lblUsername;
        private System.Windows.Forms.Label lblPort;
        private System.Windows.Forms.Label lblPassword;

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        // Instance of Rebex.Net.Sftp
        private Sftp _ftp;

        // Background thread
        private Thread _backgroundThread;

        // Remote offset to start at
        private long _remoteOffset;

        // Connection info
        private string localPath;
        private string remotePath;
        private string hostname;
        private string username;
        private string password;
        private int port;

        /// <summary>
        /// Wrapper for Invoke method that doesn't throw an exception after the object has been
        /// disposed while the calling method was running in a background thread.
        /// </summary>
        /// <param name="method"></param>
        /// <param name="args"></param>
        private void SafeInvoke(Delegate method, params object[] args)
        {
            try
            {
                if (!IsDisposed)
                    Invoke(method, args);
            }
            catch (ObjectDisposedException)
            {
            }
        }

        // This is called at the change of SFTP object state
        private void StateChanged (object sender, SftpStateChangedEventArgs e)
        {
            statusLabel.Text = e.NewState.ToString();
        }

        // This is called on transfer progress
        private void TransferProgress(object sender, SftpTransferProgressChangedEventArgs e)
        {
            pbProgress.Value = Convert.ToInt32(e.CurrentFileProgressPercentage);
            statusLabel.Text = "Transferred " + e.CurrentFileBytesTransferred + " bytes";
        }

        private void StateChangedProxy(object sender, SftpStateChangedEventArgs e)
        {
            SafeInvoke(new EventHandler<SftpStateChangedEventArgs>(StateChanged), new object[] { sender, e });
        }

        public void TransferProgressProxy(object sender, SftpTransferProgressChangedEventArgs e)
        {
            SafeInvoke(new EventHandler<SftpTransferProgressChangedEventArgs>(TransferProgress), new object[] { sender, e });
        }

        public MainForm()
        {
            Application.EnableVisualStyles();
            CheckForIllegalCrossThreadCalls = true;
            InitializeComponent();
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.txtLocal = new System.Windows.Forms.TextBox();
            this.lblRemote = new System.Windows.Forms.Label();
            this.lblLocal = new System.Windows.Forms.Label();
            this.txtRemote = new System.Windows.Forms.TextBox();
            this.cmdDownload = new System.Windows.Forms.Button();
            this.cmdUpload = new System.Windows.Forms.Button();
            this.pbProgress = new System.Windows.Forms.ProgressBar();
            this.statusStrip = new System.Windows.Forms.StatusStrip();
            this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
            this.cmdAbort = new System.Windows.Forms.Button();
            this.txtPort = new System.Windows.Forms.TextBox();
            this.txtHostname = new System.Windows.Forms.TextBox();
            this.txtUsername = new System.Windows.Forms.TextBox();
            this.txtPassword = new System.Windows.Forms.TextBox();
            this.lblHostname = new System.Windows.Forms.Label();
            this.lblUsername = new System.Windows.Forms.Label();
            this.lblPort = new System.Windows.Forms.Label();
            this.lblPassword = new System.Windows.Forms.Label();
            this.statusStrip.SuspendLayout();
            this.SuspendLayout();
            // 
            // txtLocal
            // 
            this.txtLocal.Location = new System.Drawing.Point(80, 56);
            this.txtLocal.Name = "txtLocal";
            this.txtLocal.Size = new System.Drawing.Size(248, 20);
            this.txtLocal.TabIndex = 0;
            // 
            // lblRemote
            // 
            this.lblRemote.Location = new System.Drawing.Point(8, 80);
            this.lblRemote.Name = "lblRemote";
            this.lblRemote.Size = new System.Drawing.Size(72, 24);
            this.lblRemote.TabIndex = 1;
            this.lblRemote.Text = "Remote file:";
            this.lblRemote.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // lblLocal
            // 
            this.lblLocal.Location = new System.Drawing.Point(8, 56);
            this.lblLocal.Name = "lblLocal";
            this.lblLocal.Size = new System.Drawing.Size(72, 24);
            this.lblLocal.TabIndex = 2;
            this.lblLocal.Text = "Local file:";
            this.lblLocal.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // txtRemote
            // 
            this.txtRemote.Location = new System.Drawing.Point(80, 80);
            this.txtRemote.Name = "txtRemote";
            this.txtRemote.Size = new System.Drawing.Size(248, 20);
            this.txtRemote.TabIndex = 1;
            this.txtRemote.Text = "/pub/example/readme.txt";
            // 
            // cmdDownload
            // 
            this.cmdDownload.Location = new System.Drawing.Point(192, 144);
            this.cmdDownload.Name = "cmdDownload";
            this.cmdDownload.Size = new System.Drawing.Size(64, 20);
            this.cmdDownload.TabIndex = 9;
            this.cmdDownload.Text = "Download";
            this.cmdDownload.Click += new System.EventHandler(this.cmdDownload_Click);
            // 
            // cmdUpload
            // 
            this.cmdUpload.Location = new System.Drawing.Point(120, 144);
            this.cmdUpload.Name = "cmdUpload";
            this.cmdUpload.Size = new System.Drawing.Size(64, 20);
            this.cmdUpload.TabIndex = 8;
            this.cmdUpload.Text = "Upload";
            this.cmdUpload.Click += new System.EventHandler(this.cmdUpload_Click);
            // 
            // pbProgress
            // 
            this.pbProgress.Location = new System.Drawing.Point(8, 144);
            this.pbProgress.Name = "pbProgress";
            this.pbProgress.Size = new System.Drawing.Size(104, 20);
            this.pbProgress.TabIndex = 8;
            // 
            // statusStrip
            // 
            this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.statusLabel});
            this.statusStrip.Location = new System.Drawing.Point(0, 166);
            this.statusStrip.Name = "statusStrip";
            this.statusStrip.Size = new System.Drawing.Size(334, 22);
            this.statusStrip.TabIndex = 9;
            // 
            // statusLabel
            // 
            this.statusLabel.Name = "statusLabel";
            this.statusLabel.Size = new System.Drawing.Size(90, 21);
            this.statusLabel.Text = "Ready";
            // 
            // cmdAbort
            // 
            this.cmdAbort.Enabled = false;
            this.cmdAbort.Location = new System.Drawing.Point(264, 144);
            this.cmdAbort.Name = "cmdAbort";
            this.cmdAbort.Size = new System.Drawing.Size(64, 20);
            this.cmdAbort.TabIndex = 10;
            this.cmdAbort.Text = "Abort";
            this.cmdAbort.Click += new System.EventHandler(this.cmdAbort_Click);
            // 
            // txtPort
            // 
            this.txtPort.Location = new System.Drawing.Point(272, 8);
            this.txtPort.Name = "txtPort";
            this.txtPort.Size = new System.Drawing.Size(56, 20);
            this.txtPort.TabIndex = 3;
            this.txtPort.Text = "22";
            // 
            // txtHostname
            // 
            this.txtHostname.Location = new System.Drawing.Point(80, 8);
            this.txtHostname.Name = "txtHostname";
            this.txtHostname.Size = new System.Drawing.Size(152, 20);
            this.txtHostname.TabIndex = 2;
            this.txtHostname.Text = "test.rebex.net";
            // 
            // txtUsername
            // 
            this.txtUsername.Location = new System.Drawing.Point(80, 32);
            this.txtUsername.Name = "txtUsername";
            this.txtUsername.Size = new System.Drawing.Size(88, 20);
            this.txtUsername.TabIndex = 6;
            this.txtUsername.Text = "demo";
            // 
            // txtPassword
            // 
            this.txtPassword.Location = new System.Drawing.Point(240, 32);
            this.txtPassword.Name = "txtPassword";
            this.txtPassword.PasswordChar = '*';
            this.txtPassword.Size = new System.Drawing.Size(88, 20);
            this.txtPassword.TabIndex = 7;
            this.txtPassword.Text = "password";
            // 
            // lblHostname
            // 
            this.lblHostname.Location = new System.Drawing.Point(8, 8);
            this.lblHostname.Name = "lblHostname";
            this.lblHostname.Size = new System.Drawing.Size(72, 24);
            this.lblHostname.TabIndex = 15;
            this.lblHostname.Text = "Hostname:";
            this.lblHostname.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // lblUsername
            // 
            this.lblUsername.Location = new System.Drawing.Point(8, 32);
            this.lblUsername.Name = "lblUsername";
            this.lblUsername.Size = new System.Drawing.Size(72, 24);
            this.lblUsername.TabIndex = 16;
            this.lblUsername.Text = "Username:";
            this.lblUsername.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // lblPort
            // 
            this.lblPort.Location = new System.Drawing.Point(240, 8);
            this.lblPort.Name = "lblPort";
            this.lblPort.Size = new System.Drawing.Size(32, 24);
            this.lblPort.TabIndex = 17;
            this.lblPort.Text = "Port:";
            this.lblPort.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // lblPassword
            // 
            this.lblPassword.Location = new System.Drawing.Point(176, 32);
            this.lblPassword.Name = "lblPassword";
            this.lblPassword.Size = new System.Drawing.Size(64, 24);
            this.lblPassword.TabIndex = 18;
            this.lblPassword.Text = "Password:";
            this.lblPassword.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // MainForm
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(334, 188);
            this.Controls.Add(this.lblPassword);
            this.Controls.Add(this.lblPort);
            this.Controls.Add(this.lblUsername);
            this.Controls.Add(this.lblHostname);
            this.Controls.Add(this.txtPassword);
            this.Controls.Add(this.txtUsername);
            this.Controls.Add(this.txtHostname);
            this.Controls.Add(this.txtPort);
            this.Controls.Add(this.cmdAbort);
            this.Controls.Add(this.statusStrip);
            this.Controls.Add(this.pbProgress);
            this.Controls.Add(this.cmdUpload);
            this.Controls.Add(this.cmdDownload);
            this.Controls.Add(this.txtRemote);
            this.Controls.Add(this.lblLocal);
            this.Controls.Add(this.lblRemote);
            this.Controls.Add(this.txtLocal);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "MainForm";
            this.Text = "Resumable SFTP Transfer";
            this.statusStrip.ResumeLayout(false);
            this.statusStrip.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() 
        {
            // set the trial license key
            Rebex.Licensing.Key = Rebex.Samples.TrialKey.Key;


            Application.Run(new MainForm());
        }

        // Download operation. This is run in a background thread.
        private void BackgroundDownload ()
        {
            Stream local = null;
            try
            {
                if (File.Exists (localPath))
                {
                    // If the local file exists, ask whether to resume transfer.
                    DialogResult res = ShowMessage ("Local file already exists. Do you want to resume the transfer? (If you answer 'no', the file will be overwritten.)", "Question", MessageBoxButtons.YesNoCancel);
                    switch (res)
                    {
                        case DialogResult.Yes:
                            // Open for writing and seek to the end if we are resuming
                            local = File.OpenWrite (localPath);
                            local.Seek (0, SeekOrigin.End);
                            break;
                        case DialogResult.No:
                            local = File.Create (localPath);
                            break;
                        default:
                            return;
                    }
                }
                else
                {
                    local = File.Create (localPath);
                }

                // Connect to the server.
                _ftp.Connect (hostname, port);
                // Log in.
                _ftp.Login (username, password);

                // Check whether the file exists and determine its length.
                SftpItem info = _ftp.GetInfo(remotePath, false);
                if (info == null || !info.IsFile)
                {
                    ShowMessage ("Unable to download, remote file does not exist.", "Error");
                    return;
                }
                _remoteOffset = info.Length;

                if (local.Length > _remoteOffset)
                {
                    ShowMessage ("Unable to resume transfer, local file is longer than remote file.", "Error");
                    return;
                }

                // Do the transfer if there data is available to download.
                if (local.Length < _remoteOffset)
                {
                    SetAbortState(true);
                    _remoteOffset = local.Position;
                    _ftp.GetFile(remotePath, local, _remoteOffset, -1);
                }

                ShowMessage ("Download finished.", "Info");
            }
            catch (SftpException ex)
            {
                if (ex.Status == SftpExceptionStatus.OperationAborted)
                    ShowMessage("Transfer aborted.", "Info");
                else
                    ShowMessage(ex.ToString(), "Error");
            }
            catch (Exception ex)
            {
                ShowMessage(ex.ToString(), "Error");
            }
            finally
            {
                if (local != null)
                    local.Close ();

                _ftp.Disconnect ();
                _backgroundThread = null;
                SafeInvoke(new OperationDoneDelegate(OperationDone));
            }

        }

        // Upload operation. Similar to download operation.
        private void BackgroundUpload ()
        {
            Stream local = null;
            try
            {
                local = File.OpenRead (localPath);

                // Connect to the server.
                _ftp.Connect (hostname, port);
                // Log in.
                _ftp.Login (username, password);

                // Check whether the file exists and determine its length.
                SftpItem info = _ftp.GetInfo(remotePath, false);
                if (info == null || !info.IsFile)
                {
                    _remoteOffset = -1;
                }
                else
                {
                    _remoteOffset = info.Length;
                }

                if (_remoteOffset >= 0)
                {
                    // If the remote file exists, ask whether to resume transfer.
                    DialogResult res = ShowMessage ("Remote file already exists. Do you want to resume the transfer? (If you answer 'no', the file will be overwritten.)", "Question", MessageBoxButtons.YesNoCancel);
                    switch (res)
                    {
                        case DialogResult.Yes:
                            break;
                        case DialogResult.No:
                            _remoteOffset = 0;
                            break;
                        default:
                            return;
                    }
                }
                else
                {
                    _remoteOffset = 0;
                }

                if (_remoteOffset > local.Length)
                {
                    ShowMessage ("Unable to resume transfer, remote file is longer than local file.", "Error");
                    return;
                }

                // Do the transfer if there data is available to upload.
                if (_remoteOffset < local.Length)
                {
                    SetAbortState(true);
                    local.Seek (_remoteOffset, SeekOrigin.Begin);
                    _ftp.PutFile (local, remotePath, _remoteOffset, -1);
                }

                ShowMessage ("Upload finished.", "Info");
            }
            catch (SftpException ex)
            {
                if (ex.Status == SftpExceptionStatus.OperationAborted)
                    ShowMessage("Transfer aborted.", "Info");
                else
                    ShowMessage(ex.ToString(), "Error");
            }
            catch (Exception ex)
            {
                ShowMessage(ex.ToString(), "Error");
            }
            finally
            {
                if (local != null)
                    local.Close ();

                _ftp.Disconnect ();
                _backgroundThread = null;
                SafeInvoke(new OperationDoneDelegate(OperationDone));
            }

        }

        private delegate void OperationDoneDelegate();

        private void OperationDone()
        {
            // Reset the progress bar
            pbProgress.Value = 0;

            // Enable editable boxes and buttons
            txtHostname.Enabled = true;
            txtPort.Enabled = true;
            txtUsername.Enabled = true;
            txtPassword.Enabled = true;
            txtRemote.Enabled = true;
            txtLocal.Enabled = true;
            cmdDownload.Enabled = true;
            cmdUpload.Enabled = true;

            SetAbortState(false);
        }

        // Starts the background transfer operation.
        private void StartBackgroundThread (ThreadStart threadStart)
        {
            if (_backgroundThread != null)
            {
                ShowMessage("Another background transfer is active.", "Error");
                return;
            }

            // Initialize an instance of SFTP class and set up events
            _ftp = new Sftp ();
            _ftp.StateChanged += StateChangedProxy;
            _ftp.TransferProgressChanged += TransferProgressProxy;

            // Initialize connection info
            hostname = txtHostname.Text;
            port = Convert.ToInt32(txtPort.Text);
            password = txtPassword.Text;
            username = txtUsername.Text;
            remotePath = txtRemote.Text;
            localPath = txtLocal.Text;
            // Disable editable boxes and buttons
            txtHostname.Enabled = false;
            txtPort.Enabled = false;
            txtPassword.Enabled = false;
            txtUsername.Enabled = false;
            txtRemote.Enabled = false;
            txtLocal.Enabled = false;
            cmdDownload.Enabled = false;
            cmdUpload.Enabled = false;
            // Start background thread
            _backgroundThread = new Thread (threadStart);
            _backgroundThread.Start ();
        }
        
        // Abort the background thread on exit if it is active.
        protected override void OnFormClosed (FormClosedEventArgs e)
        {
            base.OnFormClosed (e);

            Thread thread = _backgroundThread;
            if (thread != null)
                thread.Abort ();
        }

        private void cmdDownload_Click(object sender, System.EventArgs e)
        {
            StartBackgroundThread (new ThreadStart (BackgroundDownload));
        }

        private void cmdUpload_Click(object sender, System.EventArgs e)
        {
            StartBackgroundThread (new ThreadStart (BackgroundUpload));
        }

        // Abort transfer if background operation is active.
        private void cmdAbort_Click(object sender, System.EventArgs e)
        {
            if (_backgroundThread == null)
            {
                ShowMessage ("No background transfer is active.", "Error");
                return;
            }

            statusLabel.Text = "Aborting";

            _ftp.AbortTransfer ();
            _backgroundThread = null;
        }

        private void SetAbortState(bool enabled)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<bool>(SetAbortState), enabled);
                return;
            }

            cmdAbort.Enabled = enabled;
        }

        private DialogResult ShowMessage(string text, string title)
        {
            return ShowMessage(text, title, MessageBoxButtons.OK);
        }

        private DialogResult ShowMessage(string text, string title, MessageBoxButtons buttons)
        {
            // on Mono, MessageBox.Show has to be called from the application's main thread
            if (InvokeRequired)
                return (DialogResult)Invoke((ShowMessageDelegate)ShowMessage, text, title, buttons);

            return MessageBox.Show(text, title, buttons);
        }

        private delegate DialogResult ShowMessageDelegate(string text, string title, MessageBoxButtons buttons);
    }
}
