// 
// Copyright (c) Microsoft and contributors.  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.
// You may obtain a copy of the License at
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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.
// 

// Module dependencies.
var qs = require('querystring');
var url = require('url');
var util = require('util');
var _ = require('underscore');
var extend = require('extend');
var path = require('path');

var azureCommon = require('./../../common/common.core');
var Md5Wrapper = require('./../../common/md5-wrapper');
var azureutil = azureCommon.util;
var SR = azureCommon.SR;
var validate = azureCommon.validate;
var SpeedSummary = azureCommon.SpeedSummary;
var StorageServiceClient = azureCommon.StorageServiceClient;
var WebResource = azureCommon.WebResource;

// Constants
var Constants = azureCommon.Constants;
var FileConstants = Constants.FileConstants;
var HeaderConstants = Constants.HeaderConstants;
var HttpConstants = Constants.HttpConstants;
var QueryStringConstants = Constants.QueryStringConstants;

// Streams
var BatchOperation = azureCommon.BatchOperation;
var SpeedSummary = azureCommon.SpeedSummary;
var ChunkAllocator = azureCommon.ChunkAllocator;
var ChunkStream = azureCommon.ChunkStream;
var ChunkStreamWithStream = azureCommon.ChunkStreamWithStream;
var FileRangeStream = require('./internal/filerangestream');

// Models requires
var ShareResult = require('./models/shareresult');
var DirectoryResult = require('./models/directoryresult');
var FileResult = require('./models/fileresult');
var AclResult = azureCommon.AclResult;

// Errors requires
var errors = require('../../common/errors/errors');
var ArgumentNullError = errors.ArgumentNullError;
var ArgumentError = errors.ArgumentError;

/**
* Creates a new FileService object.
* If no connection string or storageaccount and storageaccesskey are provided,
* the AZURE_STORAGE_CONNECTION_STRING or AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY environment variables will be used.
* @class
* The FileService class is used to perform operations on the Microsoft Azure File Service.
* The File Service provides storage for binary large objects, and provides functions for working with data stored in files.
* 
* For more information on the File Service, as well as task focused information on using it in a Node.js application, see
* [How to Use the File Service from Node.js](http://azure.microsoft.com/en-us/documentation/articles/storage-nodejs-how-to-use-file-storage/).
* The following defaults can be set on the file service.
* defaultTimeoutIntervalInMs                          The default timeout interval, in milliseconds, to use for request made via the file service.
* defaultEnableReuseSocket                            The default boolean value to enable socket reuse when uploading local files or streams.
*                                                     If the Node.js version is lower than 0.10.x, socket reuse will always be turned off.
* defaultClientRequestTimeoutInMs                     The default timeout of client requests, in milliseconds, to use for the request made via the file service.
* defaultMaximumExecutionTimeInMs                     The default maximum execution time across all potential retries, for requests made via the file service.
* defaultLocationMode                                 The default location mode for requests made via the file service.
* parallelOperationThreadCount                        The number of parallel operations that may be performed when uploading a file.
* useNagleAlgorithm                                   Determines whether the Nagle algorithm is used for requests made via the file service; true to use the  
*                                                     Nagle algorithm; otherwise, false. The default value is false.
* enableGlobalHttpAgent                               Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use
*                                                     http(s).Agent({keepAlive:true}).
* @constructor
* @extends {StorageServiceClient}
*
* @param {string} [storageAccountOrConnectionString]  The storage account or the connection string.
* @param {string} [storageAccessKey]                  The storage access key.
* @param {string|object} [host]                       The host address. To define primary only, pass a string. 
*                                                     Otherwise 'host.primaryHost' defines the primary host and 'host.secondaryHost' defines the secondary host.
* @param {string} [sasToken]                          The Shared Access Signature token.
* @param {string} [endpointSuffix]                    The endpoint suffix.
*/
function FileService(storageAccountOrConnectionString, storageAccessKey, host, sasToken, endpointSuffix) {
  var storageServiceSettings = StorageServiceClient.getStorageSettings(storageAccountOrConnectionString, storageAccessKey, host, sasToken, endpointSuffix);

  FileService['super_'].call(this,
    storageServiceSettings._name,
    storageServiceSettings._key,
    storageServiceSettings._fileEndpoint,
    storageServiceSettings._usePathStyleUri,
    storageServiceSettings._sasToken);

  this.defaultEnableReuseSocket = Constants.DEFAULT_ENABLE_REUSE_SOCKET;
  this.singleFileThresholdInBytes = FileConstants.DEFAULT_SINGLE_FILE_GET_THRESHOLD_IN_BYTES;
  this.parallelOperationThreadCount = Constants.DEFAULT_PARALLEL_OPERATION_THREAD_COUNT;
}

util.inherits(FileService, StorageServiceClient);

// Utility methods

/**
* Create resource name
* @ignore
*
* @param {string} share          Share name
* @param {string} [directory]    Directory name
* @param {string} [file]         File name
* @return {string} The encoded resource name.
*/
function createResourceName(share, directory, file, forSAS) {
  var encode = function(name) {
    if (name && !forSAS) {
      name = encodeURIComponent(name);
      name = name.replace(/%2F/g, '/');
      name = name.replace(/%5C/g, '/');
      name = name.replace(/\+/g, '%20');
    }
    return name;
  };

  var name = share;

  if (directory) {
    // if directory does not start with '/', add it
    if (directory[0] !== '/') {
      name += ('/');
    }

    name += encode(directory);
  } 

  if (file) {
    // if the current path does not end with '/', add it
    if (name[name.length - 1] !== '/') {
      name += ('/');
    }
    
    name += encode(file);
  }

  return path.normalize(name).replace(/\\/g, '/');
}

// File service methods

/**
* Gets the properties of a storage account's File service, including Azure Storage Analytics.
*
* @this {FileService}
* @param {object}       [options]                               The request options.
* @param {LocationMode} [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                               Please see StorageUtilities.LocationMode for the possible values.
* @param {int}          [options.timeoutIntervalInMs]           The server timeout interval, in milliseconds, to use for the request.
* @param {int}          [options.clientRequestTimeoutInMs]      The timeout of client requests, in milliseconds, to use for the request.
* @param {int}          [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                               The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                               execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}       [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}         [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                               The default value is false.
* @param {errorOrResult}  callback                              `error` will contain information if an error occurs; otherwise, `[result]{@link ServiceProperties}` will contain the properties 
*                                                               and `response` will contain information related to this operation.
*/
FileService.prototype.getServiceProperties = function (optionsOrCallback, callback) {
  return this.getAccountServiceProperties(optionsOrCallback, callback);
};

/**
* Sets the properties of a storage account's File service, including Azure Storage Analytics.
* You can also use this operation to set the default request version for all incoming requests that do not have a version specified.
*
* @this {FileService}
* @param {object}             serviceProperties                        The service properties.
* @param {object}             [options]                                The request options.
* @param {LocationMode}       [options.locationMode]                   Specifies the location mode used to decide which location the request should be sent to. 
*                                                                      Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]            The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]       The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]       The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                      The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                      execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]              Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                      The default value is false.
* @param {errorOrResponse}    callback                                 `error` will contain information
*                                                                      if an error occurs; otherwise, `response`
*                                                                      will contain information related to this operation.
*/
FileService.prototype.setServiceProperties = function (serviceProperties, optionsOrCallback, callback) {
  return this.setAccountServiceProperties(serviceProperties, optionsOrCallback, callback);
};

// Share methods

/**
* Lists a segment containing a collection of share items under the specified account.
*
* @this {FileService}
* @param {object}             currentToken                                A continuation token returned by a previous listing operation. Please use 'null' or 'undefined' if this is the first operation.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.maxResults]                        Specifies the maximum number of shares to return per call to Azure storage.
* @param {string}             [options.include]                           Include this parameter to specify that the share's metadata be returned as part of the response body. (allowed values: '', 'metadata', 'snapshots' or any combination of them)
*                                                                         **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `result` will contain `entries` and `continuationToken`. 
*                                                                         `entries`  gives a list of `[shares]{@link ShareResult}` and the `continuationToken` is used for the next listing operation.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.listSharesSegmented = function (currentToken, optionsOrCallback, callback) {
  this.listSharesSegmentedWithPrefix(null /* prefix */, currentToken, optionsOrCallback, callback);
};

/**
* Lists a segment containing a collection of share items whose names begin with the specified prefix under the specified account.
*
* @this {FileService}
* @param {string}             prefix                                      The prefix of the share name.
* @param {object}             currentToken                                A continuation token returned by a previous listing operation. Please use 'null' or 'undefined' if this is the first operation.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {string}             [options.prefix]                            Filters the results to return only shares whose name begins with the specified prefix.
* @param {int}                [options.maxResults]                        Specifies the maximum number of shares to return per call to Azure storage.
* @param {string}             [options.include]                           Include this parameter to specify that the share's metadata be returned as part of the response body. (allowed values: '', 'metadata', 'snapshots' or any combination of them)
*                                                                         **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `result` will contain `entries` and `continuationToken`. 
*                                                                         `entries`  gives a list of `[shares]{@link ShareResult}` and the `continuationToken` is used for the next listing operation.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.listSharesSegmentedWithPrefix = function (prefix, currentToken, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('listShares', function (v) {
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  var webResource = WebResource.get()
    .withQueryOption(QueryStringConstants.COMP, 'list')
    .withQueryOption(QueryStringConstants.MAX_RESULTS, options.maxResults)
    .withQueryOption(QueryStringConstants.INCLUDE, options.include);
  
  if (!azureutil.objectIsNull(currentToken)) {
    webResource.withQueryOption(QueryStringConstants.MARKER, currentToken.nextMarker);
  }
  
  webResource.withQueryOption(QueryStringConstants.PREFIX, prefix);
  
  //options.requestLocationMode = azureutil.getNextListingLocationMode(currentToken);
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.listSharesResult = null;
    
    if (!responseObject.error) {
      responseObject.listSharesResult = {
        entries: null,
        continuationToken: null
      };
      responseObject.listSharesResult.entries = [];
      
      var shares = [];
      
      if (responseObject.response.body.EnumerationResults.Shares && responseObject.response.body.EnumerationResults.Shares.Share) {
        shares = responseObject.response.body.EnumerationResults.Shares.Share;
        if (!_.isArray(shares)) {
          shares = [shares];
        }
      }
      
      shares.forEach(function (currentShare) {
        var shareResult = ShareResult.parse(currentShare);
        responseObject.listSharesResult.entries.push(shareResult);
      });
      
      if (responseObject.response.body.EnumerationResults.NextMarker) {
        responseObject.listSharesResult.continuationToken = {
          nextMarker: null,
          targetLocation: null
        };
        
        responseObject.listSharesResult.continuationToken.nextMarker = responseObject.response.body.EnumerationResults.NextMarker;
        responseObject.listSharesResult.continuationToken.targetLocation = responseObject.targetLocation;
      }
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.listSharesResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Checks whether or not a share exists on the service.
*
* @this {FileService}
* @param {string}             share                                   The share name.
* @param {object}             [options]                               The request options.
* @param {string}             [options.shareSnapshotId]               The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                                     Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]           The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]      The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                     The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                     execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                     The default value is false.
* @param {errorOrResult}      callback                                `error` will contain information
*                                                                     if an error occurs; otherwise `[result]{@link ShareResult}` will contain
*                                                                     the share information including `exists` boolean member. 
*                                                                     `response` will contain information related to this operation.
*/
FileService.prototype.doesShareExist = function (share, optionsOrCallback, callback) {
  this._doesShareExist(share, false, optionsOrCallback, callback);
};

/**
* Creates a new share under the specified account.
* If a share with the same name already exists, the operation fails.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {int}                [options.quota]                     Specifies the maximum size of the share, in gigabytes.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {object}             [options.metadata]                  The metadata key/value pairs.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.   
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `[result]{@link ShareResult}` will contain
*                                                                 the share information.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.createShare = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createShare', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.shareQuotaIsValid(userOptions.quota);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withHeader(HeaderConstants.SHARE_QUOTA, options.quota);

  webResource.addOptionalMetadataHeaders(options.metadata);

  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);

      if (options.metadata) {
        responseObject.shareResult.metadata = options.metadata;
      }
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Creates a share snapshot.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {object}             [options.metadata]                  The metadata key/value pairs.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.   
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will contain
*                                                                 the ID of the snapshot.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.createShareSnapshot = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createShareSnapshot', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, QueryStringConstants.SNAPSHOT);
  webResource.addOptionalMetadataHeaders(options.metadata);

  var processResponseCallback = function (responseObject, next) {
    responseObject.snapshotId = null;
    if (!responseObject.error) {
      responseObject.snapshotId = responseObject.response.headers[HeaderConstants.SNAPSHOT];
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.snapshotId, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Creates a new share under the specified account if the share does not exists.
*
* @this {FileService}
* @param {string}             share                                     The share name.
* @param {object}             [options]                                 The request options.
* @param {LocationMode}       [options.locationMode]                    Specifies the location mode used to decide which location the request should be sent to. 
*                                                                       Please see StorageUtilities.LocationMode for the possible values.
* @param {object}             [options.metadata]                        The metadata key/value pairs.
* @param {int}                [options.timeoutIntervalInMs]             The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]        The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]        The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                       The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                       execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                 A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]               Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                       The default value is false.
* @param {errorOrResult}      callback                                  `error` will contain information
*                                                                       if an error occurs; otherwise `[result]{@link ShareResult}` will contain
*                                                                       the share information including `created` boolean member.
*                                                                       `response` will contain information related to this operation.
*
* @example
* var azure = require('azure-storage');
* var FileService = azure.createFileService();
* FileService.createShareIfNotExists('taskshare', function(error) {
*   if(!error) {
*     // Share created or already existed
*   }
* }); 
*/
FileService.prototype.createShareIfNotExists = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createShareIfNotExists', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  delete options.shareSnapshotId;

  var self = this;
  self._doesShareExist(share, true, options, function(error, result, response) {
    var exists = result.exists;
    result.created = false;
    delete result.exists;
    
    if(error){
      callback(error, result, response);
    } else if (exists) {
      response.isSuccessful = true;
      callback(error, result, response);
    } else {
      self.createShare(share, options, function (createError, responseShare, createResponse) {
        if(!createError){
          responseShare.created = true;
        }
        else if (createError && createError.statusCode === HttpConstants.HttpResponseCodes.Conflict && createError.code === Constants.FileErrorCodeStrings.SHARE_ALREADY_EXISTS) {
          // If it was created before, there was no actual error.
          createError = null;
          createResponse.isSuccessful = true;
        }

        callback(createError, responseShare, createResponse);
      });
    }
  });
};

/**
* Retrieves a share and its properties from a specified account.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.


*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {string}             [options.shareSnapshotId]           The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `[result]{@link ShareResult}` will contain
*                                                                 information for the share.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.getShareProperties = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getShareProperties', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.head(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  //options.requestLocationMode = Constants.RequestLocationMode.PRIMARY_OR_SECONDARY;

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Sets the properties for the specified share.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {object}             [properties]                                The share properties to set.
* @param {string|int}         [properties.quota]                          Specifies the maximum size of the share, in gigabytes.
* @param {object}             [options]                                   The request options.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[share]{@link ShareResult}` will contain
*                                                                         information about the share.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.setShareProperties = function (share, properties, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('setShareProperties', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.shareQuotaIsValid(userOptions.quota);
    v.callback(callback);
  });
  
  var options = extend(true, properties, userOptions);
  var resourceName = createResourceName(share);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'properties')
    .withHeader(HeaderConstants.SHARE_QUOTA, options.quota);
  
  FileResult.setProperties(webResource, options);
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Gets the share statistics for a share.
*
* @this {FileService}
* @param {string}           share                                   The share name.
* @param {object}           [options]                               The request options.
* @param {LocationMode}     [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                                   Please see StorageUtilities.LocationMode for the possible values.
* @param {int}              [options.timeoutIntervalInMs]           The timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]    The timeout of client requests, in milliseconds, to use for the request.
* @param {int}              [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                   The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                   execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}           [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}             [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                   The default value is false.
* @param {errorOrResult}    callback                                `error` will contain information if an error occurs; otherwise, `[result]{@link ServiceStats}` will contain the stats and 
*                                                                   `response` will contain information related to this operation.
*/
FileService.prototype.getShareStats = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('getShareStats', function (v) {
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share);
  var webResource = WebResource.get(resourceName)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'stats');
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = ShareResult.parse(responseObject.response.body, share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    // function to be called after all filters
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };
    
    // call the first filter
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};


/**
* Returns all user-defined metadata for the share.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
*
* @this {FileService}
* @param {string}             share                                     The share name.
* @param {object}             [options]                                 The request options.
* @param {string}             [options.shareSnapshotId]                 The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                    Specifies the location mode used to decide which location the request should be sent to. 
*                                                                       Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]             The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]        The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]        The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                       The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                       execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                 A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]               Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                       The default value is false.
* @param {errorOrResult}      callback                                  `error` will contain information
*                                                                       if an error occurs; otherwise `[result]{@link ShareResult}` will contain
*                                                                       information for the share.
*                                                                       `response` will contain information related to this operation.
*/
FileService.prototype.getShareMetadata = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getShareMetadata', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.head(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'metadata')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Sets the share's metadata.
*
* Calling the Set Share Metadata operation overwrites all existing metadata that is associated with the share.
* It's not possible to modify an individual name/value pair.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             metadata                            The metadata key/value pairs.
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResponse}  callback                              `error` will contain information
*                                                                 if an error occurs; otherwise 
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.setShareMetadata = function (share, metadata, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('setShareMetadata', function (v) {
    v.string(share, 'share');
    v.object(metadata, 'metadata');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'metadata');

  webResource.addOptionalMetadataHeaders(metadata);

  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Gets the share's ACL.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `[result]{@link ShareAclResult}` will contain
*                                                                 information for the share.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.getShareAcl = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('getShareAcl', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  var webResource = WebResource.get(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'acl');
  
  options.requestLocationMode = Constants.RequestLocationMode.PRIMARY_OR_SECONDARY;
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
      responseObject.shareResult.signedIdentifiers = AclResult.parse(responseObject.response.body);
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Updates the share's ACL.
*
* @this {FileService}
* @param {string}                         share                               The share name.
* @param {Object.<string, AccessPolicy>}  signedIdentifiers                   The share ACL settings. See `[AccessPolicy]{@link AccessPolicy}` for detailed information.
* @param {object}                         [options]                           The request options.
* @param {int}                            [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                            [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                            [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                             The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                             execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}                         [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}                           [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                             The default value is false.
* @param {errorOrResult}                  callback                            `error` will contain information
*                                                                             if an error occurs; otherwise `[result]{@link ShareAclResult}` will contain
*                                                                             information for the share.
*                                                                             `response` will contain information related to this operation.
*/
FileService.prototype.setShareAcl = function (share, signedIdentifiers, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('setShareAcl', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  
  var policies = null;
  if (signedIdentifiers) {
    if(_.isArray(signedIdentifiers)) {
      throw new TypeError(SR.INVALID_SIGNED_IDENTIFIERS);
    }
    policies = AclResult.serialize(signedIdentifiers);
  }
  
  var webResource = WebResource.put(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.COMP, 'acl')
    .withHeader(HeaderConstants.CONTENT_LENGTH, !azureutil.objectIsNull(policies) ? Buffer.byteLength(policies) : 0)
    .withBody(policies);
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.shareResult = null;
    if (!responseObject.error) {
      responseObject.shareResult = new ShareResult(share);
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
      if (signedIdentifiers) {
        responseObject.shareResult.signedIdentifiers = signedIdentifiers;
      }
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, webResource.body, options, processResponseCallback);
};

/**
* Marks the specified share for deletion.
* The share and any files contained within it are later deleted during garbage collection.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {string}             [options.deleteSnapshots]           The snapshot delete option. See azure.FileUtilities.ShareSnapshotDeleteOptions.*. 
* @param {string}             [options.shareSnapshotId]           The share snapshot identifier.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResponse}  callback                              `error` will contain information
*                                                                 if an error occurs; otherwise
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.deleteShare = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteShare', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  if (!azureutil.objectIsNull(options.shareSnapshotId) && !azureutil.objectIsNull(options.deleteSnapshots)) {
    throw new ArgumentError('options', SR.INVALID_DELETE_SNAPSHOT_OPTION);
  }

  var webResource = WebResource.del(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId)
    .withHeader(HeaderConstants.DELETE_SNAPSHOT, options.deleteSnapshots);

  var processResponseCallback = function (responseObject, next) {
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Marks the specified share for deletion if it exists.
* The share and any files contained within it are later deleted during garbage collection.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {object}             [options]                           The request options.
* @param {string}             [options.deleteSnapshots]           The snapshot delete option. See azure.FileUtilities.ShareSnapshotDeleteOptions.*. 
* @param {string}             [options.shareSnapshotId]           The share snapshot identifier.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will 
*                                                                 be true if the share exists and was deleted, or false if the share
*                                                                 did not exist.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.deleteShareIfExists = function (share, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteShareIfExists', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var self = this;
  self._doesShareExist(share, true, options, function (error, result, response) {
    if(error){
      callback(error, result.exists, response);
    } else if (!result.exists) {
      response.isSuccessful = true;
      callback(error, false, response);
    } else {
      self.deleteShare(share, options, function (deleteError, deleteResponse) {
        var deleted;
        if (!deleteError){
          deleted = true;
        } else if (deleteError && deleteError.statuscode === HttpConstants.HttpResponseCodes.NotFound && deleteError.code === Constants.FileErrorCodeStrings.SHARE_NOT_FOUND) {
          // If it was deleted already, there was no actual error.
          deleted = false;
          deleteError = null;
          deleteResponse.isSuccessful = true;
        }

        callback(deleteError, deleted, deleteResponse);
      });
    }
  });
};

// Directory methods

/**
* Checks whether or not a directory exists on the service.
*
* @this {FileService}
* @param {string}             share                                   The share name.
* @param {string}             directory                               The directory name. Use '' to refer to the base directory.
* @param {object}             [options]                               The request options.
* @param {string}             [options.shareSnapshotId]               The share snapshot identifier.
* @param {LocationMode}       [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                                     Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]           The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]      The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                     The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                     execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                     The default value is false.
* @param {errorOrResult}      callback                                `error` will contain information
*                                                                     if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                     the directory information including `exists` boolean member.
*                                                                     `response` will contain information related to this operation.
*/
FileService.prototype.doesDirectoryExist = function (share, directory, optionsOrCallback, callback) {
  this._doesDirectoryExist(share, directory, false, optionsOrCallback, callback);
};

/**
* Creates a new directory under the specified account.
* If a directory with the same name already exists, the operation fails.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name.
* @param {object}             [options]                           The request options.
* @param {object}             [options.metadata]                  The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.    
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                 the directory information.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.createDirectory = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createDirectory', function (v) {
    v.string(share, 'share');
    v.string(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(createResourceName(share, directory))
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory');

  webResource.addOptionalMetadataHeaders(options.metadata);

  var processResponseCallback = function (responseObject, next) {
    responseObject.directoryResult = null;
    if (!responseObject.error) {
      responseObject.directoryResult = new DirectoryResult(directory);
      responseObject.directoryResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.directoryResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Creates a new directory under the specified account if the directory does not exists.
*
* @this {FileService}
* @param {string}             share                                     The share name.
* @param {string}             directory                                 The directory name.
* @param {object}             [options]                                 The request options.
* @param {object}             [options.metadata]                        The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                    Specifies the location mode used to decide which location the request should be sent to. 
*                                                                       Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]             The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]        The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]        The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                       The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                       execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                 A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]               Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                       The default value is false.
* @param {errorOrResult}      callback                                  `error` will contain information
*                                                                       if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                       the directory information including `created` boolean member.
*                                                                       already exists.
*                                                                       `response` will contain information related to this operation.
*
* @example
* var azure = require('azure-storage');
* var FileService = azure.createFileService();
* FileService.createDirectoryIfNotExists('taskshare', taskdirectory', function(error) {
*   if(!error) {
*     // Directory created or already existed
*   }
* }); 
*/
FileService.prototype.createDirectoryIfNotExists = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createDirectoryIfNotExists', function (v) {
    v.string(share, 'share');
    v.string(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  delete options.shareSnapshotId;
  
  var self = this;
  self._doesDirectoryExist(share, directory, true, options, function(error, result, response) {
    var exists = result.exists;
    result.created = false;
    delete result.exists;
    
    if(error){
      callback(error, result, response);
    } else if (exists) {
      response.isSuccessful = true;
      callback(error, result, response);
    } else {
      self.createDirectory(share, directory, options, function (createError, responseDirectory, createResponse) {
        if(!createError){
          responseDirectory.created = true;
        }
        else if (createError && createError.statusCode === HttpConstants.HttpResponseCodes.Conflict && createError.code === Constants.StorageErrorCodeStrings.RESOURCE_ALREADY_EXISTS) {
          // If it was created before, there was no actual error.
          createError = null;
          createResponse.isSuccessful = true;
        }

        callback(createError, responseDirectory, createResponse);
      });
    }
  });
};

/**
* Retrieves a directory and its properties from a specified account.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name. Use '' to refer to the base directory.
* @param {object}             [options]                           The request options.
* @param {string}             [options.shareSnapshotId]           The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                 information for the directory.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.getDirectoryProperties = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getDirectoryProperties', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.head(createResourceName(share, directory))
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);
    
  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.directoryResult = null;
    if (!responseObject.error) {
      responseObject.directoryResult = new DirectoryResult(directory);
      responseObject.directoryResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.directoryResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.directoryResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Marks the specified directory for deletion. The directory must be empty before it can be deleted.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name.
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResponse}  callback                              `error` will contain information
*                                                                 if an error occurs; otherwise
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.deleteDirectory = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteDirectory', function (v) {
    v.string(share, 'share');
    v.string(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.del(createResourceName(share, directory))
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory');
  
  var processResponseCallback = function (responseObject, next) {
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Marks the specified directory for deletion if it exists. The directory must be empty before it can be deleted.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name.
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will 
*                                                                 be true if the directory exists and was deleted, or false if the directory
*                                                                 did not exist.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.deleteDirectoryIfExists = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteDirectoryIfExists', function (v) {
    v.string(share, 'share');
    v.string(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  delete options.shareSnapshotId;

  var self = this;
  self._doesDirectoryExist(share, directory, true, options, function(error, result, response) {
    if(error){
      callback(error, result.exists, response);
    } else if (!result.exists) {
      response.isSuccessful = true;
      callback(error, false, response);
    } else {
      self.deleteDirectory(share, directory, options, function (deleteError, deleteResponse) {
        var deleted;
        if (!deleteError){
          deleted = true;
        } else if (deleteError && deleteError.statuscode === HttpConstants.HttpResponseCodes.NotFound && deleteError.code === Constants.StorageErrorCodeStrings.RESOURCE_NOT_FOUND) {
          // If it was deleted already, there was no actual error.
          deleted = false;
          deleteError = null;
          deleteResponse.isSuccessful = true;
        }

        callback(deleteError, deleted, deleteResponse);
      });
    }
  });
};

/**
* Lists a segment containing a collection of file items in the directory.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name. Use '' to refer to the base directory.
* @param {object}             currentToken                        A continuation token returned by a previous listing operation. Please use 'null' or 'undefined' if this is the first operation.
* @param {object}             [options]                           The request options.
* @param {string}             [options.shareSnapshotId]           The snapshot identifier of the share.
* @param {int}                [options.maxResults]                Specifies the maximum number of files to return per call to Azure ServiceClient. This does NOT affect list size returned by this function. (maximum: 5000)
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will contain
*                                                                 entries.files which contains a list of `[files]{@link FileResult}`, entries.directories which contains a list of `[directories]{@link DirectoryResult}` and the continuationToken for the next listing operation.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.listFilesAndDirectoriesSegmented = function (share, directory, currentToken, optionsOrCallback, callback) {
  this.listFilesAndDirectoriesSegmentedWithPrefix(share, directory, null /*prefix*/, currentToken, optionsOrCallback, callback);
};


/**
* Lists a segment containing a collection of file items in the directory.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name. Use '' to refer to the base directory.
* @param {string}             prefix                              The prefix of the directory/files name.
* @param {object}             currentToken                        A continuation token returned by a previous listing operation. Please use 'null' or 'undefined' if this is the first operation.
* @param {object}             [options]                           The request options.
* @param {string}             [options.shareSnapshotId]           The snapshot identifier of the share.
* @param {int}                [options.maxResults]                Specifies the maximum number of files to return per call to Azure ServiceClient. This does NOT affect list size returned by this function. (maximum: 5000)
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will contain
*                                                                 entries.files which contains a list of `[files]{@link FileResult}`, entries.directories which contains a list of `[directories]{@link DirectoryResult}` and the continuationToken for the next listing operation.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.listFilesAndDirectoriesSegmentedWithPrefix = function (share, directory, prefix, currentToken, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('listFilesSegmented', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.get(createResourceName(share, directory))
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory')
    .withQueryOption(QueryStringConstants.COMP, 'list')
    .withQueryOption(QueryStringConstants.MAX_RESULTS, options.maxResults)
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  if (!azureutil.objectIsNull(currentToken)) {
    webResource.withQueryOption(QueryStringConstants.MARKER, currentToken.nextMarker);
  }

  webResource.withQueryOption(QueryStringConstants.PREFIX, prefix);

  var processResponseCallback = function (responseObject, next) {
    responseObject.listResult = null;
    if (!responseObject.error) {
      responseObject.listResult = {
        entries: null,
        continuationToken: null
      };

      responseObject.listResult.entries = {};
      responseObject.listResult.entries.files = [];
      responseObject.listResult.entries.directories = [];
      var files = [];
      var directories = [];

      // parse files
      if (responseObject.response.body.EnumerationResults.Entries.File) {
        files = responseObject.response.body.EnumerationResults.Entries.File;
        if (!_.isArray(files)) {
          files = [ files ];
        }
      }

      files.forEach(function (currentFile) {
        var fileResult = FileResult.parse(currentFile);
        responseObject.listResult.entries.files.push(fileResult);
      });

      // parse directories
      if (responseObject.response.body.EnumerationResults.Entries.Directory) {
        directories = responseObject.response.body.EnumerationResults.Entries.Directory;
        if (!_.isArray(directories)) {
          directories = [ directories ];
        }
      }

      directories.forEach(function (currentDirectory) {
        var directoryResult = DirectoryResult.parse(currentDirectory);
        responseObject.listResult.entries.directories.push(directoryResult);
      });

      // parse continuation token
      if(responseObject.response.body.EnumerationResults.NextMarker) {
        responseObject.listResult.continuationToken = {
          nextMarker: null,
          targetLocation: null
        };

        responseObject.listResult.continuationToken.nextMarker = responseObject.response.body.EnumerationResults.NextMarker;
        responseObject.listResult.continuationToken.targetLocation = responseObject.targetLocation;
      }
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.listResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Returns all user-defined metadata for the specified directory.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                         information about the directory.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.getDirectoryMetadata = function (share, directory, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('getDirectoryMetadata', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory);
  var webResource = WebResource.head(resourceName)
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory')
    .withQueryOption(QueryStringConstants.COMP, 'metadata')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.directoryResult = null;
    if (!responseObject.error) {
      responseObject.directoryResult = new DirectoryResult(directory);
      responseObject.directoryResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.directoryResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.directoryResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Sets user-defined metadata for the specified directory as one or more name-value pairs 
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {object}             metadata                                    The metadata key/value pairs.
* @param {object}             [options]                                   The request options.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link DirectoryResult}` will contain
*                                                                         information on the directory.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.setDirectoryMetadata = function (share, directory, metadata, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('setDirectoryMetadata', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
    v.object(metadata, 'metadata');
    v.callback(callback);
  });
  
  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory')
    .withQueryOption(QueryStringConstants.COMP, 'metadata');
  
  webResource.addOptionalMetadataHeaders(metadata);
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.directoryResult = null;
    if (!responseObject.error) {
      responseObject.directoryResult = new DirectoryResult(directory);
      responseObject.directoryResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.directoryResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

// File methods

/**
* Retrieves a shared access signature token.
*
* @this {FileService}
* @param {string}                   share                                               The share name.
* @param {string}                   [directory]                                         The directory name. Use '' to refer to the base directory.
* @param {string}                   [file]                                              The file name.
* @param {object}                   sharedAccessPolicy                                  The shared access policy.
* @param {string}                   [sharedAccessPolicy.Id]                             The signed identifier.
* @param {object}                   [sharedAccessPolicy.AccessPolicy.Permissions]       The permission type.
* @param {date|string}              [sharedAccessPolicy.AccessPolicy.Start]             The time at which the Shared Access Signature becomes valid (The UTC value will be used).
* @param {date|string}              [sharedAccessPolicy.AccessPolicy.Expiry]            The time at which the Shared Access Signature becomes expired (The UTC value will be used).
* @param {string}                   [sharedAccessPolicy.AccessPolicy.IPAddressOrRange]  An IP address or a range of IP addresses from which to accept requests. When specifying a range, note that the range is inclusive.
* @param {string}                   [sharedAccessPolicy.AccessPolicy.Protocols]         The protocols permitted for a request made with the account SAS. 
*                                                                                       Possible values are both HTTPS and HTTP (https,http) or HTTPS only (https). The default value is https,http.
* @param {object}                   [headers]                                           The optional header values to set for a file returned wth this SAS.
* @param {string}                   [headers.cacheControl]                              The optional value of the Cache-Control response header to be returned when this SAS is used.
* @param {string}                   [headers.contentType]                               The optional value of the Content-Type response header to be returned when this SAS is used.
* @param {string}                   [headers.contentEncoding]                           The optional value of the Content-Encoding response header to be returned when this SAS is used.
* @param {string}                   [headers.contentLanguage]                           The optional value of the Content-Language response header to be returned when this SAS is used.
* @param {string}                   [headers.contentDisposition]                        The optional value of the Content-Disposition response header to be returned when this SAS is used.
* @return {string}                                                                      The shared access signature query string. Note this string does not contain the leading "?".
*/
FileService.prototype.generateSharedAccessSignature = function (share, directory, file, sharedAccessPolicy, headers) {
  // check if the FileService is able to generate a shared access signature
  if (!this.storageCredentials || !this.storageCredentials.generateSignedQueryString) {
    throw new Error(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY);
  }

  // Validate share name. File name is optional.
  validate.validateArgs('generateSharedAccessSignature', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.object(sharedAccessPolicy, 'sharedAccessPolicy');
  });

  var resourceType = FileConstants.ResourceTypes.SHARE;
  if (file) {
    validate.validateArgs('generateSharedAccessSignature', function (v) {
      v.stringAllowEmpty(directory, 'directory');
      v.string(file, 'file');
    });
    resourceType = FileConstants.ResourceTypes.FILE;
  } else {
    directory = ''; // If file is not set, directory is not a part of the string to sign.
  }

  if (sharedAccessPolicy.AccessPolicy) {
    if (!azureutil.objectIsNull(sharedAccessPolicy.AccessPolicy.Start)) {
      if (!_.isDate(sharedAccessPolicy.AccessPolicy.Start)) {
        sharedAccessPolicy.AccessPolicy.Start = new Date(sharedAccessPolicy.AccessPolicy.Start);
      }
 
      sharedAccessPolicy.AccessPolicy.Start = azureutil.truncatedISO8061Date(sharedAccessPolicy.AccessPolicy.Start);
    }

    if (!azureutil.objectIsNull(sharedAccessPolicy.AccessPolicy.Expiry)) {
      if (!_.isDate(sharedAccessPolicy.AccessPolicy.Expiry)) {
        sharedAccessPolicy.AccessPolicy.Expiry = new Date(sharedAccessPolicy.AccessPolicy.Expiry);
      }

      sharedAccessPolicy.AccessPolicy.Expiry = azureutil.truncatedISO8061Date(sharedAccessPolicy.AccessPolicy.Expiry);
    }
  }

  var resourceName = createResourceName(share, directory, file, true);
  return this.storageCredentials.generateSignedQueryString(Constants.ServiceType.File, resourceName, sharedAccessPolicy, null, { headers: headers, resourceType: resourceType });
};

/**
* Retrieves a file or directory URL.
*
* @param {string}                   share                    The share name.
* @param {string}                   directory                The directory name. Use '' to refer to the base directory.
* @param {string}                   [file]                   The file name. File names may not start or end with the delimiter '/'.
* @param {string}                   [sasToken]               The Shared Access Signature token.
* @param {boolean}                  [primary]                A boolean representing whether to use the primary or the secondary endpoint.
* @param {string}                   [shareSnapshotId]        The snapshot identifier of the share.
* @return {string}                                           The formatted URL string.
* @example
* var azure = require('azure-storage');
* var fileService = azure.createFileService();
* var sharedAccessPolicy = {
*   AccessPolicy: {
*     Permissions: azure.FileUtilities.SharedAccessPermissions.READ,
*     Start: startDate,
*     Expiry: expiryDate
*   },
* };
* 
* var sasToken = fileService.generateSharedAccessSignature(shareName, directoryName, fileName, sharedAccessPolicy);
* var url = fileService.getUrl(shareName, directoryName, fileName, sasToken, true);
*/
FileService.prototype.getUrl = function (share, directory, file, sasToken, primary, shareSnapshotId) {
  validate.validateArgs('getUrl', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
  });

  var host;
  if(!azureutil.objectIsNull(primary) && primary === false) {
    host = this.host.secondaryHost;
  } else {
    host = this.host.primaryHost;
  }
  host = azureutil.trimPortFromUri(host);
  if(host && host.lastIndexOf('/') !== (host.length - 1)){
    host = host + '/';
  }

  var name = createResourceName(share, directory, file);
  var query = qs.parse(sasToken);
  if(shareSnapshotId) {
    query[QueryStringConstants.SHARE_SNAPSHOT] = shareSnapshotId;
  }
  return url.resolve(host, url.format({pathname: this._getPath(name), query: query}));
};

/**
* Returns all user-defined metadata, standard HTTP properties, and system properties for the file.
* It does not return or modify the content of the file.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         information about the file.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.getFileProperties = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getFileProperties', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.head(resourceName)
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers, true);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Returns all user-defined metadata for the specified file.
* It does not modify or return the content of the file.
* **Note** that all metadata names returned from the server will be converted to lower case by NodeJS itself as metadata is set via HTTP headers and HTTP header names are case insensitive.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         information about the file.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.getFileMetadata = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getFileMetadata', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.head(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'metadata')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Sets user-defined properties for the specified file.
* It does not modify or return the content of the file.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [properties]                                The file properties to set.
* @param {string}             [properties.contentType]                    The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [properties.contentEncoding]                The content encodings that have been applied to the file.
* @param {string}             [properties.contentLanguage]                The natural languages used by this resource.
* @param {string}             [properties.cacheControl]                   The file's cache control.
* @param {string}             [properties.contentDisposition]             The file's content disposition.
* @param {string}             [properties.contentLength]                  Resizes a file to the specified size. If the specified byte value is less than the current size of the file, 
*                                                                         then all ranges above the specified byte value are cleared.
* @param {string}             [properties.contentMD5]                     The file's MD5 hash.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         information about the file.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.setFileProperties = function (share, directory, file, properties, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('setFileProperties', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {contentSettings: properties, contentLength: properties.contentLength }, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'properties');

  FileResult.setProperties(webResource, options);

  var processResponseCallback = function(responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function(returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Sets user-defined metadata for the specified file as one or more name-value pairs 
* It does not modify or return the content of the file.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             metadata                                    The metadata key/value pairs.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         information on the file.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.setFileMetadata = function (share, directory, file, metadata, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('setFileMetadata', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.object(metadata, 'metadata');
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'metadata');

  webResource.addOptionalMetadataHeaders(metadata);

  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Resizes a file.
*
* @this {FileService}
* @param {string}               share                                       The share name.
* @param {string}               directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}               file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {String}               size                                        The size of the file, in bytes.
* @param {object}               [options]                                   The request options.
* @param {int}                  [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                  [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                  [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}               [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}                 [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {errorOrResult}        callback                                    `error` will contain information
*                                                                           if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                           information about the file.
*                                                                           `response` will contain information related to this operation.
*/
FileService.prototype.resizeFile = function (share, directory, file, size, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('resizeFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.value(size);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'properties');

  webResource.withHeader(HeaderConstants.FILE_CONTENT_LENGTH, size);

  var processResponseCallback = function(responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function(returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);  
};

/**
* Checks whether or not a file exists on the service.
*
* @this {FileService}
* @param {string}             share                                   The share name.
* @param {string}             directory                               The directory name. Use '' to refer to the base directory.
* @param {string}             file                                    The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                               The request options.
* @param {string}             [options.shareSnapshotId]               The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                                     Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]           The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]      The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                     The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                     execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                     The default value is false.
* @param {Function(error, result, response)}  callback                `error` will contain information
*                                                                     if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                     the file information including the `exists` boolean member. 
*                                                                     `response` will contain information related to this operation.
*/
FileService.prototype.doesFileExist = function (share, directory, file, optionsOrCallback, callback) {
  this._doesFileExist(share, directory, file, false, optionsOrCallback, callback);
};

/**
* Creates a file of the specified length. If the file already exists on the service, it will be overwritten.
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'.
* @param {int}                length                                        The length of the file in bytes.
* @param {object}             [options]                                     The request options.
* @param {object}             [options.contentSettings]                     The file's content settings.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The file's MD5 hash.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]            The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {errorOrResult}      callback                                      `error` will contain information
*                                                                           if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                           the file information.
*                                                                           `response` will contain information related to this operation.
*/
FileService.prototype.createFile = function (share, directory, file, length, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  validate.validateArgs('createFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.value(length);
    v.callback(callback);
  });

  var resourceName = createResourceName(share, directory, file);
  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(resourceName)
    .withHeader(HeaderConstants.TYPE, 'file')
    .withHeader(HeaderConstants.FILE_CONTENT_LENGTH, length);

  FileResult.setProperties(webResource, options);

  var processResponseCallback = function(responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function(returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Marks the specified file for deletion. The file is later deleted during garbage collection.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResponse}  callback                                      `error` will contain information
*                                                                         if an error occurs; `response` will contain information related to this operation.
*/
FileService.prototype.deleteFile = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.del(resourceName);

  var processResponseCallback = function (responseObject, next) {
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Marks the specified file for deletion if it exists. The file is later deleted during garbage collection.
*
* @this {FileService}
* @param {string}             share                               The share name.
* @param {string}             directory                           The directory name. Use '' to refer to the base directory.
* @param {string}             file                                The file name. File names may not start or end with the delimiter '/'. 
* @param {object}             [options]                           The request options.
* @param {LocationMode}       [options.locationMode]              Specifies the location mode used to decide which location the request should be sent to. 
*                                                                 Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]       The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]  The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]  The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                 The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                 execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]           A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]         Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                 The default value is false.
* @param {errorOrResult}      callback                            `error` will contain information
*                                                                 if an error occurs; otherwise `result` will
*                                                                 be true if the file was deleted, or false if the file
*                                                                 does not exist.
*                                                                 `response` will contain information related to this operation.
*/
FileService.prototype.deleteFileIfExists = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('deleteFileIfExists', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  delete options.shareSnapshotId;

  var self = this;
  self._doesFileExist(share, directory, file, true, options, function(error, result, response) {
    if(error){
      callback(error, result.exists, response);
    } else if (!result.exists) {
      response.isSuccessful = true;
      callback(error, false, response);
    } else {
      self.deleteFile(share, directory, file, options, function (deleteError, deleteResponse) {
        var deleted;
        if (!deleteError){
          deleted = true;
        } else if (deleteError && deleteError.statusCode === Constants.HttpConstants.HttpResponseCodes.NotFound && deleteError.code === Constants.FileErrorCodeStrings.FILE_NOT_FOUND) {
          // If it was deleted already, there was no actual error.
          deleted = false;
          deleteError = null;
          deleteResponse.isSuccessful = true;
        }

        callback(deleteError, deleted, deleteResponse);
      });
    }
  });
};

/**
* Downloads a file into a text string.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {int}                [options.rangeStart]                        The range start.
* @param {int}                [options.rangeEnd]                          The range end.
* @param {boolean}            [options.disableContentMD5Validation]       When set to true, MD5 validation will be disabled when downloading files.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {FileService~FileToText}  callback                               `error` will contain information
*                                                                         if an error occurs; otherwise `text` will contain the file contents,
*                                                                         and `[file]{@link FileResult}` will contain the file information.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.getFileToText = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('getFileToText', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.get(resourceName)
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId)
    .withRawResponse();

  FileResult.setHeaders(webResource, options);
  this._setRangeContentMD5Header(webResource, options);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.text = null;
    responseObject.fileResult = null;

    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers, true);
      responseObject.fileResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.text = responseObject.response.body;

      self._validateLengthAndMD5(options, responseObject);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.text, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Provides a stream to read from a file.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {string}             [options.rangeStart]                        Return only the bytes of the file in the specified range.
* @param {string}             [options.rangeEnd]                          Return only the bytes of the file in the specified range.
* @param {boolean}            [options.useTransactionalMD5]               When set to true, Calculate and send/validate content MD5 for transactions.
* @param {boolean}            [options.disableContentMD5Validation]       When set to true, MD5 validation will be disabled when downloading files.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information if an error occurs; 
*                                                                         otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                         `response` will contain information related to this operation.
* @return {Readable}                                                      A Node.js Readable stream.
* @example
* var azure = require('azure-storage');
* var fileService = azure.createFileService();
* var writable = fs.createWriteStream(destinationFileNameTarget);
* fileService.createReadStream(shareName, directoryName, fileName).pipe(writable);
*/
FileService.prototype.createReadStream = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createReadStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
  });

  var options = extend(true, {}, userOptions);

  var readStream = new ChunkStream(options);
  this.getFileToStream(share, directory, file, readStream, options, function (error, fileResponse, response) {
    if(error) {
      readStream.emit('error', error);
    }

    if(callback) {
      callback(error, fileResponse, response);
    }
  });

  return readStream;
};

/**
* Downloads a file into a stream.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {Writable}           writeStream                                 The Node.js Writable stream.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {boolean}            [options.skipSizeCheck]                     Skip the size check to perform direct download.
*                                                                         Set the option to true for small files.
*                                                                         Parallel download and speed summary won't work with this option on.
* @param {SpeedSummary}       [options.speedSummary]                      The download tracker objects.
* @param {int}                [options.parallelOperationThreadCount]      The number of parallel operations that may be performed when uploading.
* @param {string}             [options.rangeStart]                        Return only the bytes of the file in the specified range.
* @param {string}             [options.rangeEnd]                          Return only the bytes of the file in the specified range. 
* @param {boolean}            [options.useTransactionalMD5]               When set to true, Calculate and send/validate content MD5 for transactions.
* @param {boolean}            [options.disableContentMD5Validation]       When set to true, MD5 validation will be disabled when downloading files.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information if an error occurs; 
*                                                                         otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                         `response` will contain information related to this operation.
* @return {SpeedSummary}
* 
*
* @example
* var azure = require('azure-storage');
* var FileService = azure.createFileService();
* FileService.getFileToStream('taskshare', taskdirectory', 'task1', fs.createWriteStream('task1-download.txt'), function(error, serverFile) {
*   if(!error) {
*     // file available in serverFile.file variable
*   }
* }); 
*/
FileService.prototype.getFileToStream = function (share, directory, file, writeStream, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  userOptions.speedSummary = userOptions.speedSummary || new SpeedSummary(file);  
  
  validate.validateArgs('getFileToStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.object(writeStream, 'writeStream');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);

  var propertiesRequestOptions = {
    timeoutIntervalInMs : options.timeoutIntervalInMs,
    clientRequestTimeoutInMs : options.clientRequestTimeoutInMs,
    accessConditions : options.accessConditions,
    shareSnapshotId : options.shareSnapshotId
  };
  
  if (options.skipSizeCheck) {
    this._getFileToStream(share, directory, file, writeStream, options, callback);
  } else {
    var self = this;
    this.getFileProperties(share, directory, file, propertiesRequestOptions, function (error, properties) {
      if (error) {
        callback(error);
      } else {
        var size;
        if (options.rangeStart) {
          var endOffset = properties.contentLength - 1;
          var end = options.rangeEnd ? Math.min(options.rangeEnd, endOffset) : endOffset;
          size = end - options.rangeStart + 1;
        } else {
          size = properties.contentLength;
        }
        options.speedSummary.totalSize = size;
        
        if (size > self.singleFileThresholdInBytes) {
          azureutil.setObjectInnerPropertyValue(options, ['contentSettings', 'contentMD5'], azureutil.tryGetValueChain(properties, ['contentSettings', 'contentMD5'], null));
          self._getFileToRangeStream(share, directory, file, writeStream, options, callback);
        } else {
          self._getFileToStream(share, directory, file, writeStream, options, callback);
        }
      }
    });
  }
  
  return options.speedSummary;
};

/**
* Lists file ranges. Lists all of the ranges by default, or only the ranges over a specific range of bytes if rangeStart and rangeEnd are specified.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {int}                [options.rangeStart]                        The range start.
* @param {int}                [options.rangeEnd]                          The range end.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         the range information.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.listRanges = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('listRanges', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var resourceName = createResourceName(share, directory, file);
  var options = extend(true, {}, userOptions);
  var webResource = WebResource.get(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'rangelist')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  FileResult.setHeaders(webResource, options);

  var processResponseCallback = function (responseObject, next) {
    responseObject.ranges = null;
    if (!responseObject.error) {
      responseObject.ranges = [];

      var ranges = [];
      if (responseObject.response.body.Ranges.Range) {
        ranges = responseObject.response.body.Ranges.Range;

        if (!_.isArray(ranges)) {
          ranges = [ ranges ];
        }
      }

      ranges.forEach(function (fileRange) {
        var range = {
          start: parseInt(fileRange.Start, 10),
          end: parseInt(fileRange.End, 10)
        };

        responseObject.ranges.push(range);
      });
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.ranges, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Clears a range. Clears all of the ranges by default, or only the ranges over a specific range of bytes if rangeStart and rangeEnd are specified.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {int}                rangeStart                                  The range start.
* @param {int}                rangeEnd                                    The range end.
* @param {object}             [options]                                   The request options.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         the directory information.
*                                                                        `response` will contain information related to this operation.
*/
FileService.prototype.clearRange = function (share, directory, file, rangeStart, rangeEnd, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('clearRange', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.value(rangeStart);
    v.value(rangeEnd);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var request = this._updateFilesImpl(share, directory, file, rangeStart, rangeEnd, FileConstants.RangeWriteOptions.CLEAR, options);

  var processResponseCallback = function(responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }
    
    var finalCallback = function(returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(request, null, options, processResponseCallback);
};

/**
* Updates a range from a stream.
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {Readable}           readStream                                  The Node.js Readable stream.
* @param {int}                rangeStart                                  The range start.
* @param {int}                rangeEnd                                    The range end.
* @param {object}             [options]                                   The request options.
* @param {bool}               [options.useTransactionalMD5]               Calculate and send/validate content MD5 for transactions.
* @param {string}             [options.transactionalContentMD5]           An optional hash value used to ensure transactional integrity for the page. 
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information
*                                                                         if an error occurs; otherwise `[result]{@link FileResult}` will contain
*                                                                         the file information.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype.createRangesFromStream = function (share, directory, file, readStream, rangeStart, rangeEnd, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createRangesFromStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.object(readStream, 'readStream');
    v.shareNameIsValid(share);
    v.value(rangeStart);
    v.value(rangeEnd);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var requiresContentMD5 = azureutil.objectIsNull(options.transactionalContentMD5) && options.useTransactionalMD5 === true;

  var length = (rangeEnd - rangeStart) + 1;
  if(length > FileConstants.MAX_UPDATE_FILE_SIZE) {
    throw new Error(SR.INVALID_FILE_RANGE_FOR_UPDATE);
  }

  var self = this;
  if (requiresContentMD5) {
    azureutil.calculateMD5(readStream, length, options, function(internalBuff, contentMD5) {
      options.transactionalContentMD5 = contentMD5;
      self._createRanges(share, directory, file, internalBuff, null /* stream */, rangeStart, rangeEnd, options, callback);
    });
  } else {
    self._createRanges(share, directory, file, null /* text */, readStream, rangeStart, rangeEnd, options, callback);
  }
};

/**
* Uploads a file from a text string. If the file already exists on the service, it will be overwritten.
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'.
* @param {string|object}      text                                          The file text, as a string or in a Buffer.
* @param {object}             [options]                                     The request options.
* @param {SpeedSummary}       [options.speedSummary]                        The upload tracker objects;
* @param {bool}               [options.storeFileContentMD5]                 Specifies whether the file's ContentMD5 header should be set on uploads. 
*                                                                           The default value is false for files.
* @param {bool}               [options.useTransactionalMD5]                 Calculate and send/validate content MD5 for transactions.
* @param {object}             [options.contentSettings]                     The file's content settings.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The file's MD5 hash.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]            The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {FileService~FileToText}  callback                                 `error` will contain information
*                                                                           if an error occurs; otherwise `text` will contain the file contents,
*                                                                           and `[file]{@link FileResult}` will contain the file information.
*                                                                           `response` will contain information related to this operation.
* @example
* var azure = require('azure-storage');
* var fileService = azure.createFileService();
*
* var text = 'Hello World!';
*
* fileService.createFileFromText('taskshare', 'taskdirectory', 'taskfile', text, function(error, result, response) {
*   if (!error) {
*     // file created
*   }
* });
*/
FileService.prototype.createFileFromText = function (share, directory, file, text, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createFileFromText', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var length = azureutil.objectIsNull(text) ? 0 : ((Buffer.isBuffer(text) ? text.length : Buffer.byteLength(text)));
  if (length > FileConstants.MAX_UPDATE_FILE_SIZE) {
    throw new Error(SR.INVALID_FILE_LENGTH);
  }

  if(options.storeFileContentMD5 && azureutil.objectIsNull(azureutil.tryGetValueChain(options, ['contentSettings', 'contentMD5'], null))) {
     azureutil.setObjectInnerPropertyValue(options, ['contentSettings', 'contentMD5'], azureutil.getContentMd5(text));
  }

  var self = this;
  this.createFile(share, directory, file, length, options, function(error, fileResult, response) {
    if(error || length === 0) {
      callback(error, fileResult, response);
    }
    else {
      self._createRanges(share, directory, file, text, null, 0, length - 1, options, callback);
    }
  });
};

/**
* Uploads a file from a stream. If the file already exists on the service, it will be overwritten.
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'. 
* @param (Stream)             stream                                        Stream to the data to store.
* @param {int}                streamLength                                  The length of the stream to upload.
* @param {object}             [options]                                     The request options.
* @param {SpeedSummary}       [options.speedSummary]                        The download tracker objects;
* @param {bool}               [options.storeFileContentMD5]                 Specifies whether the file's ContentMD5 header should be set on uploads. 
*                                                                           The default value is false for files.
* @param {bool}               [options.useTransactionalMD5]                 Calculate and send/validate content MD5 for transactions.
* @param {object}             [options.contentSettings]                     The file's content settings.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The file's MD5 hash.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]            The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {errorOrResult}      callback                                      `error` will contain information if an error occurs; 
*                                                                           otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                           `response` will contain information related to this operation.
* @return {SpeedSummary}
* @example
* var stream = require('stream');
* var azure = require('azure-storage');
* var fileService = azure.createFileService();
*
* var fileStream = new stream.Readable();
* fileStream.push(myFileBuffer);
* fileStream.push(null);
*
* fileService.createFileFromStream('taskshare', 'taskdirectory', 'taskfile', fileStream, myFileBuffer.length, function(error, result, response) {
*   if (!error) {
*     // file uploaded
*   }
* });
*/
FileService.prototype.createFileFromStream = function(share, directory, file, stream, streamLength, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createFileFromStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.object(stream, 'stream');
    v.value(streamLength, 'streamLength');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  options.speedSummary = options.speedSummary || new SpeedSummary(file);

  stream.pause(); // Immediately pause the stream in order to compatible with Node v0.8

  var self = this;
  this.createFile(share, directory, file, streamLength, options, function(error) {
    if(error) {
      callback(error);
    } else {
      var chunkStream = new ChunkStreamWithStream(stream, {calcContentMd5: options.storeFileContentMD5});
      self._createFileFromChunkStream(share, directory, file, chunkStream, streamLength, options, callback);
    }
  });

  return options.speedSummary;
};

/**
* Provides a stream to write to a file. Assumes that the file exists. 
* If it does not, please create the file using createFile before calling this method or use createWriteStreamNewFile.
* Please note the `Stream` returned by this API should be used with piping.
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'.
* @param {object}             [options]                                     The request options.
* @param {SpeedSummary}       [options.speedSummary]                        The download tracker objects;
* @param {bool}               [options.storeFileContentMD5]                 Specifies whether the file's ContentMD5 header should be set on uploads. 
*                                                                           The default value is false for files.
* @param {bool}               [options.useTransactionalMD5]                 Calculate and send/validate content MD5 for transactions.
* @param {object}             [options.contentSettings]                     The file's content settings.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The file's MD5 hash.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {errorOrResult}      callback                                      `error` will contain information if an error occurs; 
*                                                                           otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                           `response` will contain information related to this operation.
* @return {Writable}                                                        A Node.js Writable stream.
* @example
* var azure = require('azure-storage');
* var FileService = azure.createFileService();
* FileService.createFile(shareName, directoryName, fileName, 1024, function (err) {
*   // Pipe file to a file
*   var stream = fs.createReadStream(fileNameTarget).pipe(FileService.createWriteStreamToExistingFile(shareName, directoryName, fileName));
* });
*/
FileService.prototype.createWriteStreamToExistingFile = function (share, directory, file, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createWriteStreamToExistingFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
  });

  var options = extend(true, {}, userOptions);

  var stream = new ChunkStream({calcContentMd5: options.storeFileContentMD5});
  this._createFileFromChunkStream(share, directory, file, stream, null, options, function (error, file, response) {
    if(error) {
      stream.emit('error', error);
    }

    if (callback) {
      callback(error, file, response);
    }
  });

  return stream;
};

/**
* Provides a stream to write to a file. Creates the file before writing data.
* Please note the `Stream` returned by this API should be used with piping.
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'.
* @param {string}             length                                        The file length.
* @param {object}             [options]                                     The request options.
* @param {SpeedSummary}       [options.speedSummary]                        The download tracker objects;
* @param {bool}               [options.storeFileContentMD5]                 Specifies whether the file's ContentMD5 header should be set on uploads. 
*                                                                           The default value is false for files.
* @param {bool}               [options.useTransactionalMD5]                 Calculate and send/validate content MD5 for transactions.
* @param {object}             [options.contentSettings]                     The file's content settings.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The file's MD5 hash.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]            The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {errorOrResult}      callback                                      `error` will contain information if an error occurs; 
*                                                                           otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                           `response` will contain information related to this operation.
* @return {Writable}                                                        A Node.js Writable stream.
* @example
* var azure = require('azure-storage');
* var FileService = azure.createFileService();
* var stream = fs.createReadStream(fileNameTarget).pipe(FileService.createWriteStreamToNewFile(shareName, directoryName, fileName));
*/
FileService.prototype.createWriteStreamToNewFile = function (share, directory, file, length, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('createWriteStreamToNewFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.value(length, 'length');
    v.shareNameIsValid(share);
  });

  var options = extend(true, {}, userOptions);

  var stream = new ChunkStream({calcContentMd5: options.storeFileContentMD5});
  stream.pause();
  
  var self = this;
  this.createFile(share, directory, file, length, options, function(error) {
    if(error) {
      stream.emit('error', error);
      callback(error);
    }
    else {
      stream.resume();
      self._createFileFromChunkStream(share, directory, file, stream, null, options, function (error, file, response) {
        if(error) {
          stream.emit('error', error);
        }

        if (callback) {
          callback(error, file, response);
        }
      });
    }
  });
  
  return stream;
};

/**
* Starts to copy a file to a destination within the storage account.
*
* @this {FileService}
* @param {string}             sourceUri                                 The source file or blob URI.
* @param {string}             targetShare                               The target share name.
* @param {string}             targetDirectory                           The target directory name.
* @param {string}             targetFile                                The target file name.
* @param {object}             [options]                                 The request options.
* @param {object}             [options.metadata]                        The target file metadata key/value pairs.
* @param {AccessConditions}   [options.accessConditions]                The access conditions.
* @param {AccessConditions}   [options.sourceAccessConditions]          The source access conditions.
* @param {LocationMode}       [options.locationMode]                    Specifies the location mode used to decide which location the request should be sent to. 
*                                                                       Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]             The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]        The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]        The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                       The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                       execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                 A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]               Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                       The default value is false.
* @param {errorOrResult}      callback                                  `error` will contain information if an error occurs; 
*                                                                       otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                       `response` will contain information related to this operation.
*/
FileService.prototype.startCopyFile = function (sourceUri, targetShare, targetDirectory, targetFile, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('startCopyFile', function (v) {
    v.string(targetShare, 'targetShare');
    v.stringAllowEmpty(targetDirectory, 'targetDirectory');
    v.string(targetFile, 'targetFile');
    v.shareNameIsValid(targetShare);
    v.callback(callback);
  });
  
  var targetResourceName = createResourceName(targetShare, targetDirectory, targetFile);
  
  var options = extend(true, {}, userOptions);
  
  var webResource = WebResource.put(targetResourceName)
    .withHeader(HeaderConstants.COPY_SOURCE, sourceUri)
    .addOptionalMetadataHeaders(options.metadata);
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(targetShare, targetDirectory, targetFile);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers, true);
      
      if (options.metadata) {
        responseObject.fileResult.metadata = options.metadata;
      }
    }
    
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Abort a file copy operation.
*
* @this {FileService}
* @param {string}             share                                     The destination share name.
* @param {string}             directory                                 The destination directory name.
* @param {string}             file                                      The destination file name.
* @param {string}             copyId                                    The copy operation identifier.
* @param {object}             [options]                                 The request options.
* @param {LocationMode}       [options.locationMode]                    Specifies the location mode used to decide which location the request should be sent to. 
*                                                                       Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]             The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]        The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]        The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                       The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                       execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                 A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]               Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                       The default value is false.
* @param {errorOrResult}      callback                                  `error` will contain information if an error occurs; 
*                                                                       otherwise `[result]{@link FileResult}` will contain the file information.
*                                                                       `response` will contain information related to this operation.
*/
FileService.prototype.abortCopyFile = function (share, directory, file, copyId, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });
  
  validate.validateArgs('abortCopyFile', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });
  
  var resourceName = createResourceName(share, directory, file);
  
  var options = extend(true, {}, userOptions);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.COPY_ID, copyId)
    .withQueryOption(QueryStringConstants.COMP, 'copy')
    .withHeader(HeaderConstants.COPY_ACTION, 'abort');
  
  var processResponseCallback = function (responseObject, next) {
    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.response);
    };
    
    next(responseObject, finalCallback);
  };
  
  this.performRequest(webResource, null, options, processResponseCallback);
};

// Internal Methods

/**
* Updates a file from text.
* @ignore
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {string}             text                                        The text string.
* @param {Readable}           readStream                                  The Node.js Readable stream.
* @param {int}                rangeStart                                  The range start.
* @param {int}                rangeEnd                                    The range end.
* @param {object}             [options]                                   The request options.
* @param {bool}               [options.useTransactionalMD5]               Calculate and send/validate content MD5 for transactions.
* @param {bool}               [options.transactionalContentMD5]           An MD5 hash of the content. This hash is used to verify the integrity of the data during transport.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {Function(error, file, response)}  callback                      `error` will contain information
*                                                                         if an error occurs; otherwise `file` will contain
*                                                                         the file information.
*                                                                         `response` will contain information related to this operation.
*/
FileService.prototype._createRanges = function (share, directory, file, text, readStream, rangeStart, rangeEnd, options, callback) {
  var request = this._updateFilesImpl(share, directory, file, rangeStart, rangeEnd, FileConstants.RangeWriteOptions.UPDATE, options);

  // At this point, we have already validated that the range is less than 4MB. Therefore, we just need to calculate the contentMD5 if required.
  if(!azureutil.objectIsNull(text) && azureutil.objectIsNull(options.transactionalContentMD5) && options.useTransactionalMD5 === true) {
    request.withHeader(HeaderConstants.CONTENT_MD5, azureutil.getContentMd5(text));
  }

  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;
    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  if(!azureutil.objectIsNull(text)) {
    this.performRequest(request, text, options, processResponseCallback);
  } else {
    this.performRequestOutputStream(request, readStream, options, processResponseCallback);
  }
};

/**
* Uploads a file from a stream.
* @ignore
*
* @this {FileService}
* @param {string}             share                                         The share name.
* @param {string}             directory                                     The directory name. Use '' to refer to the base directory.
* @param {string}             file                                          The file name. File names may not start or end with the delimiter '/'.
* @param (Stream)             stream                                        Stream to the data to store.
* @param {int}                streamLength                                  The length of the stream to upload.
* @param {object|function}    [options]                                     The request options.
* @param {SpeedSummary}       [options.speedSummary]                        The download tracker objects;
* @param {int}                [options.parallelOperationThreadCount]        The number of parallel operations that may be performed when uploading.
* @param {bool}               [options.useTransactionalMD5]                 Calculate and send/validate content MD5 for transactions.
* @param {bool}               [options.storeFileContentMD5]                 Specifies whether the file's ContentMD5 header should be set on uploads.
* @param {string}             [options.contentSettings.contentType]         The MIME content type of the file. The default type is application/octet-stream.
* @param {string}             [options.contentSettings.contentEncoding]     The content encodings that have been applied to the file.
* @param {string}             [options.contentSettings.contentLanguage]     The natural languages used by this resource.
* @param {string}             [options.contentSettings.cacheControl]        The file service stores this value but does not use or modify it.
* @param {string}             [options.contentSettings.contentDisposition]  The file's content disposition.
* @param {string}             [options.contentSettings.contentMD5]          The MD5 hash of the file content.
* @param {object}             [options.metadata]                            The metadata key/value pairs.
* @param {LocationMode}       [options.locationMode]                        Specifies the location mode used to decide which location the request should be sent to. 
*                                                                           Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                 The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]            The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]            The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                           The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                           execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                     A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                   Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                           The default value is false.
* @param {function(error, null)}  callback                                  The callback function.
* @return {SpeedSummary}
*/
FileService.prototype._createFileFromChunkStream = function(share, directory, file, chunkStream, streamLength, options, callback) {
  this.logger.debug(util.format('_createFileFromChunkStream for file %s', file));

  var apiName = '_createRanges';
  var sizeLimitation = FileConstants.DEFAULT_WRITE_SIZE_IN_BYTES;
  var originalContentMD5 = azureutil.tryGetValueChain(options, ['contentSettings', 'contentMD5'], null);

  this._setOperationExpiryTime(options);

  // initialize the speed summary
  var speedSummary = options.speedSummary || new SpeedSummary();
  speedSummary.totalSize = streamLength;

  var parallelOperationThreadCount = options.parallelOperationThreadCount || this.parallelOperationThreadCount;

  // initialize chunk allocator
  var allocator = new ChunkAllocator(sizeLimitation, parallelOperationThreadCount, { logger: this.logger });

  // if this is a FileReadStream, set the allocator on that stream
  if (chunkStream._stream && chunkStream._stream.setMemoryAllocator) {
    chunkStream._stream.setMemoryAllocator(allocator);
  }

  // initialize batch operations
  var batchOperations = new BatchOperation(apiName, { logger : this.logger, enableReuseSocket : this.defaultEnableReuseSocket});
  batchOperations.setConcurrency(parallelOperationThreadCount);

  // initialize options
  var rangeOptions = {
    timeoutIntervalInMs: options.timeoutIntervalInMs,
    clientRequestTimeoutInMs: options.clientRequestTimeoutInMs,
    operationExpiryTime: options.operationExpiryTime
  };

  var self = this;
  chunkStream.on('data', function (data, range) {
    var operation = null;
    var full = false;
    var autoIncrement = speedSummary.getAutoIncrementFunction(data.length);

    if(data.length > sizeLimitation) {
      throw new Error(util.format(SR.EXCEEDED_SIZE_LIMITATION, sizeLimitation, data.length));
    }

    if (options.useTransactionalMD5) {
      //calculate content md5 for the current uploading block data
      var contentMD5 = azureutil.getContentMd5(data);
      rangeOptions.transactionalContentMD5 = contentMD5;
    }

    if (azureutil.isBufferAllZero(data)) {
      self.logger.debug(util.format('Skip upload data from %s bytes to %s bytes to file %s', range.start, range.end, file));
      speedSummary.increment(data.length);
    } else {
      operation = new BatchOperation.RestOperation(self, apiName, share, directory, file, data, null, range.start, range.end, rangeOptions, function (error) {
        if(!error) {
          autoIncrement();
        } else {
          self.logger.debug(util.format('Stop downloading data as error happens. Error: %s', util.inspect(error)));
          chunkStream.stop();
        }

        allocator.releaseBuffer(data);
        data = null;
      });
    }

    if (operation) {
      full = batchOperations.addOperation(operation);
      operation = null;

      if(full) {
        self.logger.debug('file stream paused');
        chunkStream.pause();
      }
    }
  });

  chunkStream.on('end', function () {
    self.logger.debug(util.format('File read stream ended for file %s', file));
    batchOperations.enableComplete();
  });

  batchOperations.on('drain', function () {
    self.logger.debug('File stream resume');
    chunkStream.resume();
  });

  batchOperations.on('end', function (error) {
    self.logger.debug('batch operations commited');
 
    if (error) {
      callback(error);
      return;
    }

    if (originalContentMD5) {
      options.contentSettings.contentMD5 = originalContentMD5;
    } else if (options.storeFileContentMD5) {
      azureutil.setObjectInnerPropertyValue(options, ['contentSettings', 'contentMD5'], chunkStream.getContentMd5('base64'));
    }

    // upload file completely
    var fileProperties = extend(false, options.contentSettings, { contentLength: options.streamLength });
    self.setFileProperties(share, directory, file, fileProperties, function (error, file, response) {
      chunkStream.finish();
      callback(error, file, response);
    });
  });

  return speedSummary;
};

/**
* Downloads a file into a stream.
* @ignore
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {Writable}           writeStream                                 The Node.js Writable stream.
* @param {object}             [options]                                   The request options.
* @param {string}             [options.shareSnapshotId]                   The snapshot identifier of the share.
* @param {string}             [options.rangeStart]                        Return only the bytes of the file in the specified range.
* @param {string}             [options.rangeEnd]                          Return only the bytes of the file in the specified range. 
* @param {boolean}            [options.useTransactionalMD5]               When set to true, Calculate and send/validate content MD5 for transactions.
* @param {boolean}            [options.disableContentMD5Validation]       When set to true, MD5 validation will be disabled when downloading files.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information if an error occurs; 
*                                                                         otherwise `result` will contain the file information.
*                                                                         `response` will contain information related to this operation.
*
* @return {SpeedSummary}
*/
FileService.prototype._getFileToStream = function (share, directory, file, writeStream, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('_getFileToStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.object(writeStream, 'writeStream');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.get(resourceName)
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId)
    .withRawResponse();

  FileResult.setHeaders(webResource, options);
  this._setRangeContentMD5Header(webResource, options);

  var self = this;
  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = null;

    if (!responseObject.error) {
      responseObject.fileResult = new FileResult(share, directory, file);
      responseObject.fileResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers, true);

      self._validateLengthAndMD5(options, responseObject);
            
      if (options.speedSummary) {
        options.speedSummary.increment(responseObject.length);
      }
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequestInputStream(webResource, null, writeStream, options, processResponseCallback);
};

/**
* Downloads a file into a range stream.
* @ignore
*
* @this {FileService}
* @param {string}             share                                       The share name.
* @param {string}             directory                                   The directory name. Use '' to refer to the base directory.
* @param {string}             file                                        The file name. File names may not start or end with the delimiter '/'.
* @param {Writable}           writeStream                                 The Node.js Writable stream.
* @param {object}             [options]                                   The request options.
* @param {int}                [options.parallelOperationThreadCount]      The number of parallel operations that may be performed when uploading.
* @param {string}             [options.rangeStart]                        Return only the bytes of the file in the specified range.
* @param {string}             [options.rangeEnd]                          Return only the bytes of the file in the specified range. 
* @param {boolean}            [options.useTransactionalMD5]               When set to true, Calculate and send/validate content MD5 for transactions.
* @param {boolean}            [options.disableContentMD5Validation]       When set to true, MD5 validation will be disabled when downloading files.
* @param {LocationMode}       [options.locationMode]                      Specifies the location mode used to decide which location the request should be sent to. 
*                                                                         Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]               The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]          The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]          The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                         The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                         execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                   A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                 Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                         The default value is false.
* @param {errorOrResult}      callback                                    `error` will contain information if an error occurs; 
*                                                                         otherwise `result` will contain the file information.
*                                                                         `response` will contain information related to this operation.
*
* @return {SpeedSummary}
*/
FileService.prototype._getFileToRangeStream = function (share, directory, file, writeStream, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('_getFileToRangeStream', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.object(writeStream, 'writeStream');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var speedSummary = userOptions.speedSummary || new SpeedSummary(file);
  var parallelOperationThreadCount = userOptions.parallelOperationThreadCount || this.parallelOperationThreadCount;
  var batchOperations = new BatchOperation('getfile', { callbackInOrder: true, logger : this.logger, enableReuseSocket : this.defaultEnableReuseSocket });
  batchOperations.setConcurrency(parallelOperationThreadCount);

  var rangeStream = new FileRangeStream(this, share, directory, file, userOptions);

  var self = this;
  var checkMD5sum = !userOptions.disableContentMD5Validation;
  var md5Hash = null;
  if (checkMD5sum) {
    md5Hash = new Md5Wrapper().createMd5Hash();
  }

  var savedFileResult = null;
  var savedFileResponse = null;

  rangeStream.on('range', function (range) {
    if (!speedSummary.totalSize) {
      speedSummary.totalSize = rangeStream.rangeSize;
    }

    var requestOptions = {
      rangeStart : range.start,
      rangeEnd : range.end,
      responseEncoding : null //Use Buffer to store the response data
    };

    var rangeSize = range.size;
    requestOptions.shareSnapshotId = userOptions.shareSnapshotId;
    requestOptions.timeoutIntervalInMs = userOptions.timeoutIntervalInMs;
    requestOptions.clientRequestTimeoutInMs = userOptions.clientRequestTimeoutInMs;
    requestOptions.useTransactionalMD5 = userOptions.useTransactionalMD5;

    if (range.dataSize === 0) {
      var autoIncrement = speedSummary.getAutoIncrementFunction(rangeSize);
      //No operation to do and only wait for write zero to file in callback
      var writeZeroOperation = new BatchOperation.CommonOperation(BatchOperation.noOperation, function (error) {
        if (error) return;
        var bufferAvailable = azureutil.writeZerosToStream(writeStream, rangeSize, md5Hash, autoIncrement);
        //There is no need to pause the rangestream since we can perform http request and write disk at the same time
        self.logger.debug(util.format('Write %s bytes Zero from %s to %s', rangeSize, range.start, range.end));
        if (!bufferAvailable) {
          self.logger.debug('Write stream is full and pause batch operation');
          batchOperations.pause();
        }
      });
      batchOperations.addOperation(writeZeroOperation);
      return;
    }
      
    if (range.start > range.end) {
      return;
    }
      
    var operation = new BatchOperation.RestOperation(self, 'getFileToText', share, directory, file, requestOptions, function (error, content, fileResult, response) {
      if (!error) {
        if (rangeSize !== content.length) {
          self.logger.warn(util.format('Request %s bytes, but server returns %s bytes', rangeSize, content.length));
        }
        //Save one of the succeeded callback parameters and use them at the final callback
        if (!savedFileResult) {
          savedFileResult = fileResult;
        }
        if (!savedFileResponse) {
          savedFileResponse = response;
        }
        var autoIncrement = speedSummary.getAutoIncrementFunction(content.length);
        var bufferAvailable = writeStream.write(content, autoIncrement);
        if (!bufferAvailable) {
          self.logger.debug('Write stream is full and pause batch operation');
          batchOperations.pause();
        }
        if (md5Hash) {
          md5Hash.update(content);
        }
        content = null;
      } else {
        self.logger.debug(util.format('Stop downloading data as error happens. Error: %s', util.inspect(error)));
        rangeStream.stop();
      }
    });

    var full = batchOperations.addOperation(operation);
    if (full) {
      self.logger.debug('Pause range stream');
      rangeStream.pause();
    }    
  });
  
  rangeStream.on('end', function () {
    self.logger.debug('Range stream has ended.');
    batchOperations.enableComplete();
  });
  
  batchOperations.on('drain', function () {
    self.logger.debug('Resume range stream');
    rangeStream.resume();
  });
  
  writeStream.on('drain', function () {
    self.logger.debug('Resume batch operations');
    batchOperations.resume();
  });
  
  batchOperations.on('end', function (error) {
    self.logger.debug('Download completed!');
    if (error) {
      callback(error);
    } else {
      writeStream.end(function () {
        self.logger.debug('Write stream has ended');
        if (!savedFileResult) {
          savedFileResult = {};
        }
        azureutil.setObjectInnerPropertyValue(savedFileResult, ['contentSettings', 'contentMD5'], azureutil.tryGetValueChain(userOptions, ['contentSettings', 'contentMD5'], null));
        savedFileResult.clientSideContentMD5 = null;
        if (md5Hash) {
          savedFileResult.clientSideContentMD5 = md5Hash.digest('base64');
        }
        callback(error, savedFileResult, savedFileResponse);
      });
    }
  });
  
  var listOptions = {
    timeoutIntervalInMs : userOptions.timeoutIntervalInMs,
    clientRequestTimeoutInMs : userOptions.clientRequestTimeoutInMs,
  };
  
  rangeStream.list(listOptions);
  return speedSummary;
};

/**
* @ignore
*/
FileService.prototype._setRangeContentMD5Header = function (webResource, options) {
  if(!azureutil.objectIsNull(options.rangeStart) && options.useTransactionalMD5) {
    if(azureutil.objectIsNull(options.rangeEnd)) {
      throw new ArgumentNullError(util.format(SR.ARGUMENT_NULL_OR_EMPTY, options.rangeEndHeader));
    }

    var size = parseInt(options.rangeEnd, 10) - parseInt(options.rangeStart, 10) + 1;
    if (size > FileConstants.MAX_RANGE_GET_SIZE_WITH_MD5) {
      throw new Error(SR.INVALID_RANGE_FOR_MD5);
    } else {
      webResource.withHeader(HeaderConstants.RANGE_GET_CONTENT_MD5, 'true');
    }
  }
};

/**
* @ignore
*/
FileService.prototype._updateFilesImpl = function (share, directory, file, rangeStart, rangeEnd, writeMethod, options) {
  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.put(resourceName)
    .withQueryOption(QueryStringConstants.COMP, 'range')
    .withHeader(HeaderConstants.CONTENT_TYPE, 'application/octet-stream')
    .withHeader(HeaderConstants.FILE_WRITE, writeMethod);

  options.rangeStart = rangeStart;
  options.rangeEnd = rangeEnd;

  FileResult.setHeaders(webResource, options); 

  if(writeMethod === FileConstants.RangeWriteOptions.UPDATE) {
    var size = (rangeEnd - rangeStart) + 1;
    webResource.withHeader(HeaderConstants.CONTENT_LENGTH, size);
  } else {
    webResource.withHeader(HeaderConstants.CONTENT_LENGTH, 0);
  }

  return webResource;
};

/**
* @ignore
*/
FileService.prototype._validateLengthAndMD5 = function (options, responseObject) {
  var storedMD5 = responseObject.response.headers[Constants.HeaderConstants.CONTENT_MD5];
  var contentLength;

  if (!azureutil.objectIsNull(responseObject.response.headers[Constants.HeaderConstants.CONTENT_LENGTH])) {
    contentLength = parseInt(responseObject.response.headers[Constants.HeaderConstants.CONTENT_LENGTH], 10);
  }

  // If the user has not specified this option, the default value should be false.
  if(azureutil.objectIsNull(options.disableContentMD5Validation)) {
    options.disableContentMD5Validation = false;
  }

  // None of the below cases should be retried. So set the error in every case so the retry policy filter handle knows that it shouldn't be retried.
  if (options.disableContentMD5Validation === false && options.useTransactionalMD5 === true && azureutil.objectIsNull(storedMD5)) {
    responseObject.error = new Error(SR.MD5_NOT_PRESENT_ERROR);
    responseObject.retryable = false;
  }

  // Validate length and if required, MD5.
  // If getFileToText called this method, then the responseObject.length and responseObject.contentMD5 are not set. Calculate them first using responseObject.response.body and then validate.
  if(azureutil.objectIsNull(responseObject.length)) {
    if (typeof responseObject.response.body == 'string') {
      responseObject.length = Buffer.byteLength(responseObject.response.body);
    } else if (Buffer.isBuffer(responseObject.response.body)) {
      responseObject.length = responseObject.response.body.length;
    }
  }

  if(!azureutil.objectIsNull(contentLength) && responseObject.length !== contentLength) {
    responseObject.error = new Error(SR.CONTENT_LENGTH_MISMATCH);
    responseObject.retryable = false;
  }

  if(options.disableContentMD5Validation === false && azureutil.objectIsNull(responseObject.contentMD5)) {
    responseObject.contentMD5 = azureutil.getContentMd5(responseObject.response.body);
  }

  if (options.disableContentMD5Validation === false && !azureutil.objectIsNull(storedMD5) && storedMD5 !== responseObject.contentMD5) {
    responseObject.error = new Error(util.format(SR.HASH_MISMATCH, storedMD5, responseObject.contentMD5));
    responseObject.retryable = false;
  }
};

/**
* Checks whether or not a file exists on the service.
* @ignore
*
* @this {FileService}
* @param {string}             share                                             The share name.
* @param {string}             directory                                         The directory name. Use '' to refer to the base directory.
* @param {string}             file                                              The file name. File names may not start or end with the delimiter '/'.
* @param {string}             primaryOnly                                       If true, the request will be executed against the primary storage location.
* @param {object}             [options]                                         The request options.
* @param {string}             [options.shareSnapshotId]                         The snapshot identifier of the share.
* @param {LocationMode}       [options.locationMode]                            Specifies the location mode used to decide which location the request should be sent to. 
*                                                                               Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                     The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]                The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]                The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                               The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                               execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                         A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                       Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                               The default value is false.
* @param {Function(error, result, response)}  callback                          `error` will contain information
*                                                                               if an error occurs; otherwise `result` will contain
*                                                                               the file information including the `exists` boolean member.
*                                                                               `response` will contain information related to this operation.
*/
FileService.prototype._doesFileExist = function (share, directory, file, primaryOnly, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('FileExists', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.string(file, 'file');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);

  var resourceName = createResourceName(share, directory, file);
  var webResource = WebResource.head(resourceName)
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  /*if(primaryOnly === false) {
    options.requestLocationMode = RequestLocationMode.PRIMARY_OR_SECONDARY;
  }*/
  
  var processResponseCallback = function (responseObject, next) {
    responseObject.fileResult = new FileResult(share, directory, file);
    if (!responseObject.error) {
      responseObject.fileResult.exists = true;
      responseObject.fileResult.getPropertiesFromHeaders(responseObject.response.headers);
      
    } else if (responseObject.error && responseObject.error.statusCode === Constants.HttpConstants.HttpResponseCodes.NotFound) {
      responseObject.error = null;
      responseObject.fileResult.exists = false;
      responseObject.response.isSuccessful = true;
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.fileResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Checks whether or not a directory exists on the service.
* @ignore
*
* @this {FileService}
* @param {string}             share                                             The share name.
* @param {string}             directory                                         The directory name. Use '' to refer to the base directory.
* @param {string}             primaryOnly                                       If true, the request will be executed against the primary storage location.
* @param {object}             [options]                                         The request options.
* @param {string}             [options.shareSnapshotId]                         The share snapshot identifier.
* @param {LocationMode}       [options.locationMode]                            Specifies the location mode used to decide which location the request should be sent to. 
*                                                                               Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]                     The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]                The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]                The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                               The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                               execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]                         A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]                       Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                               The default value is false.
* @param {Function(error, result, response)}  callback                          `error` will contain information
*                                                                               if an error occurs; otherwise `result` will contain
*                                                                               the directory information including `exists` boolean member. 
*                                                                               `response` will contain information related to this operation.
*/
FileService.prototype._doesDirectoryExist = function (share, directory, primaryOnly, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('directoryExists', function (v) {
    v.string(share, 'share');
    v.stringAllowEmpty(directory, 'directory');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.head(createResourceName(share, directory))
    .withQueryOption(QueryStringConstants.RESTYPE, 'directory')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  /*if(primaryOnly === false) {
    options.requestLocationMode = RequestLocationMode.PRIMARY_OR_SECONDARY;
  }*/
    
  var self = this;
  var processResponseCallback = function(responseObject, next){
    responseObject.directoryResult = new DirectoryResult(directory);
    responseObject.directoryResult.exists = false;
    
    if (!responseObject.error) {
      responseObject.directoryResult.exists = true;
      responseObject.directoryResult.metadata = self.parseMetadataHeaders(responseObject.response.headers);
      responseObject.directoryResult.getPropertiesFromHeaders(responseObject.response.headers);
      
    } else if (responseObject.error && responseObject.error.statusCode === Constants.HttpConstants.HttpResponseCodes.NotFound) {
      responseObject.error = null;
      responseObject.response.isSuccessful = true;
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.directoryResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* Checks whether or not a share exists on the service.
* @ignore
*
* @this {FileService}
* @param {string}             share                                   The share name.
* @param {string}             [options.shareSnapshotId]               The share snapshot identifier.
* @param {string}             primaryOnly                             If true, the request will be executed against the primary storage location.
* @param {object}             [options]                               The request options.
* @param {LocationMode}       [options.locationMode]                  Specifies the location mode used to decide which location the request should be sent to. 
*                                                                     Please see StorageUtilities.LocationMode for the possible values.
* @param {int}                [options.timeoutIntervalInMs]           The server timeout interval, in milliseconds, to use for the request.
* @param {int}                [options.clientRequestTimeoutInMs]      The timeout of client requests, in milliseconds, to use for the request.
* @param {int}                [options.maximumExecutionTimeInMs]      The maximum execution time, in milliseconds, across all potential retries, to use when making this request.
*                                                                     The maximum execution time interval begins at the time that the client begins building the request. The maximum
*                                                                     execution time is checked intermittently while performing requests, and before executing retries.
* @param {string}             [options.clientRequestId]               A string that represents the client request ID with a 1KB character limit.
* @param {bool}               [options.useNagleAlgorithm]             Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false.
*                                                                     The default value is false.
* @param {Function(error, result, response)}      callback            `error` will contain information
*                                                                     if an error occurs; otherwise `result` will contain
*                                                                     the share information including `exists` boolean member.
*                                                                     `response` will contain information related to this operation.
*/
FileService.prototype._doesShareExist = function (share, primaryOnly, optionsOrCallback, callback) {
  var userOptions;
  azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; });

  validate.validateArgs('shareExists', function (v) {
    v.string(share, 'share');
    v.shareNameIsValid(share);
    v.callback(callback);
  });

  var options = extend(true, {}, userOptions);
  var webResource = WebResource.head(share)
    .withQueryOption(QueryStringConstants.RESTYPE, 'share')
    .withQueryOption(QueryStringConstants.SHARE_SNAPSHOT, options.shareSnapshotId);

  /*if(primaryOnly === false) {
    options.requestLocationMode = RequestLocationMode.PRIMARY_OR_SECONDARY;
  }*/
  
  var processResponseCallback = function(responseObject, next){
    responseObject.shareResult = new ShareResult(share);
    responseObject.shareResult.exists = false;
    
    if (!responseObject.error) {
      responseObject.shareResult.exists = true;
      responseObject.shareResult.getPropertiesFromHeaders(responseObject.response.headers);
      
    } else if (responseObject.error && responseObject.error.statusCode === Constants.HttpConstants.HttpResponseCodes.NotFound) {
      responseObject.error = null;
      responseObject.response.isSuccessful = true;
    }

    var finalCallback = function (returnObject) {
      callback(returnObject.error, returnObject.shareResult, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, null, options, processResponseCallback);
};

/**
* The callback for {FileService~getFileToText}.
* @typedef {function} FileService~FileToText
* @param {object} error      If an error occurs, the error information.
* @param {string} text       The text returned from the file.
* @param {object} file       Information about the file.
* @param {object} response   Information related to this operation.
*/

FileService.SpeedSummary = SpeedSummary;

module.exports = FileService;
