// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Provider;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure;
using System.Text;
using Microsoft.Samples.CloudDrive.Extensions;

namespace Microsoft.Samples.CloudDrive
{
    [CmdletProvider("BlobDrive", ProviderCapabilities.None | ProviderCapabilities.Credentials | ProviderCapabilities.ShouldProcess | ProviderCapabilities.Filter)]
    public class BlobDriveProvider : NavigationCmdletProvider
    {
        public const string ProviderName = "BlobDrive";
        public const int ElementsPerCall = 1000;

        #region Blob Drive State
        internal class CloudDriveInfo : PSDriveInfo
        {
            public CloudBlobClient Client { get; internal set; }

            public CloudDriveInfo(PSDriveInfo driveInfo)
                : base(driveInfo)
            { }
        }
        #endregion

        #region Drive creation
        /// <summary>
        /// Create a new drive.  Create a connection to the cloud and stores the client in the CloudDriveInfo.
        /// </summary>
        /// <param name="drive">
        /// Information describing the drive to add.
        /// </param>
        /// <returns>The added drive.</returns>
        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            if (drive == null)
            {
                Exception ex = new ArgumentException("Drive info are not provided");
                WriteError(new ErrorRecord(ex, "NullDrive", ErrorCategory.InvalidArgument, null));
                throw ex;
            }

            // check if drive root is not null or empty
            // and if its an existing file
            if (string.IsNullOrEmpty(drive.Root))
            {
                Exception ex = new ArgumentException("Drive root is null");
                WriteError(new ErrorRecord(ex, "NoRoot", ErrorCategory.InvalidArgument, null));
                throw ex;
            }

            // Make sure we got credentials
            if (drive.Credential.UserName == null || drive.Credential.Password == null)
            {
                ThrowTerminatingError(new ErrorRecord(new ArgumentException("drive.Credentials not present"), "NoCredentials", ErrorCategory.InvalidArgument, drive.Credential));
                return null;
            }

            // Get the credentials
            CloudDriveInfo utiliteDrive = new CloudDriveInfo(drive);

            System.Net.NetworkCredential cred = Credential.GetNetworkCredential();

            if (cred.UserName.Equals(CloudStorageAccount.DevelopmentStorageAccount.Credentials.AccountName, StringComparison.OrdinalIgnoreCase))
            {
                // Development Storage
                utiliteDrive.Client = CloudStorageAccountStorageClientExtensions.CreateCloudBlobClient(CloudStorageAccount.DevelopmentStorageAccount);
            }
            else
            {
                utiliteDrive.Client = CloudStorageAccountStorageClientExtensions.CreateCloudBlobClient(new CloudStorageAccount(new StorageCredentialsAccountAndKey(cred.UserName, cred.Password), false));
            }

            utiliteDrive.Client.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));
            // utiliteDrive.Client.WriteBlockSizeInBytes = 1024 * 64;

            WriteVerbose(ProviderName + " initialized.");

            return utiliteDrive;
        }

        /// <summary>
        /// Removes a drive from the provider.
        /// </summary>
        /// <param name="drive">The drive to remove.</param>
        /// <returns>The drive removed.</returns>
        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
            // check if drive object is null
            if (drive == null)
            {
                WriteError(new ErrorRecord(new ArgumentNullException("drive"), "NullDrive", ErrorCategory.InvalidArgument, drive));
                return null;
            }

            CloudDriveInfo specificDrive = drive as CloudDriveInfo;

            if (specificDrive != null)
            {
                specificDrive.Client = null;
            }

            return specificDrive;
        } // RemoveDrive
        #endregion


        #region Item APIs
        /// <summary>
        /// Test to see if the specified path is syntactically valid.
        /// </summary>
        /// <param name="path">The path to validate.</param>
        /// <returns>True if the specified path is valid.</returns>
        protected override bool IsValidPath(string path)
        {
            if (String.IsNullOrEmpty(path))
            {
                return false;
            }

            string container, itemName;
            CloudDriveUtilities.PathType type = SplitPath(ref path, out container, out itemName);

            if (type == CloudDriveUtilities.PathType.Drive) // We got purely a drive
            {
                return true;
            }

            if (container.IndexOfAny(@"!/\".ToCharArray()) != -1)
                return false;

            if (itemName.StartsWith("/", StringComparison.Ordinal))
                return false;

            return true;
        }

        /// <summary>
        /// Test to see if the specified item exists. 
        /// </summary>
        /// <param name="path">The path to the item to verify.</param>
        /// <returns>True if the item is found.</returns>
        protected override bool ItemExists(string path)
        {
            // EXTENSION POINT: Modify this function for wildcard support
            CloudDriveInfo specificDrive;
            string containerName, itemName;
            CloudBlobClient client;
            CloudDriveUtilities.PathType type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            switch (type)
            {
                case CloudDriveUtilities.PathType.Drive:
                    return true;
                case CloudDriveUtilities.PathType.Container:
                    {
                        WriteDebug(String.Format("Checking existence of container {0}", containerName));
                        CloudBlobContainer container = client.GetContainerReference(containerName);

                        return container.Exists();
                    }

                case CloudDriveUtilities.PathType.Item:
                    {
                        WriteDebug(String.Format("Checking existence of file {0}", itemName));
                        return PathExists(client, containerName + "/" + itemName);
                    }
                default:
                    {
                        WriteDebug("Received an invalid path");
                        return false;
                    }
            }
        }

        /// <summary>
        /// Retrieves the item representing the object at the specified path.
        /// </summary>
        /// <param name="path">The path to the item to return.</param>
        protected override void GetItem(string path)
        {
            if (!this.ItemExists(path))
            {
                WriteWarning("Item does not exist.");
                return;
            }

            // EXTENSION POINT: Modify this function for wildcard support
            CloudDriveInfo specificDrive;
            string containerName, itemName;
            CloudBlobClient client;
            CloudDriveUtilities.PathType type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            // check if the path represented is a drive
            if (CloudDriveUtilities.PathIsDrive(path, this.PSDriveInfo.Root))
            {
                WriteItemObject(this.PSDriveInfo, path, true);
                return;
            }

            CloudBlobContainer container = client.GetContainerReference(containerName);

            string fullPath = container.Uri.AbsoluteUri + CloudDriveUtilities.PathSeparator + itemName;
            WriteVerbose(String.Format("Retrieving item {0}", fullPath));

            switch (type)
            {
                case CloudDriveUtilities.PathType.Container:
                    container.FetchAttributes();
                    WriteItemObject(new ContainerInfo(container, container.Properties, fullPath), path, true);
                    break;
                case CloudDriveUtilities.PathType.Item:
                    // If the path ends with a '\', we want to get a folder even if the blob with same name exists.
                    if (path.EndsWith(CloudDriveUtilities.PathSeparator.ToString()) && IsItemContainer(path))
                    {
                        container.FetchAttributes();
                        WriteItemObject(new FolderInfo(container, fullPath), path, true);
                    }
                    else
                    {
                        CloudBlob blob = container.GetBlobReference(itemName);
                        if (blob.Exists())
                        {
                            WriteItemObject(new BlobInfo(container, fullPath, blob), path, false);
                        }
                        else
                        {
                            // throw new ArgumentException("Argument path is not a valid path");
                            WriteWarning("Item does not exist.");
                        }
                    }
                    break;
                default:
                    WriteDebug("Received an invalid path");
                    break;
            }
        } // GetItem

        #endregion

        #region Container Overloads

        /// <summary>
        /// Retrieves the objects (blobs/folders/containers) at the specified path
        /// </summary>
        /// <param name="path">The path to the container/prefix</param>
        /// <param name="recurse">True to return all child items recursively.
        /// </param>
        protected override void GetChildItems(string path, bool recurse)
        {
            // EXTENSION POINT: Modify this function for wildcard support
            GetChildInfo(path, false, recurse);
        }

        /// <summary>
        /// Retrieves the names of the objects (blobs/folders/containers) at the specified path
        /// </summary>
        /// <param name="path">The root path.</param>
        /// <param name="returnContainers">Not used.</param>
        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            // EXTENSION POINT: Modify this function for wildcard support

            GetChildInfo(path, true, false);
        }

        /// <summary>
        /// Retrieves the information about all of the objects under the specified path
        /// </summary>
        /// <param name="path">The path from which to get the child items</param>
        /// <param name="nameOnly">Retrieve names only or full blob representation</param>
        /// <param name="recurse">Recurse for all child 'folders'</param>
        private void GetChildInfo(string path, bool nameOnly, bool recurse)
        {
            WriteVerbose(String.Format("Getting child nodes for {0}", path));
            CloudDriveInfo specificDrive;
            CloudBlobClient client;
            string containerName, itemName;
            CloudDriveUtilities.PathType type;
            type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            List<string> childItems = new List<string>();

            switch (type)
            {
                // If the path represented is a drive, then the child items are containers
                case CloudDriveUtilities.PathType.Drive:
                    {
                        foreach (CloudBlobContainer container in client.ListContainers())
                        {
                            string itemPath = path + CloudDriveUtilities.PathSeparator + container.Name;
                            if (nameOnly)
                            {
                                WriteItemObject(container.Name, itemPath, true);
                            }
                            else
                            {
                                container.FetchAttributes();
                                WriteItemObject(new ContainerInfo(container, container.Properties, container.Uri.AbsoluteUri), itemPath, true);
                            }

                            if (recurse)
                            {
                                childItems.Add(itemPath);
                            }
                        }
                        break;
                    }
                case CloudDriveUtilities.PathType.Item: // This could be folder or item - we could only get children for folders
                case CloudDriveUtilities.PathType.Container:
                    {
                        // If we have a folder, append a trailing '/' separator to ensure we get the children.
                        // string prefix = itemName == null ? itemName : itemName + CloudDriveUtilities.PathSeparator;
                        string prefix = string.IsNullOrEmpty(itemName) ? containerName + CloudDriveUtilities.PathSeparator : itemName + CloudDriveUtilities.PathSeparator;
                        // If someone provided a filter, add it to the prefix
                        prefix = AddFilter(prefix);

                        var blobItems = new List<IListBlobItem>();

                        try
                        {
                            blobItems = client.ListBlobsWithPrefix(prefix).ToList();
                        }
                        catch (StorageException)
                        { }

                        foreach (IListBlobItem blobItem in blobItems)
                        {
                            CloudBlobDirectory dir = blobItem as CloudBlobDirectory;
                            if (dir != null) // We have a folder
                            {
                                string itemPath = path + Path.DirectorySeparatorChar + CloudDriveUtilities.GetFileNameFromBlobName(dir.Uri.AbsolutePath);
                                string fullPath = String.Format("{0}{1}{2}", dir.Uri.AbsolutePath, CloudDriveUtilities.PathSeparator, dir);
                                if (nameOnly)
                                {
                                    string name = CloudDriveUtilities.GetFileNameFromBlobName(dir.Uri.AbsolutePath); // The behavior for finding the file names is appropriate for finding folders as well
                                    WriteItemObject(name, fullPath, true);
                                }
                                else
                                {
                                    dir.Container.FetchAttributes();
                                    WriteItemObject(new FolderInfo(dir.Container, fullPath), itemPath, true);
                                }

                                //This path has a better look for the user. It doesn't contain the vip address and account name
                                string cosmeticPath = String.Format("{0}:{1}{2}{1}{3}", specificDrive.Name, CloudDriveUtilities.PathSeparator, dir.Container.Name, dir);
                                string cosmeticRooted = CloudDriveUtilities.ConvertFullPathToRootedPath(cosmeticPath, PSDriveInfo.Root, PSDriveInfo.Name);
                                if (recurse)
                                    childItems.Add(cosmeticRooted);
                            }
                            else //otherwise it's a blob object
                            {
                                CloudBlob blob = blobItem as CloudBlob;
                                string name = CloudDriveUtilities.GetFileNameFromBlobName(blob.Uri.AbsoluteUri);
                                string itemPath = path + Path.DirectorySeparatorChar + name;
                                if (nameOnly)
                                {
                                    WriteItemObject(name, path, false);
                                }
                                else
                                {
                                    string fullPath = String.Format("{0}:{1}{2}{1}{3}", specificDrive.Name, CloudDriveUtilities.PathSeparator, blob.Container.Name, blob.Uri.AbsoluteUri);
                                    blob.FetchAttributes();
                                    WriteItemObject(new BlobInfo(blob.Container, fullPath, blob), itemPath, false);
                                }
                            }
                        }

                        break;
                    }
                default:
                    WriteDebug("Received an invalid path type");
                    break;
            }

            // Recurse through all of the child items
            foreach (string childPath in childItems)
            {
                GetChildInfo(childPath, nameOnly, recurse);
            }
        }

        /// <summary>
        /// Determines if the specified path has child items.
        /// </summary>
        /// <param name="path">The path to examine.</param>
        /// <returns>
        /// True if the specified path has child items.
        /// </returns>
        protected override bool HasChildItems(string path)
        {
            WriteVerbose(String.Format("Checking for children in {0}", path));
            CloudDriveInfo specificDrive;
            string containerName, itemName;
            CloudBlobClient client;
            CloudDriveUtilities.PathType type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            if (string.IsNullOrEmpty(path) ||
                CloudDriveUtilities.PathIsDrive(path, this.PSDriveInfo.Root))
            {
                return true;
            }

            if (specificDrive == null)
            {
                return false;
            }

            switch (type)
            {
                case CloudDriveUtilities.PathType.Drive:
                case CloudDriveUtilities.PathType.Container:
                    return true;
                case CloudDriveUtilities.PathType.Item:
                    return PathExists(client, itemName + CloudDriveUtilities.PathSeparator);
                default:
                    Exception ex = new InvalidDataException("Invalid path");
                    ThrowTerminatingError(new ErrorRecord(ex, "InvalidPathType", ErrorCategory.InvalidArgument, path));
                    return false;
            }
        } // HasChildItems

        /// <summary>
        /// Creates a new item at the specified path.
        /// </summary>
        /// 
        /// <param name="path"> 
        /// The path to the new item.
        /// </param>
        /// <param name="itemTypeName">
        /// Type for the object to create. (Ignored)
        /// </param>
        /// <param name="newItemValue">
        /// Path to upload the data from.
        /// </param>
        protected override void NewItem(string path, string itemTypeName, object newItemValue)
        {
            WriteVerbose(String.Format("Creating new item at {0}", path));

            CloudDriveInfo specificDrive;
            string containerName, itemName;
            CloudBlobClient client;
            CloudDriveUtilities.PathType type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            // Remove
            if (type != CloudDriveUtilities.PathType.Container && // We can handle new containers to be created empty, but no empty 'folders'
                !string.IsNullOrEmpty(itemTypeName) && itemTypeName.Equals("directory"))
            {
                Exception ex = new ArgumentException("Storage Service does not support empty directories. Directories only exist while they contain files.");
                WriteError(new ErrorRecord(ex, "InvalidArgument", ErrorCategory.InvalidArgument, itemTypeName));
                return;
            }

            RuntimeDefinedParameterDictionary paramDict = DynamicParameters as RuntimeDefinedParameterDictionary;

            switch (type)
            {
                case CloudDriveUtilities.PathType.Container:
                    if (newItemValue != null)
                    {
                        Exception ex = new ArgumentException("Storage Service does not support files outside containers. Create a container and then copy to it.");
                        WriteError(new ErrorRecord(ex, "InvalidArgument", ErrorCategory.InvalidArgument, itemTypeName));
                        return;
                    }

                    if (ShouldProcess(containerName, "New"))
                    {
                        WriteVerbose("Creating new container");
                        // Check for ACL set to Public
                        BlobContainerPublicAccessType acls = BlobContainerPublicAccessType.Off;
                        if (paramDict != null && paramDict["Public"].IsSet)
                            acls = BlobContainerPublicAccessType.Container;

                        CloudBlobContainer blobContainer = client.GetContainerReference(containerName);
                        blobContainer.CreateIfNotExist();//Check the bool value and act upon its
                        BlobContainerPermissions permissions = blobContainer.GetPermissions();
                        permissions.PublicAccess = acls;
                        blobContainer.SetPermissions(permissions);

                        // To allow for PassThru, write an item.
                        WriteItemObject(new ContainerInfo(blobContainer, blobContainer.Properties, blobContainer.Uri.AbsoluteUri),
                                        blobContainer.Uri.AbsoluteUri, true);

                    }
                    break;

                case CloudDriveUtilities.PathType.Item:
                    if (paramDict != null && paramDict["Public"].IsSet)
                        WriteWarning("The parameter -Public is only applicable to containers");

                    CloudBlobContainer container = client.GetContainerReference(containerName);
                    if (newItemValue == null)
                    {
                        if (ShouldProcess(itemName, "New"))
                        {
                            WriteVerbose("Creating new Empty Blob ");

                            CloudBlob blob = client.GetBlobReference(itemName);

                            using (Stream tmpStream = new MemoryStream(0))
                            {
                                if (blob.Properties.Length > 0)
                                {
                                    if (ShouldContinue(String.Format("Would you like to overwrite \"{0}\"?", CloudDriveUtilities.DenormalizePath(containerName + CloudDriveUtilities.PathSeparator + itemName)), "Destination file already exists"))
                                    {
                                        tmpStream.Position = 0;
                                        blob.UploadFromStream(tmpStream);
                                    }
                                }
                            }
                            // To allow for PassThru, write an item.
                            WriteItemObject(new BlobInfo(container, path, blob), path, false);
                        }
                    }
                    else
                    {
                        WriteVerbose("Uploading a Blob");

                        UploadItem(newItemValue, itemName, container);
                    }
                    break;
                default:
                    WriteDebug("Invalid path provided");
                    break;
            }
        }

        /// <summary>
        /// Define the dynamic parameter for NewItem. Creates the new parameter "Public" that makes the container public.
        /// </summary>
        /// <param name="path">Not used</param>
        /// <param name="itemTypeName">Not used</param>
        /// <param name="newItemValue">Not Used</param>
        /// <returns>RuntimeDefinedParamterDictionary with "Public" parameter</returns>
        protected override object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue)
        {
            RuntimeDefinedParameterDictionary dynamicParameters = new RuntimeDefinedParameterDictionary();
            Collection<Attribute> atts = new Collection<Attribute>();
            ParameterAttribute parameterAttribute = new ParameterAttribute();
            parameterAttribute.Mandatory = false;
            parameterAttribute.HelpMessage = "Sets the container as public. Only applicable to containers.";
            atts.Add(parameterAttribute);
            dynamicParameters.Add("Public", new RuntimeDefinedParameter("Public", typeof(SwitchParameter), atts));
            return dynamicParameters;
        }


        /// <summary>
        /// Removes (expires) the item at the specified path
        /// </summary>
        /// <param name="path">
        /// The path to the item to remove.
        /// </param>
        /// <param name="recurse">
        /// True if all children in a subtree should be removed, false if only
        /// the item at the specified path should be removed. Is applicable
        /// only for container (table) items. Its ignored otherwise (even if
        /// specified).
        /// </param>
        /// <remarks>
        /// There are no elements in this store which are hidden from the user.
        /// Hence this method will not check for the presence of the Force
        /// parameter
        /// </remarks>
        /// 
        protected override void RemoveItem(string path, bool recurse)
        {
            WriteVerbose(String.Format("Removing {0}", path));

            // EXTENSION POINT: Modify this function for wildcard support

            CloudDriveInfo specificDrive;
            string containerName, itemName;
            CloudBlobClient client;
            CloudDriveUtilities.PathType type = GetStorageFromPath(ref path, out specificDrive, out client, out containerName, out itemName);

            if (ShouldProcess("path", VerbsCommon.Remove))
            {
                CloudBlobContainer container = client.GetContainerReference(containerName);

                if (type == CloudDriveUtilities.PathType.Container)
                {
                    if (this.Force || ShouldContinue(String.Format("Are you sure you want to delete {0} and everything within it?", containerName), "Confirm Delete"))
                    {
                        container.Delete();
                    }
                }
                else
                {
                    string newPath = CloudDriveUtilities.GetFullPath(containerName, itemName);
                    if (IsItemContainer(newPath))
                    {
                        if (!recurse && ShouldContinue(String.Format("Are you sure you want to delete {0} and everything within it?", CloudDriveUtilities.DenormalizePath(newPath)), "Confirm Delete"))
                        {
                            recurse = true;
                        }

                        WriteDebug("Deleting a folder");

                        string prefix = AddFilter(itemName + CloudDriveUtilities.PathSeparator);

                        foreach (CloudBlob blob in client.ListBlobsWithPrefix(prefix))
                        {
                            blob.DeleteIfExists();
                        }
                    }
                    else
                    {
                        var item = container.GetBlobReference(itemName);
                        item.DeleteIfExists();
                    }
                }
            }

        }
        #endregion

        #region Navigation
        /// <summary>
        /// Determine if the path specified is that of a container.
        /// </summary>
        /// <param name="path">The path to check.</param>
        /// <returns>True if the path specifies a container.</returns>
        protected override bool IsItemContainer(string path)
        {
            return HasChildItems(path);
        }

        #endregion


        #region Utility functions
        /// <summary>
        /// Checks if the specified path exist on the server. It will return true if the path is a common-prefix to existing blobs.
        /// ie. "rd:\container\folder\blob" will return true if "rd:\container\folder\blobName" is a valid blob name/prefix
        /// If only 'folder' existence is important, end the path with a Separator.
        /// </summary>
        /// <param name="storage">The storage service to use</param>
        /// <param name="containerName">The name of the container</param>
        /// <returns>True if the specified path exists</returns>
        private bool PathExists(CloudBlobClient client, string path)
        {
            WriteDebug(String.Format("Checking existence of blob/folder {0}", path));

            IEnumerable<IListBlobItem> blobs = client.ListBlobsWithPrefix(path);

            try
            {
                if (blobs.Any())
                {
                    return true;
                }
            }
            catch (StorageException)
            {
            }

            return false;
        }

        /// <summary>
        /// Uses the Filter property to modify the prefix. Only filters of form "[txt]*" are supported.
        /// </summary>
        /// <param name="prefix">The previous prefix</param>
        /// <returns>New prefix with the Filter expression appended</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly",
            Justification = "Filter is the property that is set by user and checked here")]
        private string AddFilter(string prefix)
        {
            if (!string.IsNullOrEmpty(Filter))
            {
                int starIndex = Filter.IndexOf('*');
                if (starIndex < 0 || starIndex != Filter.Length - 1)
                {
                    ThrowTerminatingError(new ErrorRecord(
                        new ArgumentException("Unsupported filter expression", "Filter"),
                        "FilterError", ErrorCategory.InvalidArgument, Filter));
                }
                prefix += Filter.Substring(0, starIndex);
            }
            return prefix;
        }

        /// <summary>
        /// Copies a directory from the HD to the Cloud
        /// </summary>
        /// <param name="dstPath">The destination path.</param>
        /// <param name="container">The container.</param>
        /// <param name="srcFilePath">The source file path.</param>
        private void UploadDirectory(string dstPath, CloudBlobContainer container, String srcFilePath)
        {
            WriteVerbose("Uploading a directory");
            string[] files = Directory.GetFiles(srcFilePath, "*.*", SearchOption.AllDirectories);

            foreach (string srcFile in files)
            {
                // We want to create a destination that includes all the folders below the starting folder, so we can't just use the filename
                string dstFilePath = CloudDriveUtilities.NormalizePath(dstPath + CloudDriveUtilities.PathSeparator + srcFile.Substring(srcFilePath.Length + 1));
                WriteVerbose(String.Format("Uploading {0} to {1}", srcFile, dstFilePath));
                try
                {
                    using (FileStream readStream = File.OpenRead(srcFile))
                    {
                        try
                        {
                            CloudBlob blob = container.ServiceClient.GetBlobReference(dstFilePath);
                            blob.UploadFromStream(readStream);
                        }
                        catch (StorageException ex)
                        {
                            if (ex.ErrorCode != StorageErrorCode.BlobAlreadyExists)
                                throw;
                            if (ShouldContinue(String.Format("Would you like to overwrite \"{0}\"?", dstPath), "Replace"))
                            {
                                readStream.Position = 0;
                                CloudBlob blob = container.ServiceClient.GetBlobReference(dstFilePath);
                                blob.UploadFromStream(readStream);
                            }
                        }
                    }
                }
                catch (IOException e)
                {
                    WriteError(new ErrorRecord(e, "IO Error", ErrorCategory.ReadError, srcFile));
                }
            }
        }

        /// <summary>
        /// Uploads the item to the Cloud.
        /// </summary>
        /// <param name="newItemValue">The new item value.</param>
        /// <param name="dstPath">The destination path.</param>
        /// <param name="container">The container</param>
        private void UploadItem(object newItemValue, string dstPath, CloudBlobContainer container)
        {
            String srcFilePath = newItemValue as string;
            if (srcFilePath != null)
            {
                string containerName, folder;
                SplitPath(ref srcFilePath, out containerName, out folder);

                if (ShouldProcess(srcFilePath, "copy"))
                {
                    if (File.Exists(srcFilePath))
                    {
                        WriteVerbose("Uploading file");
                        CloudBlob blob = container.GetBlobReference(dstPath);

                        if (blob.Exists())
                        {
                            // Item exists. Overwrite?
                            if (ShouldContinue(String.Format("Would you like to overwrite \"{0}\"?", dstPath), "Replace"))
                            {
                                blob.DeleteIfExists();
                                // blob.UploadFile(srcFilePath);
                                UploadBlobStream(blob, srcFilePath);
                            }
                        }
                        else
                        {
                            // Item does not exist
                            // blob.UploadFile(srcFilePath);
                            UploadBlobStream(blob, srcFilePath);
                        }

                        WriteVerbose("File Uploaded");
                    }
                    else if (Directory.Exists(srcFilePath)) // We were handed a directory, start iterating
                    {
                        UploadDirectory(dstPath, container, srcFilePath);
                    } // IsDirectory
                } // ShouldProcess
            }
            else
                throw new ArgumentException("To upload a file, pass the FileSystem path as a string to -Value");
        }

        private static void UploadBlobStream(CloudBlob blob, string sourceFile)
        {
            using (FileStream readStream = File.OpenRead(sourceFile))
            {
                byte[] buffer = new byte[1024 * 128];

                using (BlobStream blobStream = blob.OpenWrite())
                {
                    blobStream.BlockSize = 1024 * 128;

                    while (true)
                    {
                        int bytesCount = readStream.Read(buffer, 0, buffer.Length);

                        if (bytesCount <= 0)
                        {
                            break;
                        }

                        blobStream.Write(buffer, 0, bytesCount);
                    }
                }
            }
        }

        /// <summary>
        /// Splits the path into containerName, potentially folder and file
        /// </summary>
        /// <param name="path">The path to be split</param>
        /// <param name="containerName">The containerName (anything between first two '/'s</param>
        /// <param name="folder">Everything between second '/' and the final '/'</param>
        /// 
        /// <returns>The type of path it was</returns>
        private CloudDriveUtilities.PathType SplitPath(ref string path, out string containerName, out string item)
        {
            containerName = item = null;
            path = CloudDriveUtilities.NormalizePath(path);

            string remainder = CloudDriveUtilities.RemoveDriveFromPath(path, this.PSDriveInfo.Root);
            if (string.IsNullOrEmpty(remainder))
                return CloudDriveUtilities.PathType.Drive;

            // Get the containerName part
            int index = remainder.IndexOf(CloudDriveUtilities.PathSeparator);
            if (index == -1) // We only have a containerName
                index = remainder.Length;

            containerName = remainder.Substring(0, index);
            remainder = remainder.Substring(index).TrimStart(CloudDriveUtilities.PathSeparator);

            if (string.IsNullOrEmpty(remainder))
                return CloudDriveUtilities.PathType.Container;

            item = remainder.TrimEnd(CloudDriveUtilities.PathSeparator);

            return CloudDriveUtilities.PathType.Item;
        }

        private CloudDriveUtilities.PathType GetStorageFromPath(ref string path, out CloudDriveInfo specificDrive, out CloudBlobClient client, out string containerName, out string itemName)
        {
            specificDrive = this.PSDriveInfo as CloudDriveInfo;
            client = null;
            containerName = itemName = null;

            if (specificDrive == null)
            {
                WriteDebug("Received an empty drive");
                return CloudDriveUtilities.PathType.Invalid;
            }
            client = specificDrive.Client;
            return SplitPath(ref path, out containerName, out itemName);
        }
        #endregion

    }
}