//  
//   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.IO;
using System.Windows.Forms;
using System.Threading.Tasks;

using Rebex.Net;
using Rebex.IO;

namespace Rebex.Samples
{
    public class MainForm : System.Windows.Forms.Form
    {
        private enum BackgroundOperation
        {
            ConnectFinished,
            LoginFinished,
            GetFileFinished,
            DisconnectFinished,
        }

        const string Title = "WinForm Sample SFTP Downloader";

        Sftp ftp = null;
        Stream output = null;
        string user = null;
        string pass = null;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.TextBox txtPath;
        string server = null;
        private System.Windows.Forms.TextBox txtServer;
        string remotePath = null;

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

        // Required by the Windows Form Designer
        private System.ComponentModel.IContainer components = null;

        private System.Windows.Forms.Label Label1;
        private System.Windows.Forms.Label Label2;
        private System.Windows.Forms.Label Label3;
        private System.Windows.Forms.Label Label4;
        private System.Windows.Forms.SaveFileDialog dlgSaveFile;
        private System.Windows.Forms.Button cmdGet;
        private System.Windows.Forms.TextBox txtUser;
        private System.Windows.Forms.TextBox txtPass;
        private System.Windows.Forms.ProgressBar pbProgress;
        private System.Windows.Forms.Button cmdAbort;
        private StatusStrip statusStrip;
        private ToolStripStatusLabel statusLabel;

        /// <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.txtPath = new System.Windows.Forms.TextBox();
            this.Label1 = new System.Windows.Forms.Label();
            this.cmdGet = new System.Windows.Forms.Button();
            this.txtUser = new System.Windows.Forms.TextBox();
            this.txtPass = new System.Windows.Forms.TextBox();
            this.Label2 = new System.Windows.Forms.Label();
            this.Label3 = new System.Windows.Forms.Label();
            this.Label4 = new System.Windows.Forms.Label();
            this.pbProgress = new System.Windows.Forms.ProgressBar();
            this.dlgSaveFile = new System.Windows.Forms.SaveFileDialog();
            this.cmdAbort = new System.Windows.Forms.Button();
            this.statusStrip = new System.Windows.Forms.StatusStrip();
            this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
            this.txtServer = new System.Windows.Forms.TextBox();
            this.label5 = new System.Windows.Forms.Label();
            this.statusStrip.SuspendLayout();
            this.SuspendLayout();
            // 
            // txtPath
            // 
            this.txtPath.Location = new System.Drawing.Point(192, 8);
            this.txtPath.Name = "txtPath";
            this.txtPath.Size = new System.Drawing.Size(176, 20);
            this.txtPath.TabIndex = 0;
            this.txtPath.Text = "/pub/example/readme.txt";
            // 
            // Label1
            // 
            this.Label1.Location = new System.Drawing.Point(8, 8);
            this.Label1.Name = "Label1";
            this.Label1.Size = new System.Drawing.Size(48, 20);
            this.Label1.TabIndex = 1;
            this.Label1.Text = "Server:";
            this.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // cmdGet
            // 
            this.cmdGet.Location = new System.Drawing.Point(376, 8);
            this.cmdGet.Name = "cmdGet";
            this.cmdGet.Size = new System.Drawing.Size(56, 20);
            this.cmdGet.TabIndex = 2;
            this.cmdGet.Text = "Get";
            this.cmdGet.Click += new System.EventHandler(this.cmdGet_Click);
            // 
            // txtUser
            // 
            this.txtUser.Location = new System.Drawing.Point(56, 32);
            this.txtUser.Name = "txtUser";
            this.txtUser.Size = new System.Drawing.Size(100, 20);
            this.txtUser.TabIndex = 3;
            this.txtUser.Text = "demo";
            // 
            // txtPass
            // 
            this.txtPass.Location = new System.Drawing.Point(56, 56);
            this.txtPass.Name = "txtPass";
            this.txtPass.PasswordChar = '*';
            this.txtPass.Size = new System.Drawing.Size(100, 20);
            this.txtPass.TabIndex = 4;
            this.txtPass.Text = "password";
            // 
            // Label2
            // 
            this.Label2.Location = new System.Drawing.Point(8, 32);
            this.Label2.Name = "Label2";
            this.Label2.Size = new System.Drawing.Size(48, 20);
            this.Label2.TabIndex = 5;
            this.Label2.Text = "User:";
            this.Label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // Label3
            // 
            this.Label3.Location = new System.Drawing.Point(8, 56);
            this.Label3.Name = "Label3";
            this.Label3.Size = new System.Drawing.Size(48, 20);
            this.Label3.TabIndex = 6;
            this.Label3.Text = "Pass:";
            this.Label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // Label4
            // 
            this.Label4.Location = new System.Drawing.Point(192, 32);
            this.Label4.Name = "Label4";
            this.Label4.Size = new System.Drawing.Size(176, 48);
            this.Label4.TabIndex = 7;
            this.Label4.Text = "This sample demonstrates using asynchronous methods. It does not block the user i" +
    "nterface at all!";
            // 
            // pbProgress
            // 
            this.pbProgress.Location = new System.Drawing.Point(8, 80);
            this.pbProgress.Name = "pbProgress";
            this.pbProgress.Size = new System.Drawing.Size(424, 20);
            this.pbProgress.TabIndex = 8;
            // 
            // dlgSaveFile
            // 
            this.dlgSaveFile.FileName = "doc1";
            // 
            // cmdAbort
            // 
            this.cmdAbort.Enabled = false;
            this.cmdAbort.Location = new System.Drawing.Point(376, 32);
            this.cmdAbort.Name = "cmdAbort";
            this.cmdAbort.Size = new System.Drawing.Size(56, 20);
            this.cmdAbort.TabIndex = 9;
            this.cmdAbort.Text = "Abort";
            this.cmdAbort.Click += new System.EventHandler(this.cmdAbort_Click);
            // 
            // statusStrip
            // 
            this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.statusLabel});
            this.statusStrip.Location = new System.Drawing.Point(0, 109);
            this.statusStrip.Name = "statusStrip";
            this.statusStrip.Size = new System.Drawing.Size(442, 16);
            this.statusStrip.SizingGrip = false;
            this.statusStrip.TabIndex = 10;
            // 
            // statusLabel
            // 
            this.statusLabel.Name = "statusLabel";
            this.statusLabel.Size = new System.Drawing.Size(90, 16);
            this.statusLabel.Text = "Ready";
            // 
            // txtServer
            // 
            this.txtServer.Location = new System.Drawing.Point(56, 8);
            this.txtServer.Name = "txtServer";
            this.txtServer.Size = new System.Drawing.Size(100, 20);
            this.txtServer.TabIndex = 11;
            this.txtServer.Text = "test.rebex.net";
            // 
            // label5
            // 
            this.label5.Location = new System.Drawing.Point(160, 8);
            this.label5.Name = "label5";
            this.label5.Size = new System.Drawing.Size(32, 23);
            this.label5.TabIndex = 12;
            this.label5.Text = "Path:";
            this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // MainForm
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(442, 125);
            this.Controls.Add(this.label5);
            this.Controls.Add(this.txtServer);
            this.Controls.Add(this.txtPath);
            this.Controls.Add(this.statusStrip);
            this.Controls.Add(this.cmdAbort);
            this.Controls.Add(this.pbProgress);
            this.Controls.Add(this.Label4);
            this.Controls.Add(this.Label3);
            this.Controls.Add(this.Label2);
            this.Controls.Add(this.txtPass);
            this.Controls.Add(this.txtUser);
            this.Controls.Add(this.cmdGet);
            this.Controls.Add(this.Label1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "MainForm";
            this.Text = "WinForm Sample SFTP Downloader";
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_Closing);
            this.statusStrip.ResumeLayout(false);
            this.statusStrip.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

    #endregion

        private void cmdGet_Click(object sender, System.EventArgs e)
        {
            // Validate server information
            if (txtServer.Text.Trim() == "") 
            {
                MessageBox.Show("Server address can't be empty.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (txtPath.Text.Trim() == "") 
            {
                MessageBox.Show("Path can't be empty.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (txtPath.Text.EndsWith("/"))
            {
                MessageBox.Show("Path does not specify a remote file.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            server = txtServer.Text;
            remotePath = txtPath.Text;

            // Display File Save dialog and let user select destination
            dlgSaveFile.FileName = System.IO.Path.GetFileName(Path.GetFileName(remotePath));
            dlgSaveFile.Filter = "All files (*.*)|*.*";
            if (dlgSaveFile.ShowDialog() != DialogResult.OK)
            {
                return;											   
            }

            user = txtUser.Text;
            pass = txtPass.Text;
            try
            {
                cmdGet.Enabled = false;
                // Create an instance of Sftp class and start connecting
                ftp = new Sftp();
                ftp.TransferProgressChanged += new EventHandler<SftpTransferProgressChangedEventArgs>(sftp_TransferProgressChanged);
                statusLabel.Text = "Connecting...";
                ftp.ConnectAsync(server, Sftp.DefaultPort, BackgroundOperation.ConnectFinished).ContinueWith(TaskFinished);
            }
            catch (Exception err)
            {
                // If an error occurs, clean up and display the message
                CleanUp();
                ShowMessage(err);
            }
        }

        /// <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)
            {
            }
        }		

        private void TaskFinished(Task task)
        {
            if (this.InvokeRequired)
            {
                SafeInvoke(new Action<Task>(TaskFinished), task);
                return;
            }
            try
            {
                if (task.IsFaulted)
                    throw task.Exception;

                if (task.IsCanceled)
                    throw new OperationCanceledException("Operation has been canceled.");

                switch ((BackgroundOperation)task.AsyncState)
                {
                    // This gets called when the connecting is finished.
                    case BackgroundOperation.ConnectFinished:						
                        statusLabel.Text = "Logging in...";
                        ftp.LoginAsync(user, pass, BackgroundOperation.LoginFinished).ContinueWith(TaskFinished);
                        break;

                    // This gets called when logging in is finished.
                    case BackgroundOperation.LoginFinished:
                        statusLabel.Text = "Getting file length...";

                        // Open the file
                        output = dlgSaveFile.OpenFile();
                        if (output == null)
                            throw new Exception("Unable to open file.");

                        // Download the file
                        statusLabel.Text = "Downloading...";
                        ftp.GetFileAsync(remotePath, output, BackgroundOperation.GetFileFinished).ContinueWith(TaskFinished);
                        cmdAbort.Enabled = true;
                        break;


                    // This gets called when the transfer is finished.
                    case BackgroundOperation.GetFileFinished:
                        cmdAbort.Enabled = false;
                        // Close the file
                        output.Close();
                        output = null;
                        MessageBox.Show("Transfer is finished.", Title, MessageBoxButtons.OK, MessageBoxIcon.Information);
                        statusLabel.Text = "Disconnecting...";
                        ftp.DisconnectAsync(BackgroundOperation.DisconnectFinished).ContinueWith(TaskFinished);
                        break;
                    // This gets called when the disconnect operation is finished.
                    case BackgroundOperation.DisconnectFinished:
                        // Clean up
                        CleanUp();
                        break;
                }
            }
            catch (Exception err)
            {
                // If an error occurs, clean up and display the message
                CleanUp();
                ShowMessage(err);
            }
        }
        
        private void CleanUp()
        {
            // Dispose Sftp object
            if (ftp != null)
            {
                ftp.Dispose();
                ftp = null;
            }

            if (output != null)
            {
                output.Close();
                output = null;
            }

            // Set the window title back to the original title
            Text = Title;

            // Reset the progress bar
            pbProgress.Value = 0;

            // Reset the status bar
            statusLabel.Text = "Ready";

            // Disable Abort button
            cmdAbort.Enabled = false;

            // Enable Get button
            cmdGet.Enabled = true;
        }

        private void ShowMessage(Exception err)
        {
            AggregateException aggregateEx = err as AggregateException;
            if (aggregateEx != null)
            {
                foreach (Exception ex in aggregateEx.InnerExceptions)
                {
                    ShowMessage(ex);
                }
                return;
            }
            SftpException ftpErr = err as SftpException;
            if (ftpErr != null && ftpErr.Status == SftpExceptionStatus.AsyncError)
            {
                err = ftpErr.InnerException;
            }

            if (ftpErr != null && ftpErr.Status == SftpExceptionStatus.OperationAborted)
            {
                MessageBox.Show(err.Message, Title, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show(err.Message, Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Abort()
        {
            // The ftp object may have been set to nothing while we were waiting
            // for user's response in MainForm_Closing callback. It might be being
            // set to nothing even at this moment by another thread. By using our
            // own temporary object (which cannot be set nothing by other threads),
            // we are able to make this method thread-safe.
            // Also please note that even though the Sftp object itself might have
            // been disconnected and disposed, it is still safe to call the Abort
            // method - it will do nothing if no asynchronous operation is taking
            // place. This allows us to avoid using synchronization classes
            // such as Monitor or Mutex and still being thread-safe.
            Sftp tmp = ftp;
            if (tmp != null)
            {
                tmp.AbortTransfer();
                statusLabel.Text = "Aborting...";
                cmdAbort.Enabled = false;
            }
        }

        private void cmdAbort_Click(System.Object sender, System.EventArgs e) 
        {
            Abort();
        }

        private void MainForm_Closing(object sender, System.Windows.Forms.FormClosingEventArgs e)
        {
            // If a form is closing and transfer is in progress, ask the user what to do
            if (ftp == null)
            {
                return;
            }
            DialogResult res = MessageBox.Show("Transfer is in progress, do you want to abort it?", Title, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
            if (res != DialogResult.Yes)
            {
                e.Cancel = true;
            }
            else
            {
                Abort();
            }
        }

        void sftp_TransferProgressChanged(object sender, SftpTransferProgressChangedEventArgs e)
        {
            if (e.TransferState == TransferProgressState.DataBlockProcessed)
            {
                // Gets called if there is a progress in transfer, so update our display accordingly
                pbProgress.Value = (int)e.ProgressPercentage;
                Text = String.Format("{0} bytes transferred.", e.CurrentFileBytesTransferred);
            }
        }

        [STAThread]
        static void Main() 
        {
            // set the trial license key
            Rebex.Licensing.Key = Rebex.Samples.TrialKey.Key;


            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }
    }
}