// ----------------------------------------------------------------------------------
// 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.Specialized;
using System.IO;
using Microsoft.WindowsAzure.StorageClient;


namespace Microsoft.Samples.CloudDrive
{
    public interface IBlobDriveObject
    {
        string ObjectName { get; }
        string Url { get; }
        void CopyTo(string dstPath);
        void CopyTo(string dstPath, bool overwrite);
    }

    class BlobInfo : IBlobDriveObject
    {
        private CloudBlobContainer container;
        private CloudBlob blob;

        //////////////////////////////////////////////////////////////////////////
        // Properties of a blob object
        //////////////////////////////////////////////////////////////////////////
        // These could be done using IPropertyProvider, but there's no good update semantics for these properties.

        /// <summary>
        /// The encoding of the particular file. 
        /// </summary>
        public string ContentEncoding { get { return blob.Properties.ContentEncoding; } }

        /// <summary>
        /// The language for the blob content
        /// </summary>
        public string ContentLanguage { get { return blob.Properties.ContentLanguage; } }

        /// <summary>
        /// The type of the content
        /// </summary>
        public string ContentType { get { return blob.Properties.ContentType; } }

        //////////////////////////////////////////////////////////////////////////
        // Properties that are similar to a file system object
        //////////////////////////////////////////////////////////////////////////
        public long Size { get { return blob.Properties.Length; } }
        public DateTime LastWriteTimeUtc { get { return blob.Properties.LastModifiedUtc; } }
        public string ObjectName { get; private set; } // Generated from full path
        public string Url { get; private set; } // The full URL to the blob object

        // This could be done using IDynamicPropertiesProvider, but that would increase the number of roundtrips
        public NameValueCollection Metadata
        {
            get { return blob.Metadata ?? new NameValueCollection(); }
            set
            {
                blob.Metadata.Add(value);
                blob.SetMetadata();
                // container.UpdateBlobMetadataIfNotModified(blob);
            }
        }

        public BlobInfo(CloudBlobContainer blobContainer, string pathURL, CloudBlob blob)
        {
            if (blob == null)
            {
                throw new ArgumentNullException("blob", "Blob must not be NULL");
            }


            if (blobContainer == null)
            {
                throw new ArgumentNullException("blobContainer","Reference to the container must not be null");
            }

            this.container = blobContainer;
            this.Url = pathURL;
            this.blob = blob;
            this.ObjectName = CloudDriveUtilities.GetFileNameFromBlobName(blob.Uri.AbsoluteUri);
        }

        /// <summary>
        /// Copies to the current file to the destination path
        /// </summary>
        /// <param name="dstPath">The destination path</param>
        public void CopyTo(string dstPath)
        {
            CopyTo(dstPath, false);
        }

        /// <summary>
        /// Copies to the current file to the destination path
        /// </summary>
        /// <param name="dstPath">The destination path</param>
        /// <param name="force">if set to <c>true</c> we overwrite the folder.</param>
        /// <exception cref="ArgumentException"> thrown in case the provided destination directory doesn't exist or if the directory exists but we didn't enable the force overwrite</exception>
        public void CopyTo(string dstPath, bool overwrite)
        {

            // If the destination path ends with a '\', that means we provided the destination folder
            // So we just append the file name, otherwise we should check if the provided name is an existing directory
            if (dstPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
            {
                // if the provided directory exists, we only need to append the blob name
                if (Directory.Exists(dstPath.Substring(0, dstPath.LastIndexOf(Path.DirectorySeparatorChar))))
                {
                    dstPath = dstPath + CloudDriveUtilities.GetFileNameFromBlobName(blob.Uri.AbsoluteUri);
                }
                else // Otherwise, we can't copy the file because the provided directory doesn't exist
                {
                    throw new ArgumentException("Specified path doesn't exist: " + dstPath);
                }
            }
            else
            {
                //if the provided name doesn't end with a '\', we should check if the provided name is a directory 
                //if it's a directory, we just append the blob name
                //otherwise, we need to check if we provided a new name inside an exisisting directory
                //if the path before the last '\' exists, we copy and rename the blob
                //otherwise, the path doesn't exist and we shouldn't copy the blob
                if (Directory.Exists(dstPath))
                {
                    dstPath = dstPath + Path.DirectorySeparatorChar + CloudDriveUtilities.GetFileNameFromBlobName(blob.Uri.AbsoluteUri);
                }
                else if (!Directory.Exists(dstPath.Substring(0, dstPath.LastIndexOf(Path.DirectorySeparatorChar))))
                {
                    throw new ArgumentException("Specified path doesn't exist");
                }
            }

            dstPath = dstPath.Replace(CloudDriveUtilities.PathSeparator, Path.DirectorySeparatorChar);

            if (File.Exists(dstPath))
            {
                if (!overwrite)
                {
                    throw new IOException("File already exists");
                }
            }

            blob.DownloadToFile(dstPath);

            //using (FileStream cpyStream = File.Open(dstPath, FileMode.Create))
            //{
            //    blob.UploadFromStream(cpyStream);
            //}
        }

        public override string ToString()
        {
            return Url;
        }
    }

    class FolderInfo : IBlobDriveObject
    {
        protected CloudBlobContainer container;
        protected string folderPath; // // Used to represent the current folder path relative to the container path

        public virtual string ObjectName { get; private set; }  // Used to get and/or display the current folder name
        public virtual string Url { get; private set; } // Used to get and/or display the current folder's full path 

        public FolderInfo(CloudBlobContainer blobContainer, string pathURL)
        {
            this.container = blobContainer;
            this.Url = pathURL.Trim(CloudDriveUtilities.PathSeparator);
            this.ObjectName = CloudDriveUtilities.GetFileNameFromBlobName(Url);
            //I'm getting the current folder path from the container
            this.folderPath = Url.Substring(Url.IndexOf(container.Uri.AbsoluteUri) + container.Uri.AbsoluteUri.Length + 1);
            //Removing the current folder name, in that case I get the final path of the parent folder from the container location
            this.folderPath = folderPath.Remove(folderPath.LastIndexOf(ObjectName));
        }

        protected FolderInfo(CloudBlobContainer blobContainer, string fullPath, string folderName, string folderPath)
        {
            this.container = blobContainer;
            this.Url = fullPath;
            this.ObjectName = folderName;
            this.folderPath = folderPath;
        }

        /// <summary>
        /// Copies to the current file to the destination path
        /// </summary>
        /// <param name="dstPath">The destination path</param>
        public void CopyTo(string dstPath)
        {
            CopyTo(dstPath, false);
        }

        /// <summary>
        /// Copies the content of the current folder to the specified destination path
        /// </summary>
        /// <param name="dstPath">The destination path.</param>
        /// <param name="overwrite">if set to <c>true</c> overwrite existing file.</param>
        /// <exception cref="ArgumentException">thrown if the destination folder doesn't exists</exception>
        /// <exception cref="ArgumentException">thrown if the file already exists</exception>
        public void CopyTo(string dstPath, bool overwrite)
        {
            // Used when getting the list of blobs inside a current folder
            dstPath = dstPath.Replace(CloudDriveUtilities.PathSeparator, Path.DirectorySeparatorChar);

            // If we are copying into an existing directory, this should put it as a sub-folder:
            // ie. if c:\temp\test doesn't exist, CopyTo("c:\temp\test") will create test and copy into it.
            //     if c:\temp\test exists, CopyTo("C:\temp\test") will copy the current folder as subfolder of test
            if (Directory.Exists(dstPath))
            {
                //Add the current folder name in the cloud
                if (dstPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
                {
                    dstPath += ObjectName + Path.DirectorySeparatorChar;
                }
                else
                {
                    dstPath += Path.DirectorySeparatorChar + ObjectName + Path.DirectorySeparatorChar;
                }
            }
            if (!Directory.Exists(dstPath))
            {
                Directory.CreateDirectory(dstPath);
            }

            string queryPrefix = folderPath + ObjectName;

            if (!string.IsNullOrEmpty(queryPrefix))
                queryPrefix += CloudDriveUtilities.PathSeparator;

            IEnumerable<IListBlobItem> blobs = container.ServiceClient.ListBlobsWithPrefix(queryPrefix);

            foreach (object obj in blobs)
            {
                CloudBlob blob = obj as CloudBlob;
                if (blob != null) //meaning the current object is a file
                {
                    //Getting the file name from the blob name
                    string fileName = CloudDriveUtilities.GetFileNameFromBlobName(blob.Uri.AbsoluteUri);
                    BlobInfo fileObject = new BlobInfo(container, Url + CloudDriveUtilities.PathSeparator + fileName, blob);
                    string dstFilePath = dstPath + fileName;

                    fileObject.CopyTo(dstFilePath, overwrite);
                }
                else//otherwise, the current object is a folder and we implicitly support recursion
                {
                    string subFolderPath = obj as string;
                    if (subFolderPath != null)
                    {
                        //Get the child folder name
                        subFolderPath = subFolderPath.Substring(0, subFolderPath.LastIndexOf(CloudDriveUtilities.PathSeparator));
                        string childFolderName = subFolderPath.Substring(subFolderPath.LastIndexOf(CloudDriveUtilities.PathSeparator) + 1);

                        FolderInfo subFolder = new FolderInfo(container, Url + CloudDriveUtilities.PathSeparator + childFolderName);
                        //copy the child folders
                        subFolder.CopyTo(dstPath, overwrite);
                    }
                }
            }
        }

        public override string ToString()
        {
            return Url;
        }

    }

    /// <summary>
    /// Containers are very similar to folders, except with more information. So we inherit from a folder.
    /// </summary>
    class ContainerInfo : FolderInfo
    {
        private BlobContainerProperties properties;
        public override string ObjectName { get { return container.Name; } }
        public DateTime LastWriteTimeUtc { get { return properties.LastModifiedUtc; } }

        public NameValueCollection Metadata
        {
            get { return container.Metadata ?? new NameValueCollection(); }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ContainerInfo"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="pathURL">The path URL.</param>
        public ContainerInfo(CloudBlobContainer container, BlobContainerProperties props, string pathURL)
            : base(container, pathURL.Trim(CloudDriveUtilities.PathSeparator), string.Empty, string.Empty)
        {
            properties = props;
        }

        /// <summary>
        /// Returns true if the container is public, false if it's private
        /// </summary>
        /// <returns></returns>
        public bool IsPublic()
        {
            return container.GetPermissions().PublicAccess == BlobContainerPublicAccessType.Container;
        }

        /// <summary>
        /// Sets the container to public
        /// </summary>
        public void SetPublic()
        {
            BlobContainerPermissions permissions = container.GetPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Container;
            container.SetPermissions(permissions);
        }

        /// <summary>
        /// Sets the container to be private
        /// </summary>
        public void SetPrivate()
        {
            BlobContainerPermissions permissions = container.GetPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Off;
            container.SetPermissions(permissions);
        }
    }
}