/**
 * File system based on storage provider
 */

var fs = require('fs'),
  path = require('path'),
  async = require('async'),
  File = require('./file').File,
  Container = require('./container').Container;

module.exports.storage = module.exports; // To make it consistent with pkgcloud

module.exports.File = File;
module.exports.Container = Container;
module.exports.Client = FileSystemProvider;
module.exports.createClient = function (options) {
  return new FileSystemProvider(options);
};

function FileSystemProvider(options) {
  options = options || {};
  this.root = options.root;
  var exists = fs.existsSync(this.root);
  if (!exists) {
    throw new Error('FileSystemProvider: Path does not exist: ' + this.root);
  }
  var stat = fs.statSync(this.root);
  if (!stat.isDirectory()) {
    throw new Error('FileSystemProvider: Invalid directory: ' + this.root);
  }
}

var namePattern = new RegExp('[^' + path.sep + '/]+');

function validateName(name, cb) {
  if (!name) {
    cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name)));
    if (!cb) {
      console.error('FileSystemProvider: Invalid name: ', name);
    }
    return false;
  }
  var match = namePattern.exec(name);
  if (match && match.index === 0 && match[0].length === name.length) {
    return true;
  } else {
    cb && process.nextTick(cb.bind(null,
      new Error('FileSystemProvider: Invalid name: ' + name)));
    if (!cb) {
      console.error('FileSystemProvider: Invalid name: ', name);
    }
    return false;
  }
}

/*!
 * Populate the metadata from file stat into props
 * @param {fs.Stats} stat The file stat instance
 * @param {Object} props The metadata object
 */
function populateMetadata(stat, props) {
  for (var p in stat) {
    switch (p) {
      case 'size':
      case 'atime':
      case 'mtime':
      case 'ctime':
        props[p] = stat[p];
        break;
    }
  }
}

FileSystemProvider.prototype.getContainers = function (cb) {
  var self = this;
  fs.readdir(self.root, function (err, files) {
    var containers = [];
    var tasks = [];
    files.forEach(function (f) {
      tasks.push(fs.stat.bind(fs, path.join(self.root, f)));
    });
    async.parallel(tasks, function (err, stats) {
      if (err) {
        cb && cb(err);
      } else {
        stats.forEach(function (stat, index) {
          if (stat.isDirectory()) {
            var name = files[index];
            var props = {name: name};
            populateMetadata(stat, props);
            var container = new Container(self, props);
            containers.push(container);
          }
        });
        cb && cb(err, containers);
      }
    });
  });
};

FileSystemProvider.prototype.createContainer = function (options, cb) {
  var self = this;
  var name = options.name;
  var dir = path.join(this.root, name);
  validateName(name, cb) && fs.mkdir(dir, options, function (err) {
    if(err) {
      return cb && cb(err);
    }
    fs.stat(dir, function (err, stat) {
      var container = null;
      if (!err) {
        var props = {name: name};
        populateMetadata(stat, props);
        container = new Container(self, props);
      }
      cb && cb(err, container);
    });
  });
};

FileSystemProvider.prototype.destroyContainer = function (containerName, cb) {
  if (!validateName(containerName, cb)) return;

  var dir = path.join(this.root, containerName);
  fs.readdir(dir, function (err, files) {
    var tasks = [];
    files.forEach(function (f) {
      tasks.push(fs.unlink.bind(fs, path.join(dir, f)));
    });
    async.parallel(tasks, function (err) {
      if (err) {
        cb && cb(err);
      } else {
        fs.rmdir(dir, cb);
      }
    });
  });
};

FileSystemProvider.prototype.getContainer = function (containerName, cb) {
  var self = this;
  if (!validateName(containerName, cb)) return;
  var dir = path.join(this.root, containerName);
  fs.stat(dir, function (err, stat) {
    var container = null;
    if (!err) {
      var props = {name: containerName};
      populateMetadata(stat, props);
      container = new Container(self, props);
    }
    cb && cb(err, container);
  });
};

// File related functions
FileSystemProvider.prototype.upload = function (options, cb) {
  var container = options.container;
  if (!validateName(container, cb)) return;
  var file = options.remote;
  if (!validateName(file, cb)) return;
  var filePath = path.join(this.root, container, file);

  var fileOpts = {flags: options.flags || 'w+',
    encoding: options.encoding || null,
    mode: options.mode || 0666
  };

  try {
    return fs.createWriteStream(filePath, fileOpts);
  } catch (e) {
    cb && cb(e);
  }
};

FileSystemProvider.prototype.download = function (options, cb) {
  var container = options.container;
  if (!validateName(container, cb)) return;
  var file = options.remote;
  if (!validateName(file, cb)) return;

  var filePath = path.join(this.root, container, file);

  var fileOpts = {flags: 'r',
    autoClose: true };

  try {
    return fs.createReadStream(filePath, fileOpts);
  } catch (e) {
    cb && cb(e);
  }
};

FileSystemProvider.prototype.getFiles = function (container, options, cb) {
  if (typeof options === 'function' && !(options instanceof RegExp)) {
    cb = options;
    options = false;
  }
  var self = this;
  if (!validateName(container, cb)) return;
  var dir = path.join(this.root, container);
  fs.readdir(dir, function (err, entries) {
    entries = entries || [];
    var files = [];
    var tasks = [];
    entries.forEach(function (f) {
      tasks.push(fs.stat.bind(fs, path.join(dir, f)));
    });
    async.parallel(tasks, function (err, stats) {
      if (err) {
        cb && cb(err);
      } else {
        stats.forEach(function (stat, index) {
          if (stat.isFile()) {
            var props = {container: container, name: entries[index]};
            populateMetadata(stat, props);
            var file = new File(self, props);
            files.push(file);
          }
        });
        cb && cb(err, files);
      }
    });
  });
};

FileSystemProvider.prototype.getFile = function (container, file, cb) {
  var self = this;
  if (!validateName(container, cb)) return;
  if (!validateName(file, cb)) return;
  var filePath = path.join(this.root, container, file);
  fs.stat(filePath, function (err, stat) {
    var f = null;
    if (!err) {
      var props = {container: container, name: file};
      populateMetadata(stat, props);
      f = new File(self, props);
    }
    cb && cb(err, f);
  });
};

FileSystemProvider.prototype.getUrl = function (options) {
  options = options || {};
  var filePath = path.join(this.root, options.container, options.path);
  return filePath;
};

FileSystemProvider.prototype.removeFile = function (container, file, cb) {
  if (!validateName(container, cb)) return;
  if (!validateName(file, cb)) return;

  var filePath = path.join(this.root, container, file);
  fs.unlink(filePath, cb);
};
