//  
//   Rebex Sample Code License
// 
//   Copyright 2026, 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 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 FTP Downloader";

        Ftp _ftp = null;
        Stream _output = null;
        string _user = null;
        string _pass = null;
        private System.Windows.Forms.CheckBox cbSecure;
        internal System.Windows.Forms.CheckBox cbAcceptAll;
        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 txtUrl;
        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 System.Windows.Forms.StatusStrip statusStrip;
        private System.Windows.Forms.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.txtUrl = 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.cbSecure = new System.Windows.Forms.CheckBox();
            this.cbAcceptAll = new System.Windows.Forms.CheckBox();
            this.statusStrip.SuspendLayout();
            this.SuspendLayout();
            // 
            // txtUrl
            // 
            this.txtUrl.Location = new System.Drawing.Point(40, 8);
            this.txtUrl.Name = "txtUrl";
            this.txtUrl.Size = new System.Drawing.Size(336, 20);
            this.txtUrl.TabIndex = 0;
            this.txtUrl.Text = "ftp://test.rebex.net/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(32, 20);
            this.Label1.TabIndex = 1;
            this.Label1.Text = "URL:";
            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(80, 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(40, 32);
            this.txtUser.Name = "txtUser";
            this.txtUser.Size = new System.Drawing.Size(120, 20);
            this.txtUser.TabIndex = 3;
            this.txtUser.Text = "demo";
            // 
            // txtPass
            // 
            this.txtPass.Location = new System.Drawing.Point(40, 56);
            this.txtPass.Name = "txtPass";
            this.txtPass.PasswordChar = '*';
            this.txtPass.Size = new System.Drawing.Size(120, 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(32, 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(32, 20);
            this.Label3.TabIndex = 6;
            this.Label3.Text = "Pass:";
            this.Label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
            // 
            // Label4
            // 
            this.Label4.Location = new System.Drawing.Point(168, 32);
            this.Label4.Name = "Label4";
            this.Label4.Size = new System.Drawing.Size(208, 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, 112);
            this.pbProgress.Name = "pbProgress";
            this.pbProgress.Size = new System.Drawing.Size(448, 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(80, 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, 132);
            this.statusStrip.Name = "statusStrip";
            this.statusStrip.Size = new System.Drawing.Size(458, 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";
            // 
            // cbSecure
            // 
            this.cbSecure.Location = new System.Drawing.Point(40, 80);
            this.cbSecure.Name = "cbSecure";
            this.cbSecure.Size = new System.Drawing.Size(80, 32);
            this.cbSecure.TabIndex = 11;
            this.cbSecure.Text = "TLS/SSL";
            // 
            // cbAcceptAll
            // 
            this.cbAcceptAll.Location = new System.Drawing.Point(136, 80);
            this.cbAcceptAll.Name = "cbAcceptAll";
            this.cbAcceptAll.Size = new System.Drawing.Size(312, 32);
            this.cbAcceptAll.TabIndex = 14;
            this.cbAcceptAll.Text = "Do not validate any server certificates - this is insecure but useful for testing" +
    ".";
            // 
            // MainForm
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(458, 148);
            this.Controls.Add(this.cbAcceptAll);
            this.Controls.Add(this.cbSecure);
            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.Controls.Add(this.txtUrl);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "MainForm";
            this.Text = "WinForm Sample FTP 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 URL and get the path
            Uri url;
            try
            {
                url = new Uri(txtUrl.Text);
            }
            catch (Exception err)
            {
                ShowMessage(err);
                return;
            }

            if (!url.Scheme.Equals("ftp"))
            {
                MessageBox.Show("URL must be an FTP URL.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (url.AbsolutePath.EndsWith("/"))
            {
                MessageBox.Show("URL does not specify a remote file.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            _remotePath = url.AbsolutePath;

            // 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 Ftp class and start connecting
                _ftp = new Ftp();
                _ftp.TransferProgressChanged += new EventHandler<FtpTransferProgressChangedEventArgs>(ftp_TransferProgressChanged);

                cmdAbort.Enabled = true;
                statusLabel.Text = "Connecting...";
                
                _ftp.Settings.SslAcceptAllCertificates = cbAcceptAll.Checked;
                
                SslMode security;
                if (cbSecure.Checked)
                    security = SslMode.Explicit;
                else
                    security = SslMode.None;
                _ftp.BeginConnect(url.Host, url.Port, security, new AsyncCallback(Finished), BackgroundOperation.ConnectFinished);
            }
            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 delegate void FinishedDelegate(IAsyncResult asyncResult);

        private void Finished(IAsyncResult asyncResult)
        {
            if (this.InvokeRequired)
            {
                SafeInvoke(new FinishedDelegate(Finished), new object[] { asyncResult });
                return;
            }

            try
            {
                switch ((BackgroundOperation)asyncResult.AsyncState)
                {
                    // This gets called when the connecting is finished.
                    case BackgroundOperation.ConnectFinished:
                        // End asynchronous operation
                        _ftp.EndConnect(asyncResult);

                        statusLabel.Text = "Logging in...";
                        _ftp.BeginLogin(_user, _pass, new AsyncCallback(Finished), BackgroundOperation.LoginFinished);
                        break;

                    // This gets called when logging in is finished.
                    case BackgroundOperation.LoginFinished:
                        // End asynchronous operation
                        _ftp.EndLogin(asyncResult);

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

                        // Download the file
                        statusLabel.Text = "Downloading...";
                        _ftp.BeginGetFile(_remotePath, _output, new AsyncCallback(Finished), BackgroundOperation.GetFileFinished);
                        break;

                    // This gets called when the transfer is finished.
                    case BackgroundOperation.GetFileFinished:
                        // End asynchronous operation and inform the user
                        _ftp.EndGetFile(asyncResult);
                        // Close the file
                        _output.Close();
                        _output = null;
                        MessageBox.Show("Transfer is finished.", Title, MessageBoxButtons.OK, MessageBoxIcon.Information);
                        statusLabel.Text = "Disconnecting...";
                        _ftp.BeginDisconnect(new AsyncCallback(Finished), BackgroundOperation.DisconnectFinished);
                        break;
                    // This gets called when the disconnect operation is finished.
                    case BackgroundOperation.DisconnectFinished:
                        // End asynchronous operation.
                        _ftp.EndDisconnect(asyncResult);
                        // Clean up
                        CleanUp();
                        break;
                }
            }
            catch (Exception err)
            {
                // If an error occurs, clean up and display the message
                CleanUp();
                ShowMessage(err);
            }
        }

        private void CleanUp()
        {
            // Dispose Ftp 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)
        {

            FtpException ftpErr = err as FtpException;
            if (ftpErr != null && ftpErr.Status == FtpExceptionStatus.AsyncError)
            {
                err = ftpErr.InnerException;
            }

            if (ftpErr != null && ftpErr.Status == FtpExceptionStatus.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 Ftp 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.
            Ftp tmp = _ftp;
            if (tmp != null)
            {
                tmp.Abort();
                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 ftp_TransferProgressChanged(object sender, FtpTransferProgressChangedEventArgs 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());
        }

    }
}