// ----------------------------------------------------------------------------------
// 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.

namespace Microsoft.Samples.CloudDrive
{
    using System;
    using System.Management.Automation;
    using System.Management.Automation.Provider;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;

    [CmdletProvider("QueueDrive", ProviderCapabilities.None | ProviderCapabilities.Credentials | ProviderCapabilities.ShouldProcess | ProviderCapabilities.Filter)]
    class QueueDriveProvider : ContainerCmdletProvider
    {
        public const string ProviderName = "QueueDrive";
        public const int ElementsPerCall = 1000;

        #region Queue Drive State
        internal class QueueDriveInfo : PSDriveInfo
        {
            public CloudQueueClient Client { get; internal set; }

            public QueueDriveInfo(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 QueueDriveInfo.
        /// </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
            QueueDriveInfo utiliteDrive = new QueueDriveInfo(drive);

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

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

            utiliteDrive.Client.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));

            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;
            }

            QueueDriveInfo specificDrive = drive as QueueDriveInfo;

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

            return specificDrive;
        } // RemoveDrive
        #endregion

        #region ItemCmdletProvider
        protected override bool IsValidPath(string path)
        {
            if (String.IsNullOrEmpty(path))
            {
                return false;
            }

            path = CloudDriveUtilities.NormalizePath(path);

            string queueName = CloudDriveUtilities.RemoveDriveFromPath(path, this.PSDriveInfo.Root);

            if (String.IsNullOrEmpty(queueName)) // just a drive
            {
                return true;
            }

            // Illegal characters for Queue names
            if (queueName.IndexOfAny(@"!/\".ToCharArray()) != -1)
                return false;

            return true;
        }

        protected override void GetItem(string path)
        {
            QueueDriveInfo specificDrive = this.PSDriveInfo as QueueDriveInfo;

            if (specificDrive == null)
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid drive provided"), "InvalidDrive", ErrorCategory.InvalidOperation, specificDrive));
                return;
            }

            path = CloudDriveUtilities.NormalizePath(path);

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

            CloudQueueClient client = specificDrive.Client;


            string queueName;
            SplitPath(ref path, out queueName);

            CloudQueue queue = client.GetQueueReference(queueName);
            queue.FetchAttributes();

            // EXTENSION POINT: Create full Queue paths.
            WriteItemObject(queue, queue.Name, false);
        }


        protected override bool ItemExists(string path)
        {
            QueueDriveInfo specificDrive;
            string queueName;
            CloudQueueClient client;
            CloudDriveUtilities.PathType type = GetQueueFromPath(ref path, out specificDrive, out client, out queueName);

            if (client == null)
                return false;

            switch (type)
            {
                case CloudDriveUtilities.PathType.Drive:
                    return true;
                case CloudDriveUtilities.PathType.Container:
                    {
                        WriteDebug(String.Format("Checking existence of container {0}", queueName));
                        CloudQueue queue = client.GetQueueReference(queueName);
                        return queue.Exists();
                    }
                default:
                    WriteDebug("Path is invalid");
                    return false;
            }
        }

        protected override void ClearItem(string path)
        {
            QueueDriveInfo specificDrive;
            string queueName;
            CloudQueueClient client;
            GetQueueFromPath(ref path, out specificDrive, out client, out queueName);

            if (client == null)
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid drive provided"), "InvalidDrive", ErrorCategory.InvalidOperation, specificDrive));
                return;
            }

            // check if the path represented is a drive
            if (CloudDriveUtilities.PathIsDrive(path, this.PSDriveInfo.Root))
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid path provided"), "InvalidPath", ErrorCategory.InvalidOperation, path));
                return;
            }

            CloudQueue queue = client.GetQueueReference(queueName);

            WriteDebug("Clearing queue: " + queue.Name);
            if (ShouldProcess(queue.Name))
            {
                queue.Clear();
            }
        }
        #endregion

        #region ContainerCmdletProvider

        protected override bool HasChildItems(string path)
        {
            return false;
        }

        protected override void GetChildItems(string path, bool recurse)
        {
            GetChildren(path, false);

        }

        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            GetChildren(path, true);
        }

        private void GetChildren(string path, bool namesOnly)
        {
            QueueDriveInfo specificDrive;
            string queueName;
            CloudQueueClient client;
            CloudDriveUtilities.PathType type = GetQueueFromPath(ref path, out specificDrive, out client, out queueName);

            switch (type)
            {
                case CloudDriveUtilities.PathType.Drive:
                    foreach (CloudQueue q in client.ListQueues(AddFilter("")))
                    {
                        // EXTENSION POINT: Construct the full Queue path
                        if (namesOnly)
                        {
                            WriteItemObject(q.Name, q.Name, false);
                        }
                        else
                        {
                            WriteItemObject(q, q.Name, false);
                        }
                    }
                    break;
                default:
                    WriteDebug("Path doesn't contain child items");
                    break;
            }
        }

        protected override void NewItem(string path, string itemTypeName, object newItemValue)
        {
            QueueDriveInfo specificDrive;
            string queueName;
            CloudQueueClient client;
            GetQueueFromPath(ref path, out specificDrive, out client, out queueName);

            if (client == null)
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid drive provided"), "InvalidDrive", ErrorCategory.InvalidOperation, specificDrive));
                return;
            }

            // Check if the path represented is a drive
            if (CloudDriveUtilities.PathIsDrive(path, this.PSDriveInfo.Root))
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid path provided"), "InvalidPath", ErrorCategory.InvalidOperation, path));
                return;
            }

            CloudQueue queue = client.GetQueueReference(queueName);

            WriteDebug("Creating queue: " + queue.Name);

            if (ShouldProcess(queue.Name))
            {
                queue.Create();
            }

            WriteItemObject(queue, queue.Name, false);
        }

        protected override void RemoveItem(string path, bool recurse)
        {
            QueueDriveInfo specificDrive;
            string queueName;
            CloudQueueClient client;
            GetQueueFromPath(ref path, out specificDrive, out client, out queueName);

            if (client == null)
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid drive provided"), "InvalidDrive", ErrorCategory.InvalidOperation, specificDrive));
                return;
            }

            // check if the path represented is a drive
            if (CloudDriveUtilities.PathIsDrive(path, this.PSDriveInfo.Root))
            {
                WriteError(new ErrorRecord(new ArgumentException("Invalid path provided"), "InvalidPath", ErrorCategory.InvalidOperation, path));
                return;
            }

            CloudQueue queue = client.GetQueueReference(queueName);

            WriteDebug("Deleting queue: " + queue.Name);
            if (ShouldProcess(queue.Name))
            {
                if (Force || ShouldContinue("Deleting queue with name " + queue.Name, "Would you like to continue?"))
                {
                    queue.Delete();
                }
            }
        }

        #endregion

        #region Utilities
        private CloudDriveUtilities.PathType SplitPath(ref string path, out string queueName)
        {
            queueName = 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
            if (remainder.IndexOf('/') != -1) // We only have a containerName
            {
                throw new ArgumentException("queue path contains a /", path);
            }
            queueName = remainder;

            return CloudDriveUtilities.PathType.Container;
        }

        private CloudDriveUtilities.PathType GetQueueFromPath(ref string path, out QueueDriveInfo specificDrive, out CloudQueueClient client, out string queueName)
        {


            specificDrive = this.PSDriveInfo as QueueDriveInfo;
            client = null;
            queueName = null;

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

        /// <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 user-provided parameter")]
        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;
        }


        #endregion
    }
}