var fs = require("fs");
var path = require("path");
var createHash = require("crypto").createHash;
var compile = require("../lib/compiler.js").compile;
var hasOwn = Object.prototype.hasOwnProperty;

// Map from absolute file paths to the package.json that governs them.
var pkgInfoCache = Object.create(null);

// Take only the major and minor components of the reify version, so that
// we don't invalidate the cache every time a patch version is released.
var reifyVersion = require("../package.json")
  .version.split(".", 2).join(".");

exports.compile = function (content, filename) {
  if (filename === "repl") {
    // Treat the REPL as if there was no filename.
    filename = null;
  }
  
  try {
    var info = filename ? getPkgInfo(filename) : fallbackPkgInfo;
    return info ? readWithCache(info, content) : content;
  } catch(e) {
    if (filename) {
      e.message += ' while processing file: ' + filename;
    }
    throw e;
  }

};

// Used when compile filename argument is falsy. Enables in-memory
// caching, at least.
var fallbackPkgInfo = {
  cache: Object.create(null)
};

function readWithCache(info, content) {
  var cacheFilename = getCacheFilename(content);
  var absCachePath = typeof info.cacheDir === "string" &&
    path.join(info.cacheDir, cacheFilename);

  if (hasOwn.call(info.cache, cacheFilename)) {
    var cacheValue = info.cache[cacheFilename];

    if (cacheValue === true && absCachePath) {
      cacheValue = info.cache[cacheFilename] =
        readFileOrNull(absCachePath);
    }

    if (typeof cacheValue === "string") {
      return cacheValue;
    }
  }

  var options = { ast: false };
  content = compile(content, options).code;
  if (options.identical) {
    // Don't bother caching result if compiler made no changes.
    return content;
  }

  info.cache[cacheFilename] = content;

  if (typeof info.cacheDir === "string") {
    // Writing cache files is something that should only happen at package
    // development time, so it's acceptable to use fs.writeFileSync
    // instead of some complicated asynchronous-but-atomic strategy.
    fs.writeFileSync(absCachePath, content, "utf8");
  }

  return content;
}

function readFileOrNull(filename) {
  try {
    return fs.readFileSync(filename, "utf8");
  } catch (e) {
    return null;
  }
}

function getCacheFilename(content) {
  return createHash("sha1")
    .update(reifyVersion)
    .update("\0")
    .update(content)
    .digest("hex") + ".json";
}

function getPkgInfo(filename) {
  if (hasOwn.call(pkgInfoCache, filename)) {
    return pkgInfoCache[filename];
  }

  var stat = statOrNull(filename);
  if (! stat) {
    return pkgInfoCache[filename] = null;
  }

  if (stat.isDirectory()) {
    if (path.basename(filename) === "node_modules") {
      return pkgInfoCache[filename] = null;
    }

    var info = readPkgInfo(filename);
    if (info) {
      return pkgInfoCache[filename] = info;
    }
  }

  var parentDir = path.dirname(filename);
  return pkgInfoCache[filename] =
    parentDir !== filename &&
    getPkgInfo(parentDir);
}

function statOrNull(filename) {
  try {
    return fs.statSync(filename);
  } catch (e) {
    return null;
  }
}

function readPkgInfo(dir) {
  try {
    var pkg = JSON.parse(fs.readFileSync(
      path.join(dir, "package.json")
    ));

  } catch (e) {
    if (! (e instanceof SyntaxError ||
           e.code === "ENOENT")) {
      throw e;
    }
  }

  if (pkg && typeof pkg === "object") {
    var reify = pkg.reify;
    if (reify === false) {
      // An explicit "reify": false property in package.json disables
      // reification even if "reify" is listed as a dependency.
      return null;
    }

    function check(name) {
      return typeof pkg[name] === "object" &&
        hasOwn.call(pkg[name], "reify");
    }

    if (! check("dependencies") &&
        ! check("peerDependencies") &&
        // Use case: a package.json file may have "reify" in its
        // "devDependencies" section because it expects another package or
        // application to enable reification in production, but needs its
        // own copy of the "reify" package during development. Disabling
        // reification in production when it was enabled in development
        // would be dangerous in this case.
        ! check("devDependencies")) {
      return null;
    }

    var info = {
      json: pkg,
      cacheDir: null,
      cache: Object.create(null)
    };

    if (reify) {
      var cacheDir = getOwn(reify, "cache-directory");

      if (typeof cacheDir === "string") {
        cacheDir = mkdirp(dir, cacheDir);

        var cacheFiles = cacheDir && fs.readdirSync(cacheDir);
        if (cacheFiles) {
          // If we leave info.cacheDir === null, we won't be able to save
          // cache files to disk, but we can still cache compilation
          // results in memory.
          info.cacheDir = cacheDir;

          cacheFiles.forEach(function (file) {
            // Later we'll change the value to the actual contents of the
            // file, but for now we merely register that it exists.
            if (/\.json$/.test(file)) {
              info.cache[file] = true;
            }
          });
        }
      }
    }

    return info;
  }

  return null;
}

function getOwn(obj, name) {
  return obj &&
    typeof obj === "object" &&
    hasOwn.call(obj, name) &&
    obj[name];
}

function mkdirp(rootDir, relativeDir) {
  var parentDir = path.dirname(relativeDir);
  if (parentDir === relativeDir) {
    return rootDir;
  }

  if (! mkdirp(rootDir, parentDir)) {
    return null;
  }

  var absoluteDir = path.join(rootDir, relativeDir);
  var stat = statOrNull(absoluteDir);
  if (stat && stat.isDirectory()) {
    return absoluteDir;
  }

  try {
    fs.mkdirSync(absoluteDir);
  } catch (e) {
    return null;
  }

  return absoluteDir;
}

@@ -692,21 +692,9 @@
   %7D%0A
-  %0A  try %7B%0A  
+%0A
   va
@@ -755,29 +755,76 @@
 Info;%0A  
-  return info
+if (! info) %7B%0A    return content;%0A  %7D%0A%0A  return filename%0A   
  ? readW
@@ -823,32 +823,43 @@
  ? readWithCache
+AndFilename
 (info, content) 
@@ -860,19 +860,53 @@
 tent
-) :
+, filename)%0A    : readWithCache(info,
  content
 ;%0A  
@@ -905,47 +905,141 @@
 tent
+)
 ;%0A
-  %7D catch(e) %7B%0A    if (filenam
+%7D;%0A%0Afunction readWithCacheAndFilename(info, content, filename) %7B%0A  try %7B%0A    return readWithCache(info, content);%0A  %7D catch (
 e) %7B%0A
-  
     
@@ -1090,22 +1090,16 @@
 lename;%0A
-    %7D%0A
     thro
@@ -1107,19 +1107,17 @@
  e;%0A  %7D%0A
-%0A%7D;
+%7D
 %0A%0A// Use

Move try-catch block to readWithCacheAndFilename.