'use strict';

/**
 * file-locking.js - file system locking (replaces fs-ext)
 */

const async = require('async');
const locker = require('lockfile');
const fs = require('fs');
const path = require('path');

// locks a file by creating a lock file
const lockFile = function(name, next) {
  const lockFileName = `${name}.lock`;
  const lockOpts = {
      wait: 1000,             // time (ms) to wait when checking for stale locks
      pollPeriod: 100,              // how often (ms) to re-check stale locks

      stale: 5 * 60 * 1000,    // locks are considered stale after 5 minutes

      retries: 100,              // number of times to attempt to create a lock
      retryWait: 100,               // time (ms) between tries
  };

  async.series({

    statdir: function(callback) {
      // test to see if the directory exists
      fs.stat(path.dirname(name), function(err, stats) {
        if (err) {
          callback(err);
        } else if (!stats.isDirectory()) {
          callback(new Error(path.dirname(name) + ' is not a directory'));
        } else {
          callback(null);
        }
      });
    },

    statfile: function(callback) {
      // test to see if the file to lock exists
      fs.stat(name, function(err, stats) {
        if (err) {
          callback(err);
        } else if (!stats.isFile()) {
          callback(new Error(path.dirname(name) + ' is not a file'));
        } else {
          callback(null);
        }
      });
    },

    lockfile: function(callback) {
      // try to lock the file
      locker.lock(lockFileName, lockOpts, callback);
    },

  }, function(err) {
    if (err) {
      // lock failed
      return next(err);
    }

    // lock succeeded
    return next(null);
  });
};

// unlocks file by removing existing lock file
const unlockFile= function(name, next) {
  const lockFileName = `${name}.lock`;
  locker.unlock(lockFileName, function(err) {
    if (err) {
      return next(err);
    }
    return next(null);
  });
};

/**
 *  Reads a local file, which involves
 *  optionally taking a lock
 *  reading the file contents
 *  optionally parsing JSON contents
 * @param {*} name
 * @param {*} options
 * @param {*} next
 */
function readFile(name, options, next) {
  if (typeof options === 'function' && next === null) {
    next = options;
    options = {};
  }

  options = options || {};
  options.lock = options.lock || false;
  options.parse = options.parse || false;

  const lock = function(callback) {
    if (!options.lock) {
      return callback(null);
    }

    lockFile(name, function(err) {
      if (err) {
        return callback(err);
      }
      return callback(null);
    });
  };

  const read = function(callback) {
    fs.readFile(name, 'utf8', function(err, contents) {
      if (err) {
        return callback(err);
      }

      callback(null, contents);
    });
  };

  const parseJSON = function(contents, callback) {
    if (!options.parse) {
      return callback(null, contents);
    }

    try {
      contents = JSON.parse(contents);
      return callback(null, contents);
    } catch (err) {
      return callback(err);
    }
  };

  async.waterfall([
    lock,
    read,
    parseJSON,
  ],

    function(err, result) {
      if (err) {
        return next(err);
      } else {
        return next(null, result);
      }
    });
}

exports.lockFile = lockFile;
exports.unlockFile = unlockFile;
exports.readFile = readFile;