(function() {
  var LessCache, Parser, basename, cacheVersion, crypto, dirname, extname, fs, join, lessFs, relative, walkdir, _, _ref,
    __slice = [].slice;

  crypto = require('crypto');

  _ref = require('path'), basename = _ref.basename, dirname = _ref.dirname, extname = _ref.extname, join = _ref.join, relative = _ref.relative;

  _ = require('underscore-plus');

  fs = require('fs-plus');

  lessFs = null;

  Parser = null;

  walkdir = require('walkdir').sync;

  cacheVersion = 1;

  module.exports = LessCache = (function() {
    function LessCache(_arg) {
      var _ref1;
      _ref1 = _arg != null ? _arg : {}, this.cacheDir = _ref1.cacheDir, this.importPaths = _ref1.importPaths, this.resourcePath = _ref1.resourcePath, this.fallbackDir = _ref1.fallbackDir, this.syncCaches = _ref1.syncCaches;
      this.importsCacheDir = this.cacheDirectoryForImports(this.importPaths);
      if (this.fallbackDir) {
        this.importsFallbackDir = join(this.fallbackDir, basename(this.importsCacheDir));
      }
      try {
        this.importedFiles = this.readJson(join(this.importsCacheDir, 'imports.json')).importedFiles;
      } catch (_error) {}
      this.setImportPaths(this.importPaths);
      this.stats = {
        hits: 0,
        misses: 0
      };
    }

    LessCache.prototype.cacheDirectoryForImports = function(importPaths) {
      if (importPaths == null) {
        importPaths = [];
      }
      if (this.resourcePath) {
        importPaths = importPaths.map((function(_this) {
          return function(importPath) {
            return _this.relativize(_this.resourcePath, importPath);
          };
        })(this));
      }
      return join(this.cacheDir, this.digestForContent(importPaths.join('\n')));
    };

    LessCache.prototype.getDirectory = function() {
      return this.cacheDir;
    };

    LessCache.prototype.getImportPaths = function() {
      return _.clone(this.importPaths);
    };

    LessCache.prototype.getImportedFiles = function(importPaths) {
      var error, importPath, importedFiles, _i, _len;
      importedFiles = [];
      for (_i = 0, _len = importPaths.length; _i < _len; _i++) {
        importPath = importPaths[_i];
        try {
          walkdir(importPath, {
            no_return: true
          }, (function(_this) {
            return function(filePath, stat) {
              if (!stat.isFile()) {
                return;
              }
              if (_this.resourcePath) {
                filePath = _this.relativize(_this.resourcePath, filePath);
              }
              return importedFiles.push(filePath);
            };
          })(this));
        } catch (_error) {
          error = _error;
          continue;
        }
      }
      return importedFiles;
    };

    LessCache.prototype.setImportPaths = function(importPaths) {
      var error, filesChanged, importedFiles, pathsChanged;
      if (importPaths == null) {
        importPaths = [];
      }
      importedFiles = this.getImportedFiles(importPaths);
      pathsChanged = !_.isEqual(this.importPaths, importPaths);
      filesChanged = !_.isEqual(this.importedFiles, importedFiles);
      if (pathsChanged) {
        this.importsCacheDir = this.cacheDirectoryForImports(importPaths);
        if (this.fallbackDir) {
          this.importsFallbackDir = join(this.fallbackDir, basename(this.importsCacheDir));
        }
      } else if (filesChanged) {
        try {
          fs.removeSync(this.importsCacheDir);
        } catch (_error) {
          error = _error;
          if ((error != null ? error.code : void 0) === 'ENOENT') {
            try {
              fs.removeSync(this.importsCacheDir);
            } catch (_error) {}
          }
        }
      }
      this.writeJson(join(this.importsCacheDir, 'imports.json'), {
        importedFiles: importedFiles
      });
      this.importedFiles = importedFiles;
      return this.importPaths = importPaths;
    };

    LessCache.prototype.observeImportedFilePaths = function(callback) {
      var importedPaths, originalFsReadFileSync;
      importedPaths = [];
      if (lessFs == null) {
        lessFs = require('less/lib/less/fs.js');
      }
      originalFsReadFileSync = lessFs.readFileSync;
      lessFs.readFileSync = (function(_this) {
        return function() {
          var args, content, filePath;
          filePath = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
          content = originalFsReadFileSync.apply(null, [filePath].concat(__slice.call(args)));
          if (_this.resourcePath) {
            filePath = _this.relativize(_this.resourcePath, filePath);
          }
          importedPaths.push({
            path: filePath,
            digest: _this.digestForContent(content)
          });
          return content;
        };
      })(this);
      try {
        callback();
      } finally {
        lessFs.readFileSync = originalFsReadFileSync;
      }
      return importedPaths;
    };

    LessCache.prototype.readJson = function(filePath) {
      return JSON.parse(fs.readFileSync(filePath));
    };

    LessCache.prototype.writeJson = function(filePath, object) {
      return fs.writeFileSync(filePath, JSON.stringify(object));
    };

    LessCache.prototype.digestForPath = function(filePath) {
      return this.digestForContent(fs.readFileSync(filePath));
    };

    LessCache.prototype.digestForContent = function(content) {
      return crypto.createHash('SHA1').update(content, 'utf8').digest('hex');
    };

    LessCache.prototype.relativize = function(from, to) {
      var relativePath;
      relativePath = relative(from, to);
      if (relativePath.indexOf('..') === 0) {
        return to;
      } else {
        return relativePath;
      }
    };

    LessCache.prototype.getCachePath = function(directory, filePath) {
      var cacheFile, directoryPath;
      cacheFile = "" + (basename(filePath, extname(filePath))) + ".json";
      directoryPath = dirname(filePath);
      if (this.resourcePath) {
        directoryPath = this.relativize(this.resourcePath, directoryPath);
      }
      if (directoryPath) {
        directoryPath = this.digestForContent(directoryPath);
      }
      return join(directory, 'content', directoryPath, cacheFile);
    };

    LessCache.prototype.getCachedCss = function(filePath, digest) {
      var cacheEntry, error, fallbackDirUsed, path, _i, _len, _ref1, _ref2;
      try {
        cacheEntry = this.readJson(this.getCachePath(this.importsCacheDir, filePath));
      } catch (_error) {
        error = _error;
        if (this.importsFallbackDir != null) {
          try {
            cacheEntry = this.readJson(this.getCachePath(this.importsFallbackDir, filePath));
            fallbackDirUsed = true;
          } catch (_error) {}
        }
      }
      if (digest !== (cacheEntry != null ? cacheEntry.digest : void 0)) {
        return;
      }
      _ref1 = cacheEntry.imports;
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        _ref2 = _ref1[_i], path = _ref2.path, digest = _ref2.digest;
        try {
          if (this.resourcePath && !fs.isAbsolute(path)) {
            path = join(this.resourcePath, path);
          }
          if (this.digestForPath(path) !== digest) {
            return;
          }
        } catch (_error) {
          error = _error;
          return;
        }
      }
      if (this.syncCaches) {
        if (fallbackDirUsed) {
          this.writeJson(this.getCachePath(this.importsCacheDir, filePath), cacheEntry);
        } else if (this.importsFallbackDir != null) {
          this.writeJson(this.getCachePath(this.importsFallbackDir, filePath), cacheEntry);
        }
      }
      return cacheEntry.css;
    };

    LessCache.prototype.putCachedCss = function(filePath, digest, css, imports) {
      var cacheEntry;
      cacheEntry = {
        digest: digest,
        css: css,
        imports: imports,
        version: cacheVersion
      };
      this.writeJson(this.getCachePath(this.importsCacheDir, filePath), cacheEntry);
      if (this.syncCaches && (this.importsFallbackDir != null)) {
        return this.writeJson(this.getCachePath(this.importsFallbackDir, filePath), cacheEntry);
      }
    };

    LessCache.prototype.parseLess = function(filePath, less) {
      var css, imports, options, parser;
      css = null;
      options = {
        filename: filePath,
        syncImport: true,
        paths: this.importPaths
      };
      if (Parser == null) {
        Parser = require('less').Parser;
      }
      parser = new Parser(options);
      imports = this.observeImportedFilePaths((function(_this) {
        return function() {
          return parser.parse(less, function(error, tree) {
            if (error != null) {
              throw error;
            } else {
              return css = tree.toCSS();
            }
          });
        };
      })(this));
      return {
        imports: imports,
        css: css
      };
    };

    LessCache.prototype.readFileSync = function(filePath) {
      return this.cssForFile(filePath, fs.readFileSync(filePath, 'utf8'));
    };

    LessCache.prototype.cssForFile = function(filePath, lessContent) {
      var css, digest, imports, _ref1;
      digest = this.digestForContent(lessContent);
      css = this.getCachedCss(filePath, digest);
      if (css != null) {
        this.stats.hits++;
        return css;
      }
      this.stats.misses++;
      _ref1 = this.parseLess(filePath, lessContent), imports = _ref1.imports, css = _ref1.css;
      this.putCachedCss(filePath, digest, css, imports);
      return css;
    };

    return LessCache;

  })();

}).call(this);
