/*******************************************************************************
 *  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *  Licensed under the Apache License, Version 2.0 (the "License"). You may not use
 *  this file except in compliance with the License. A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 *  or in the "license" file accompanying this file.
 *  This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 *  CONDITIONS OF ANY KIND, either express or implied. See the License for the
 *  specific language governing permissions and limitations under the License.
 * *****************************************************************************
 *    __  _    _  ___
 *   (  )( \/\/ )/ __)
 *   /__\ \    / \__ \
 *  (_)(_) \/\/  (___/
 *
 *  AWS SDK for .NET
 *  API Version: 2006-03-01
 *
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Security.AccessControl;

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Util;
using System.Globalization;

namespace Amazon.S3.IO
{
    /// <summary>
    /// Mimics the System.IO.FileInfo for a file in S3.  It exposes properties and methods manipulating files in S3.
    /// </summary>
    public sealed class S3FileInfo : IS3FileSystemInfo
    {
        private IAmazonS3 s3Client;
        private string bucket;
        private string key;

        /// <summary>
        /// Initialize a new instance of the S3FileInfo class for the specified S3 bucket and S3 object key.
        /// </summary>
        /// <param name="s3Client">S3 client which is used to access the S3 resources.</param>
        /// <param name="bucket">Name of the S3 bucket.</param>
        /// <param name="key">The S3 object key.</param>
        /// <exception cref="T:System.ArgumentNullException"></exception>
        public S3FileInfo(IAmazonS3 s3Client, string bucket, string key)
        {
            if (s3Client == null)
            {
                throw new ArgumentNullException("s3Client");
            }
            if (String.IsNullOrEmpty(bucket))
            {
                throw new ArgumentNullException("bucket");
            }
            if (String.IsNullOrEmpty(key) || String.Equals(key, "\\"))
            {
                throw new ArgumentNullException("key");
            }

            if (key.EndsWith("\\", StringComparison.Ordinal))
            {
                throw new ArgumentException("key is a directory name");
            }

            this.s3Client = s3Client;
            this.bucket = bucket;
            this.key = key;
        }

        /// <summary>
        /// Returns the parent S3DirectoryInfo.
        /// </summary>
        public S3DirectoryInfo Directory
        {
            get
            {
                int index = key.LastIndexOf('\\');
                string directoryName = null;
                if (index >= 0)
                    directoryName = key.Substring(0, index);
                return new S3DirectoryInfo(s3Client, bucket, directoryName);
            }
        }

        /// <summary>
        /// The full name of parent directory.
        /// </summary>
        public string DirectoryName
        {
            get
            {
                return Directory.FullName;
            }
        }

        /// <summary>
        /// Checks S3 if the file exists in S3 and return true if it does.
        /// </summary>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        public bool Exists
        {
            get
            {
                bool bucketExists;
                return ExistsWithBucketCheck(out bucketExists);
            }
        }

        internal bool ExistsWithBucketCheck(out bool bucketExists)
        {
            bucketExists = true;
            try
            {
                var request = new GetObjectMetadataRequest
                {
                    BucketName = bucket,
                    Key = S3Helper.EncodeKey(key)
                };
                ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);

                // If the object doesn't exist then a "NotFound" will be thrown
                s3Client.GetObjectMetadata(request);
                return true;
            }
            catch (AmazonS3Exception e)
            {
                if (string.Equals(e.ErrorCode, "NoSuchBucket"))
                {
                    bucketExists = false;
                    return false;
                }
                else if (string.Equals(e.ErrorCode, "NotFound"))
                {
                    return false;
                }
                throw;
            }
        }

        /// <summary>
        /// Gets the file's extension.
        /// </summary>
        public string Extension
        {
            get
            {
                int index = Name.LastIndexOf('.');
                if (index == -1 || this.Name.Length <= (index + 1))
                    return null;

                return this.Name.Substring(index + 1);
            }
        }

        /// <summary>
        /// Returns the full path including the bucket.
        /// </summary>
        public string FullName
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "{0}:\\{1}", bucket, key);
            }
        }

        /// <summary>
        /// Returns the last time the file was modified.
        /// </summary>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        public DateTime LastWriteTime
        {
            get
            {
                DateTime ret = DateTime.MinValue;
                if (Exists)
                {
                    //ret = s3Client.GetObjectMetadata(new GetObjectMetadataRequest()
                    //        .WithBucketName(bucket)
                    //        .WithKey(S3Helper.EncodeKey(key))
                    //        .WithBeforeRequestHandler(S3Helper.FileIORequestEventHandler) as GetObjectMetadataRequest)
                    //    .LastModified.ToLocalTime();
                    var request = new GetObjectMetadataRequest
                    {
                        BucketName = bucket,
                        Key = S3Helper.EncodeKey(key),
                    };
                    ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                    var response = s3Client.GetObjectMetadata(request);
                    ret = response.LastModified.ToLocalTime();
                }
                return ret;
            }

        }

        /// <summary>
        /// Returns the last time the file was modified in UTC.
        /// </summary>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        public DateTime LastWriteTimeUtc
        {
            get
            {
                DateTime ret = DateTime.MinValue;
                if (Exists)
                {
                    //ret = s3Client.GetObjectMetadata(new GetObjectMetadataRequest()
                    //        .WithBucketName(bucket)
                    //        .WithKey(S3Helper.EncodeKey(key))
                    //        .WithBeforeRequestHandler(S3Helper.FileIORequestEventHandler) as GetObjectMetadataRequest)
                    //    .LastModified;
                    var request = new GetObjectMetadataRequest
                    {
                        BucketName = bucket,
                        Key = S3Helper.EncodeKey(key),
                    };
                    ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                    var response = s3Client.GetObjectMetadata(request);
                    ret = response.LastModified;
                }
                return ret;
            }
        }

        /// <summary>
        /// Returns the content length of the file.
        /// </summary>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        public long Length
        {
            get
            {
                long ret = 0;
                if (Exists)
                {
                    //ret = s3Client.GetObjectMetadata(new GetObjectMetadataRequest()
                    //        .WithBucketName(bucket)
                    //        .WithKey(S3Helper.EncodeKey(key))
                    //        .WithBeforeRequestHandler(S3Helper.FileIORequestEventHandler) as GetObjectMetadataRequest)
                    //    .ContentLength;
                    var request = new GetObjectMetadataRequest
                    {
                        BucketName = bucket,
                        Key = S3Helper.EncodeKey(key),
                    };
                    ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                    var response = s3Client.GetObjectMetadata(request);
                    ret = response.ContentLength;
                }
                return ret;
            }
        }

        /// <summary>
        /// Returns the file name without its directory name.
        /// </summary>
        public string Name
        {
            get
            {
                int index = key.LastIndexOf('\\');
                return key.Substring(index + 1);
            }
        }

        /// <summary>
        /// Returns the type of file system element.
        /// </summary>
        public FileSystemType Type
        {
            get
            {
                return FileSystemType.File;
            }
        }

        /// <summary>
        /// Copies this file's content to the file indicated by the S3 bucket and object key.
        /// If the file already exists in S3 than an ArgumentException is thrown.
        /// </summary>
        /// <param name="newBucket">S3 bucket to copy the file to.</param>
        /// <param name="newKey">S3 object key to copy the file to.</param>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(string newBucket, string newKey)
        {
            return CopyTo(newBucket, newKey, false);
        }

        /// <summary>
        /// Copies this file's content to the file indicated by the S3 bucket and object key.
        /// If the file already exists in S3 and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="newBucket">S3 bucket to copy the file to.</param>
        /// <param name="newKey">S3 object key to copy the file to.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3 and overwrite is set to false.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(string newBucket, string newKey, bool overwrite)
        {
            if (String.IsNullOrEmpty(newBucket))
            {
                throw new ArgumentException("A bucket is required to copy an object","newBucket");
            }

            S3FileInfo ret = null;
            if (String.IsNullOrEmpty(newKey) || newKey.EndsWith("\\", StringComparison.Ordinal))
            {
                ret = CopyTo(new S3DirectoryInfo(s3Client, newBucket, newKey), overwrite);
            }
            else
            {
                ret = CopyTo(new S3FileInfo(s3Client, newBucket, newKey), overwrite);
            }
            return ret;
        }

        /// <summary>
        /// Copies this file to the target directory.
        /// If the file already exists in S3 than an ArgumentException is thrown.
        /// </summary>
        /// <param name="dir">Target directory where to copy the file to.</param>
        /// <exception cref="T:System.ArgumentException">If the directory does not exist.</exception>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(S3DirectoryInfo dir)
        {
            return CopyTo(dir, false);
        }

        /// <summary>
        /// Copies this file to the target directory.
        /// If the file already exists in S3 and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="dir">Target directory where to copy the file to.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.ArgumentException">If the directory does not exist.</exception>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3 and overwrite is set to false.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(S3DirectoryInfo dir, bool overwrite)
        {
            if (!dir.Exists)
            {
                throw new ArgumentException("directory does not exist", "dir");
            }

            S3FileInfo ret = CopyTo(new S3FileInfo(dir.S3Client, dir.BucketName, string.Format(CultureInfo.InvariantCulture, "{0}{1}", dir.ObjectKey, Name)),overwrite);
            return ret;
        }

        /// <summary>
        /// Copies this file to the location indicated by the passed in S3FileInfo.
        /// If the file already exists in S3 than an ArgumentException is thrown.
        /// </summary>
        /// <param name="file">The target location to copy this file to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(S3FileInfo file)
        {
            return CopyTo(file, false);
        }

        /// <summary>
        /// Copies this file to the location indicated by the passed in S3FileInfo.
        /// If the file already exists in S3 and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="file">The target location to copy this file to.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3 and overwrite is set to false.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the newly copied file.</returns>
        public S3FileInfo CopyTo(S3FileInfo file, bool overwrite)
        {
            if (!overwrite)
            {
                if (file.Exists)
                {
                    throw new IOException("File already exists");
                }
            }

            if (SameClient(file))
            {
                var request = new CopyObjectRequest
                {
                    DestinationBucket = file.BucketName,
                    DestinationKey = S3Helper.EncodeKey(file.ObjectKey),
                    SourceBucket = bucket,
                    SourceKey = S3Helper.EncodeKey(key)
                };
                ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                s3Client.CopyObject(request);
            }
            else
            {
                var getObjectRequest = new GetObjectRequest
                {
                    BucketName = bucket,
                    Key = S3Helper.EncodeKey(key)
                };
                ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)getObjectRequest).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                var getObjectResponse = s3Client.GetObject(getObjectRequest);
                using (Stream stream = getObjectResponse.ResponseStream)
                {
                    var putObjectRequest = new PutObjectRequest
                    {
                        BucketName = file.BucketName,
                        Key = S3Helper.EncodeKey(file.ObjectKey),
                        InputStream = stream
                    };
                    ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)putObjectRequest).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                    file.S3Client.PutObject(putObjectRequest);
                }
            }

            return file;
        }

        /// <summary>
        /// Copies from S3 to the local file system.  
        /// If the file already exists on the local file system than an ArgumentException is thrown.
        /// </summary>
        /// <param name="destFileName">The path where to copy the file to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists locally.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>FileInfo for the local file where file is copied to.</returns>
        public FileInfo CopyToLocal(string destFileName)
        {
            return CopyToLocal(destFileName, false);
        }

        /// <summary>
        /// Copies from S3 to the local file system.  
        /// If the file already exists on the local file system and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="destFileName">The path where to copy the file to.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists locally and overwrite is set to false.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>FileInfo for the local file where file is copied to.</returns>
        public FileInfo CopyToLocal(string destFileName, bool overwrite)
        {
            if (!overwrite)
            {
                if (new FileInfo(destFileName).Exists)
                {
                    throw new IOException("File already exists");
                }
            }

            var getObjectRequest = new GetObjectRequest
            {
                BucketName = bucket,
                Key = S3Helper.EncodeKey(key)
            };
            ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)getObjectRequest).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
            using (var getObjectResponse = s3Client.GetObject(getObjectRequest))
            {
                getObjectResponse.WriteResponseStreamToFile(destFileName);
            }

            return new FileInfo(destFileName);
        }

        /// <summary>
        /// Copies the file from the local file system to S3.
        /// If the file already exists in S3 than an ArgumentException is thrown.
        /// </summary>
        /// <param name="srcFileName">Location of the file on the local file system to copy.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo where the file is copied to.</returns>
        public S3FileInfo CopyFromLocal(string srcFileName)
        {
            return CopyFromLocal(srcFileName, false);
        }

        /// <summary>
        /// Copies the file from the local file system to S3.
        /// If the file already exists in S3 and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="srcFileName">Location of the file on the local file system to copy.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3 and overwrite is set to false.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo where the file is copied to.</returns>
        public S3FileInfo CopyFromLocal(string srcFileName, bool overwrite)
        {
            if (!overwrite)
            {
                if (Exists)
                {
                    throw new IOException("File already exists");
                }
            }

            var putObjectRequest = new PutObjectRequest
            {
                BucketName = bucket,
                Key = S3Helper.EncodeKey(key),
                FilePath = srcFileName
            };
            ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)putObjectRequest).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
            s3Client.PutObject(putObjectRequest);

            return this;
        }

        /// <summary>
        /// Returns a Stream that can be used to write data to S3. 
        /// </summary>
        /// <remarks>
        /// <note>The content will not be written to S3 until the Stream is closed.</note>
        /// </remarks>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>Stream to write content to.</returns>
        public Stream Create()
        {
            return new S3FileStream(this.S3Client, this.BucketName, this.ObjectKey,  FileMode.Create, FileAccess.Write);
        }

        /// <summary>
        /// Returns a StreamWriter that can be used to write data to S3. 
        /// </summary>
        /// <remarks>
        /// <note>The content will not be written to S3 until the Stream is closed.</note>
        /// </remarks>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>Stream to write content to.</returns>
        public StreamWriter CreateText()
        {
            return new StreamWriter(Create());
        }

        /// <summary>
        /// Deletes the from S3.
        /// </summary>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        public void Delete()
        {
            if (Exists)
            {
                var deleteObjectRequest = new DeleteObjectRequest
                {
                    BucketName = bucket,
                    Key = S3Helper.EncodeKey(key)
                };
                ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)deleteObjectRequest).AddBeforeRequestHandler(S3Helper.FileIORequestEventHandler);
                s3Client.DeleteObject(deleteObjectRequest);

                Directory.Create();
            }
        }

        /// <summary>
        /// Moves the file to a a new location in S3.
        /// </summary>
        /// <param name="bucket">Bucket to move the file to.</param>
        /// <param name="key">Object key to move the file to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo for the target location.</returns>
        public S3FileInfo MoveTo(string bucket, string key)
        {
            S3FileInfo ret = CopyTo(bucket,key,false);
            Delete();
            return ret;
        }


        /// <summary>
        /// Moves the file to a a new location in S3.
        /// </summary>
        /// <param name="path">The target directory to copy to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo for the target location.</returns>
        public S3FileInfo MoveTo(S3DirectoryInfo path)
        {
            S3FileInfo ret = CopyTo(path, false);
            Delete();
            return ret;
        }

        /// <summary>
        /// Moves the file to a a new location in S3.
        /// </summary>
        /// <param name="file">The target file to copy to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo for the target location.</returns>
        public S3FileInfo MoveTo(S3FileInfo file)
        {
            S3FileInfo ret = CopyTo(file, false);
            Delete();
            return ret;
        }

        /// <summary>
        /// Moves the file from S3 to the local file system in the location indicated by the path parameter.
        /// </summary>
        /// <param name="path">The location on the local file system to move the file to.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists locally.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>FileInfo for the local file where file is moved to.</returns>
        public FileInfo MoveToLocal(string path)
        {
            FileInfo ret = CopyToLocal(path, false);
            Delete();
            return ret;
        }

        /// <summary>
        /// Moves the file from the local file system to S3 in this directory.
        /// If the file already exists in S3 than an ArgumentException is thrown.
        /// </summary>
        /// <param name="path">The local file system path where the files are to be moved.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists locally.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo where the file is moved to.</returns>
        public S3FileInfo MoveFromLocal(string path)
        {
            S3FileInfo ret = CopyFromLocal(path, false);
            File.Delete(path);
            return ret;
        }

        /// <summary>
        /// Moves the file from the local file system to S3 in this directory.
        /// If the file already exists in S3 and overwrite is set to false than an ArgumentException is thrown.
        /// </summary>
        /// <param name="path">The local file system path where the files are to be moved.</param>
        /// <param name="overwrite">Determines whether the file can be overwritten.</param>
        /// <exception cref="T:System.IO.IOException">If the file already exists in S3 and overwrite is set to false.</exception>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo where the file is moved to.</returns>
        public S3FileInfo MoveFromLocal(string path, bool overwrite)
        {
            S3FileInfo ret = CopyFromLocal(path, overwrite);
            File.Delete(path);
            return ret;
        }

        /// <summary>
        /// Returns a Stream for reading the contents of the file.
        /// </summary>
        /// <exception cref="T:System.IO.IOException">The file is already open.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>Stream to read from.</returns>
        public Stream OpenRead()
        {
            return new S3FileStream(this.S3Client, this.BucketName, this.ObjectKey, FileMode.Open, FileAccess.Read);
        }

        /// <summary>
        /// Returns a StreamReader for reading the contents of the file.
        /// </summary>
        /// <exception cref="T:System.IO.IOException">The file is already open.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>StreamReader to read from.</returns>
        public StreamReader OpenText()
        {
            return new StreamReader(OpenRead());
        }

        /// <summary>
        /// Returns a Stream for writing to S3.  If the file already exists it will be overwritten.
        /// </summary>
        /// <remarks>
        /// <note>The content will not be written to S3 until the Stream is closed.</note>
        /// </remarks>
        /// <exception cref="T:System.IO.IOException">The file is already open.</exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>Stream to write from.</returns>
        public Stream OpenWrite()
        {
            return new S3FileStream(this.S3Client, this.BucketName, this.ObjectKey, FileMode.OpenOrCreate, FileAccess.Write);
        }

        /// <summary>
        /// Replaces the destination file with the content of this file and then deletes the orignial file.  If a backup location is specifed then the content of destination file is 
        /// backup to it.
        /// </summary>
        /// <param name="destinationBucket">Destination bucket of this file will be copy to.</param>
        /// <param name="destinationKey">Destination object key of this file will be copy to.</param>
        /// <param name="backupBucket">Backup bucket to store the contents of the destination file.</param>
        /// <param name="backupKey">Backup object key to store the contents of the destination file.</param>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.IO.IOException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the destination file.</returns>
        public S3FileInfo Replace(string destinationBucket, string destinationKey, string backupBucket, string backupKey)
        {
            if (String.IsNullOrEmpty(destinationBucket))
            {
                throw new ArgumentException("A bucket is required to replace an object", "destinationBucket");
            }

            S3FileInfo destinationInfo;
            if (String.IsNullOrEmpty(destinationKey))
            {
                destinationInfo = new S3FileInfo(this.s3Client, destinationBucket, this.Name);
            }
            else if (destinationKey.EndsWith("\\", StringComparison.Ordinal))
            {
                destinationInfo = new S3FileInfo(this.s3Client, destinationBucket, destinationKey + this.Name);
            }
            else
            {
                destinationInfo = new S3FileInfo(this.s3Client, destinationBucket, destinationKey);
            }


            S3FileInfo backupInfo = null;
            if(!string.IsNullOrEmpty(backupBucket))
            {
                if (String.IsNullOrEmpty(backupKey))
                {
                    backupInfo = new S3FileInfo(this.s3Client, backupBucket, this.Name);
                }
                else if (backupKey.EndsWith("\\", StringComparison.Ordinal))
                {
                    backupInfo = new S3FileInfo(this.s3Client, backupBucket, backupKey + this.Name);
                }
                else 
                {
                    backupInfo = new S3FileInfo(this.s3Client, backupBucket, backupKey);
                }        
            }

            S3FileInfo ret = Replace(destinationInfo, backupInfo);
            return ret;
        }

        /// <summary>
        /// Replaces the destination file with the content of this file and then deletes the orignial file.  If a backupDir is specifed then the content of destination file is 
        /// backup to it.
        /// </summary>
        /// <param name="destDir">Where the contents of this file will be copy to.</param>
        /// <param name="backupDir">If specified the destFile is backup to it.</param>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.IO.IOException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the destination file.</returns>
        public S3FileInfo Replace(S3DirectoryInfo destDir, S3DirectoryInfo backupDir)
        {
            S3FileInfo ret = Replace(
                new S3FileInfo(destDir.S3Client, destDir.BucketName, string.Format(CultureInfo.InvariantCulture, "{0}{1}", destDir.ObjectKey, Name)),
                backupDir == null ? null : new S3FileInfo(backupDir.S3Client, backupDir.BucketName, string.Format(CultureInfo.InvariantCulture, "{0}{1}", backupDir.ObjectKey, Name)));
            return ret;
        }

        /// <summary>
        /// Replaces the destination file with the content of this file and then deletes the orignial file.  If a backupFile is specifed then the content of destination file is 
        /// backup to it.
        /// </summary>
        /// <param name="destFile">Where the contents of this file will be copy to.</param>
        /// <param name="backupFile">If specified the destFile is backup to it.</param>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.IO.IOException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns>S3FileInfo of the destination file.</returns>
        public S3FileInfo Replace(S3FileInfo destFile, S3FileInfo backupFile)
        {
            if (string.Equals(this.BucketName, destFile.BucketName) && string.Equals(this.ObjectKey, destFile.ObjectKey))
            {
                throw new ArgumentException("Destination file can not be the same as the source file when doing a replace.", "destFile");
            }
            if (backupFile != null)
            {
                destFile.CopyTo(backupFile, true);
            }
            S3FileInfo ret = CopyTo(destFile, true);
            Delete();
            return ret;
        }

        /// <summary>
        /// Replaces the content of the destination file on the local file system with the content from this file from S3.
        /// If a backup file is specified then the content of the destination file is backup to it.
        /// </summary>
        /// <param name="destinationFileName"></param>
        /// <param name="destinationBackupFileName"></param>
        /// <exception cref="T:System.ArgumentException"></exception>
        /// <exception cref="T:System.IO.IOException"></exception>
        /// <exception cref="T:System.Net.WebException"></exception>
        /// <exception cref="T:Amazon.S3.AmazonS3Exception"></exception>
        /// <returns></returns>
        public FileInfo ReplaceLocal(string destinationFileName, string destinationBackupFileName)
        {
            if (!string.IsNullOrEmpty(destinationBackupFileName))
            {
                File.Replace(destinationFileName, destinationBackupFileName, null);
            }

            return CopyToLocal(destinationFileName, true);
        }

        /// <summary>
        /// Implement the ToString method.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return FullName;
        }

        internal IAmazonS3 S3Client
        {
            get
            {
                return s3Client;
            }
        }

        internal string BucketName
        {
            get
            {
                return bucket;
            }
        }

        internal string ObjectKey
        {
            get
            {
                return key;
            }
        }

        private bool SameClient(S3FileInfo otherFile)
        {
            return s3Client.Equals(otherFile.S3Client);
        }
        
    }
}