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

/**
* 1. Demonstrates how to upload all files from a given directory in parallel
* 
* 2. Demonstrates how to download all files from a given blob container to a given destination directory.
* 
* 3. Demonstrate making requests using AccessConditions.
*/

var fs = require('fs');

var azure;
if (fs.existsSync('absolute path to azure-storage.js')) {
  azure = require('absolute path to azure-storage');
} else {
  azure = require('azure-storage');
}

var container = 'updownsample3';
var blob = 'updownsample';
var blobAccess = 'updownaccesssample';

var blobService = azure.createBlobService()
      .withFilter(new azure.ExponentialRetryPolicyFilter());

// optionally set a proxy
/*var proxy = {
  protocol: 'http:',
  host: '127.0.0.1',
  port: 8888
};

blobService.setProxy(proxy);
*/

function uploadSample() {
  var processArguments = process.argv;
  if (processArguments.length !== 4) {
    console.log('Incorrect number of arguments. Should be: srcPath destPath');
    process.exit(1);
  } 

  var srcPath = processArguments[2];
  var destPath = processArguments[3];

  console.log('Starting blobuploaddownloadsample.');

  // Create the container
  createContainer(container, function () {
    
    // Demonstrates how to upload all files from a given directoy
    uploadBlobs(srcPath, container, function () {

      // Demonstrates how to download all files from a given
      // blob container to a given destination directory.
      downloadBlobs(container, destPath, function () {

        // Demonstrate making requests using AccessConditions.
        useAccessCondition(container, function () {

          // Delete the container
          deleteContainer(container, function () {
            console.log('Ending blobuploaddownloadsample.');
          });
        });
      });
    });
  });
}

function createContainer (container, callback) {
  // Create the container.
  blobService.createContainerIfNotExists(container, function (error) {
    if (error) {
      console.log(error);
    } else {
      console.log('Created the container ' + container);
      callback();
    }
  });
}

function uploadBlobs(sourceDirectoryPath, containerName, callback) {
  console.log('Entering uploadBlobs.');

  // validate directory is valid.
  if (!fs.existsSync(sourceDirectoryPath)) {
    console.log(sourceDirectoryPath + ' is an invalid directory path.');
  } else {
    // Search the directory and generate a list of files to upload.
    walk(sourceDirectoryPath, function (error, files) {
      if (error) {
        console.log(error);
      } else {
        var finished = 0;

        // generate and schedule an upload for each file
        files.forEach(function (file) {
          var blobName = file.replace(/^.*[\\\/]/, '');

          blobService.createBlockBlobFromLocalFile(containerName, blobName, file, function (error) {
            finished++;

            if (error) {
              console.log(error);
            } else {
              console.log(' Blob ' + blobName + ' upload finished.');

              if (finished === files.length) {
                // Wait until all workers complete and the blobs are uploaded to the server.
                console.log('All files uploaded');
                callback();
              }
            }
          });
        });
      }
    });
  }
}

function downloadBlobs(containerName, destinationDirectoryPath, callback) {
  console.log('Entering downloadBlobs.');

  // Validate directory
  if (!fs.existsSync(destinationDirectoryPath)) {
    console.log(destinationDirectoryPath + ' does not exist. Attempting to create this directory...');
    fs.mkdirSync(destinationDirectoryPath);
    console.log(destinationDirectoryPath + ' created.');
  }

  // NOTE: does not handle pagination.
  blobService.listBlobsSegmented(containerName, null, function (error, result) {
    if (error) {
      console.log(error);
    } else {
      var blobs = result.entries;
      var blobsDownloaded = 0;

      blobs.forEach(function (blob) {
          blobService.getBlobToLocalFile(containerName, blob.name, destinationDirectoryPath + '/' + blob.name, function (error2) {
          blobsDownloaded++;

          if (error2) {
            console.log(error2);
          } else {
            console.log(' Blob ' + blob.name + ' download finished.');

            if (blobsDownloaded === blobs.length) {
              // Wait until all workers complete and the blobs are downloaded
              console.log('All files downloaded');
              callback();
            }
          }
        });
      });
    }
  });
}

function useAccessCondition(containerName, callback) {
  console.log('Entering useAccessCondition.');

  // Create a blob.
  blobService.createBlockBlobFromText(containerName, blobAccess, 'hello', function (error, blobAccess) {
    if (error) {
      console.log(error);
    } else {
      console.log(' Created the blob ' + blobAccess.blob);
      console.log(' Blob Etag is: ' + blobAccess.etag);
          
      // Use the If-not-match ETag condition to access the blob. By
      // using the IfNoneMatch condition we are asserting that the blob needs
      // to have been modified in order to complete the request. In this
      // sample no other client is accessing the blob, so this will fail as
      // expected.
      var options = { accessConditions: { EtagNonMatch: blobAccess.etag} };
      blobService.createBlockBlobFromText(containerName, blobAccess.blob, 'new hello', options, function (error2) {
        if (error2 && error2.statusCode === 412 && error2.code === 'ConditionNotMet') {
          console.log('Attempted to recreate the blob with the if-none-match access condition and got the expected exception.');
          callback();
        } else {
          console.log(' Blob was incorrectly updated');
          if (error2) {
            console.log(error2);
          }
        }
      });
    }
  });
}

function deleteContainer (container, callback) {
  // Delete the container.
  blobService.deleteContainerIfExists(container, function (error) {
    if (error) {
      console.log(error);
    } else {
      console.log('Deleted the container ' + container);
      callback();
    }
  });
}

// Utility function

var walk = function (dir, done) {
  var results = [];
  fs.readdir(dir, function (err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = dir + '/' + file;
      fs.stat(file, function (err2, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function (err3, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};

uploadSample();
