using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using Renci.SshNet.Sftp;
using System.Globalization;
namespace Renci.SshNet
{
    /// 
    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
    /// 
    public partial class SftpClient
    {
        #region SynchronizeDirectories
        /// 
        /// Synchronizes the directories.
        /// 
        /// The source path.
        /// The destination path.
        /// The search pattern.
        /// List of uploaded files.
        public IEnumerable SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern)
        {
            return InternalSynchronizeDirectories(sourcePath, destinationPath, searchPattern, null);
        }
        /// 
        /// Begins the synchronize directories.
        /// 
        /// The source path.
        /// The destination path.
        /// The search pattern.
        /// The async callback.
        /// The state.
        /// 
        /// An  that represents the asynchronous directory synchronization.
        /// 
        ///  is null.
        ///  is null or contains only whitespace.
        public IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, AsyncCallback asyncCallback, object state)
        {
            if (sourcePath == null)
                throw new ArgumentNullException("sourcePath");
            if (destinationPath.IsNullOrWhiteSpace())
                throw new ArgumentException("destDir");
            var asyncResult = new SftpSynchronizeDirectoriesAsyncResult(asyncCallback, state);
            this.ExecuteThread(() =>
            {
                try
                {
                    var result = this.InternalSynchronizeDirectories(sourcePath, destinationPath, searchPattern, asyncResult);
                    asyncResult.SetAsCompleted(result, false);
                }
                catch (Exception exp)
                {
                    asyncResult.SetAsCompleted(exp, false);
                }
            });
            return asyncResult;
        }
        /// 
        /// Ends the synchronize directories.
        /// 
        /// The async result.
        /// List of uploaded files.
        /// Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.
        public IEnumerable EndSynchronizeDirectories(IAsyncResult asyncResult)
        {
            var ar = asyncResult as SftpSynchronizeDirectoriesAsyncResult;
            if (ar == null || ar.EndInvokeCalled)
                throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.");
            // Wait for operation to complete, then return result or throw exception
            return ar.EndInvoke();
        }
        private IEnumerable InternalSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, SftpSynchronizeDirectoriesAsyncResult asynchResult)
        {
            if (destinationPath.IsNullOrWhiteSpace())
                throw new ArgumentException("destinationPath");
            if (!Directory.Exists(sourcePath))
                throw new FileNotFoundException(string.Format("Source directory not found: {0}", sourcePath));
            IList uploadedFiles = new List();
            DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
#if SILVERLIGHT
            var sourceFiles = sourceDirectory.EnumerateFiles(searchPattern);
#else
            var sourceFiles = sourceDirectory.GetFiles(searchPattern);
#endif
            if (sourceFiles == null || !sourceFiles.Any())
                return uploadedFiles;
            #region Existing Files at The Destination
            var destFiles = InternalListDirectory(destinationPath, null);
            Dictionary destDict = new Dictionary();
            foreach (var destFile in destFiles)
            {
                if (destFile.IsDirectory)
                    continue;
                destDict.Add(destFile.Name, destFile);
            }
            #endregion
            #region Upload the difference
            const Flags uploadFlag = Flags.Write | Flags.Truncate | Flags.CreateNewOrOpen;
            foreach (var localFile in sourceFiles)
            {
                bool isDifferent = !destDict.ContainsKey(localFile.Name);
                if (!isDifferent)
                {
                    SftpFile temp = destDict[localFile.Name];
                    //  TODO:   Use md5 to detect a difference
                    //ltang: File exists at the destination => Using filesize to detect the difference
                    isDifferent = localFile.Length != temp.Length;
                }
                if (isDifferent)
                {
                    var remoteFileName = string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", destinationPath, localFile.Name);
                    try
                    {
                        using (var file = File.OpenRead(localFile.FullName))
                        {
                            this.InternalUploadFile(file, remoteFileName, uploadFlag, null, null);
                        }
                        uploadedFiles.Add(localFile);
                        if (asynchResult != null)
                        {
                            asynchResult.Update(uploadedFiles.Count);
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(string.Format("Failed to upload {0} to {1}", localFile.FullName, remoteFileName), ex);
                    }
                }
            }
            #endregion
            return uploadedFiles;
        }
        #endregion
    }
}