'use strict';

var isObject = require('isobject');
var path = require('path');
var fs = require('fs');

/**
 * Expose `relative`
 */

module.exports = relative;

/**
 * Get the relative path from `a` to `b`.
 */

function relative(a, b, stat) {
  if (typeof a !== 'string') {
    throw new TypeError('relative expects a string.');
  }

  if (a == '' && !b) return a;

  var len = arguments.length;
  if (len === 1) {
    b = a; a = process.cwd(); stat = null;
  }

  if (len === 2 && typeof b === 'boolean') {
    b = a; a = process.cwd(); stat = true;
  }

  if (len === 2 && typeof b === 'object') {
    stat = b; b = a; a = process.cwd();
  }

  var origB = b;

  // see if a slash exists before normalizing
  var slashA = endsWith(a, '/');
  var slashB = endsWith(b, '/');

  a = unixify(a);
  b = unixify(b);

  // if `a` had a slash, add it back
  if (slashA) { a = a + '/'; }

  if (isFile(a, stat)) {
    a = path.dirname(a);
  }

  var res = path.relative(a, b);
  if (res === '') {
    return '.';
  }

  // if `b` originally had a slash, and the path ends
  // with `b` missing a slash, then re-add the slash.
  var noslash = trimEnd(origB, '/');
  if (slashB && (res === noslash || endsWith(res, noslash))) {
    res = res + '/';
  }
  return res;
}

/**
 * Get the path relative to the given base path.
 */

relative.toBase = function toBase(base, fp) {
  base = unixify(base);
  fp = unixify(fp);

  var res = fp.slice(base.length);
  if (res.charAt(0) === '/') {
    res = res.slice(1);
  }
  return res;
};

/**
 * Normalize slashes in paths to unix slashes. This is necessary since
 * paths are not calculated the same by node.js when
 * windows paths are used.
 */

function unixify(str) {
  return str.replace(/[\\\/]+/g, '/');
}

/**
 * Remove the given character from the path.
 */

function trimEnd(fp, ch) {
  return fp.slice(0, fp.length - ch.length);
}

/**
 * Does the path end with the given `ch`aracter?
 */

function endsWith(fp, ch) {
  return fp.slice(-ch.length)[0] === ch;
}

/**
 * If `fp` exists, return a `stat` object,
 * otherwise return `null`.
 */

function tryStats(fp) {
  try {
    return fs.statSync(fp);
  } catch(err) {}
  return null;
}

/**
 * Returns true if `fp` is a directory. To use a `fs`
 * stat object to actually check the file system,
 * either pass `true` as the second arg, or pass your
 * own stat object as the second arg.
 *
 * @param  {String} `fp`
 * @param  {Boolean|Object} `stat`
 * @return {Boolean}
 */

function isDir(fp, stat) {
  if (endsWith(fp, '/')) {
    return true;
  }

  if (stat === null) {
    // try to get the directory info if it hasn't been done yet
    // to ensure directories containing dots are well handle
    stat = tryStats(fp);
  }

  if (isObject(stat) && typeof stat.isDirectory === 'function') {
    return stat.isDirectory();
  }

  var segs = fp.split('/');
  var last = segs[segs.length - 1];
  if (last && last.indexOf('.') !== -1) {
    return false;
  }
  return true;
}

/**
 * Return true if `fp` looks like a file, or
 * actually is a file if fs.stat is used.
 */

function isFile(fp, stat) {
  if (stat === true) {
    stat = tryStats(fp);
  }
  return !isDir(fp, stat);
}
