// Function that provides map of rules found in ignorefiles for given ignorefile
// type. Additinally it invokes event on changes in map which are results of
// changes in ignorefiles.

'use strict';

var invoke    = require('es5-ext/lib/Function/invoke')
  , extend    = require('es5-ext/lib/Object/extend')
  , forEach   = require('es5-ext/lib/Object/for-each')
  , isCopy    = require('es5-ext/lib/Object/is-copy')
  , deferred  = require('deferred')
  , dirname   = require('path').dirname
  , sep       = require('../path/sep')
  , FindRoot  = require('./find-root').FindRoot
  , readFile  = require('./read-file').readFile

  , Map, readRules, readRulesWatch, paths, getMap;

paths = function (root, path2) {
	if (root === path2) {
		return [root];
	}
	return [root].concat(path2.slice(root.length + 1).split(sep)
		.map(function (path) {
			return root += sep + path;
		}));
};

readRules = function (path) {
	return this.readFile(path + sep + this.filename)(function (src) {
			return (src == null) ? src : this.parse(src, path);
		}.bind(this));
};
readRulesWatch = function (path) {
	var watcher, promise, current;
	watcher = this.readFile(path + sep + this.filename);
	promise = watcher(function (src) {
		return (current = ((src == null) ? src : this.parse(src, path)));
	}.bind(this));
	watcher.on('change', function (data) {
		data = ((data == null) ? data : this.parse(data));
		if (!isCopy(current, data)) {
			current = promise.value = data;
			promise.emit('change', current, path);
		}
	}.bind(this));
	promise.close = watcher.close;
	return promise;
};

Map = function (path, watch) {
	var def, root;
	this.path = path;
	this.watch = watch;
	this.data = { root: null, map: {} };
	extend(this, deferred());
	if (this.watch) {
		this.onRulesChange = this.onRulesChange.bind(this);
		this.rulePromises = {};
		this.promise.close = this.close.bind(this);
	}
};
Map.prototype = {
	init: function (findRoot) {
		this.findRoot = findRoot;
		if (this.watch) {
			findRoot.on('change', this.updateRoot.bind(this));
		}
		findRoot.end(function (root) {
			if (root) {
				this.data.root = root;
				this.addPaths(root, this.path).end(function () {
					this.resolve(this.data);
				}.bind(this), this.resolve);
			} else {
				this.resolve(this.data);
			}
		}.bind(this), this.resolve);
		return this.promise;
	},
	close: function () {
		this.findRoot.close();
		forEach(this.rulePromises, invoke('close'));
		delete this.rulePromises;
	},
	parse: String,
	readFile: function (path) {
		return readFile(path, { loose: true, watch: this.watch });
	},
	onRulesChange: function (rules, path) {
		if (rules == null) {
			delete this.data.map[path];
		} else {
			this.data.map[path] = rules;
		}
		this.promise.emit('change', this.data);
	},
	addPaths: function (root, path) {
		return deferred.map(paths(root, path), function (path) {
			var rules = this.readRules(path);
			if (this.watch) {
				this.rulePromises[path] = rules;
				rules.on('change', this.onRulesChange);
			}
			return rules.aside(function (rules) {
				if (rules != null) {
					this[path] = rules;
				}
			}.bind(this.data.map));
		}, this);
	},
	removePaths: function (root, path) {
		paths(root, path).forEach(function (path) {
			this.rulePromises[path].close();
			delete this.rulePromises[path];
			delete this.data.map[path];
		}, this);
	},
	updateRoot: function (root) {
		if (!root) {
			this.removePaths(this.data.root, this.path);
			this.data.root = null;
			this.promise.emit('change', this.data);
		} else if (!this.data.root) {
			this.data.root = root;
			this.addPaths(root, this.path)
				.end(this.promise.emit.bind(this.promise, 'change', this.data));
		} else if (this.data.root < root) {
			this.removePaths(this.data.root, dirname(root));
			this.data.root = root;
			this.promise.emit('change', this.data);
		} else {
			this.addPaths(root, dirname(this.data.root))
				.end(this.promise.emit.bind(this.promise, 'change', this.data));
			this.data.root = root;
		}
	}
};

getMap = module.exports = function (path, mode, watch, parse) {
	var map = new Map(path, watch), finder, isRoot;
	map.filename = mode.filename;
	map.readRules = watch ? readRulesWatch : readRules;
	if (parse) {
		map.parse = parse;
	}
	finder = new FindRoot(path, watch);
	finder.isRoot = watch ? mode.isRootWatch : mode.isRoot;
	finder.next();
	return map.init(finder.promise);
};
getMap.Map = Map;
getMap.readRules = readRules;
getMap.readRulesWatch = readRulesWatch;

@@ -1412,16 +1412,26 @@
 nt, data
+, Infinity
 )) %7B%0A%09%09%09

Use deep check for differencies