// ----------------------------------------------------------------------------------
// 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.ObjectModel;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;


namespace Microsoft.Samples.CloudDrive
{
    // Copies from local drive to the cloud
    [Cmdlet(VerbsCommon.Copy, "CD", SupportsShouldProcess = true)]
    public class CopyCDCommand : PSCmdlet
    {
        string srcPath, dstPath;
        private bool force;

        #region Parameters
        [Parameter(Position = 0,
            ParameterSetName = "Path",
            Mandatory = true,
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true)]
        [ValidateNotNullOrEmpty]
        public string Path
        {
            get { return srcPath; }
            set { srcPath = value; }
        }

        [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)]
        public string Destination
        {
            get { return dstPath; }
            set { dstPath = value; }
        }

        [Parameter]
        public SwitchParameter Force
        {
            get { return force; }
            set { force = value; }
        }
        #endregion

        protected override void ProcessRecord()
        {
            ProviderInfo srcProvider, dstProvider = null;
            WriteVerbose("Checking providers");
            WriteDebug("Checking source provider");
            // Find out what's the source provider
            SessionState.Path.GetResolvedProviderPathFromPSPath(srcPath, out srcProvider);


            if (dstPath != null) // Find the dest provider
            {
                WriteDebug("Checking destination provider");
                if (dstPath == ".") // We need to end in a separator to make sure it's a folder
                    dstPath += CloudDriveUtilities.PathSeparator;
                string dstDrivePath = dstPath.Substring(0, dstPath.IndexOf(':') + 1); // Cut to only have the drive, since that's all we need for destination provider.

                SessionState.Path.GetResolvedProviderPathFromPSPath(dstDrivePath, out dstProvider);
            }
            else
            {
                WriteDebug("Using default destination provider");
                // We could use the current location: SessionState.Drive.Current.CurrentLocation
                // But that would create a relative path that uses current folder. Instead we just use "."
                dstPath = "." + CloudDriveUtilities.PathSeparator; // Make sure folders end in '\'
                dstProvider = SessionState.Drive.Current.Provider;
            }

            bool srcProviderIsCloud = srcProvider != null && srcProvider.Name.Equals(Microsoft.Samples.CloudDrive.BlobDriveProvider.ProviderName);
            bool dstProviderIsCloud = dstProvider != null && dstProvider.Name.Equals(Microsoft.Samples.CloudDrive.BlobDriveProvider.ProviderName);
            bool srcProviderIsFS = srcProvider != null && srcProvider.Name.Equals(Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName);
            bool dstProviderIsFS = dstProvider != null && dstProvider.Name.Equals(Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName);

            if (srcProviderIsCloud) // Copying from Cloud 
            {
                // Check to make sure we support the copying between these providers (No cloud->cloud or cloud->!FS)
                CheckProperProviders(dstProviderIsCloud, dstProviderIsFS);
                WriteDebug("Copying from Cloud to the filesystem");

                // Get the items to copy
                System.Collections.ObjectModel.Collection<PSObject> fileList = InvokeProvider.Item.Get(srcPath);

                Download(fileList);
            }
            else if (dstProviderIsCloud)
            {
                // Check to make sure we support the copying between these providers (No cloud->cloud or cloud->!FS)
                CheckProperProviders(srcProviderIsCloud, srcProviderIsFS);
                WriteDebug("Copying from the filesystem to Cloud");
                dstPath = CloudDriveUtilities.NormalizePath(dstPath);

                // Get the items to copy 
                // EXTENSION POINT: Support for hidden files
                Collection<PSObject> fileList = InvokeProvider.Item.Get(srcPath);

                Upload(fileList);
            }
            else
            {
                // Default case - both things are on the same provider, so ask Copy-Item to do the work.
                string cmd = String.Format("copy {0} {1}", srcPath, dstPath);
                if (this.Force)
                    cmd += " -Force";
                foreach (object o in this.InvokeCommand.InvokeScript(cmd))
                {
                    WriteObject(o);
                }
                
            }
        }

        private void Upload(Collection<PSObject> fileList)
        {
            foreach (PSObject obj in fileList)
            {
                FileSystemInfo file = obj.BaseObject as FileSystemInfo;
                try
                {
                    // Split the destination into folder and file
                    int index = dstPath.LastIndexOf(CloudDriveUtilities.PathSeparator);
                    string destFolder = String.Empty, destFileName = dstPath;

                    // if the dstPath contains a '\', the path before the last '\' is a folder, the rest is a file.
                    // If the dstPath ends in a '\', the whole dstPath is considered to be a folder, and the file is name of the file object.
                    if (index > 0)
                    {
                        destFolder = dstPath.Substring(0, index);
                        destFileName = dstPath.Substring(index + 1);
                    }

                    if (string.IsNullOrEmpty(destFileName))
                        destFileName = file.Name;

                    InvokeProvider.Item.New(destFolder, destFileName, null, file.FullName);
                }
                catch (NullReferenceException e)
                {
                    WriteDebug("Error trying to copy:" + e.ToString());
                }
            } // foreach
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="The error is exposed to the user and operation continues")]
        private void Download(Collection<PSObject> fileList)
        {
            srcPath = CloudDriveUtilities.NormalizePath(srcPath);

            foreach (PSObject obj in fileList)
            {
                IBlobDriveObject rd = obj.BaseObject as IBlobDriveObject;
                if (rd != null)
                {
                    if (ShouldProcess(rd.ObjectName, "Copy"))
                    {
                        try
                        {
                            WriteVerbose(String.Format("Download {0} from {1}", dstPath, rd.Url));
                            rd.CopyTo(dstPath, Force);
                        }
                        catch (IOException ex)
                        {
                            if (ex.Message.Contains("File already exists") && ShouldContinue(String.Format("Would you like to overwrite \"{0}\"?", dstPath), "Destination file already exists"))
                            {
                                WriteVerbose(String.Format("Overwriting {0} with {1}", dstPath, rd.Url));
                                rd.CopyTo(dstPath, true);
                            }
                            else
                                WriteError(new ErrorRecord(ex, ex.Message, ErrorCategory.InvalidArgument, null));
                        }
                        catch (Exception ex)
                        {
                            WriteError(new ErrorRecord(ex, ex.Message, ErrorCategory.InvalidArgument, null));
                        }
                    } // ShouldProcess
                }
            } // foreach
        }

        private void CheckProperProviders(bool providerIsCloud, bool providerIsFS)
        {
            if (providerIsCloud) // This is Cloud->Cloud Copy - we don't support it
            {
                Exception ex = new ArgumentException("specificDrive to specificDrive copy not supported");
                WriteError(new ErrorRecord(ex, "NotSupported", ErrorCategory.InvalidArgument, null));
                throw ex;
            }
            if (providerIsFS == false)
            {
                Exception ex = new ArgumentException("Copying to Non-FileSystem is not supported");
                WriteError(new ErrorRecord(ex, "NotSupported", ErrorCategory.InvalidArgument, null));
                throw ex;
            }
        }
    }
}