"use strict";

/**
 * Declare variables needed for this module
 */
var _ = require("./alloy/underscore");
var path = require("path");

if(!_.contains) {
	_.mixin({
		contains: _.includes
	});
}

/**
 * Registry for storing all the files, directories, and core modules to be used by the application.
 */
var registry = {
	files: [],
	directories: [],
	core: [],
	fallback: [],
};

var logger = console;

var ensureCore = function(key, value) {

	if(!_.find(registry.core, function(item) {
			return item.id === key;
		})) {
		registry.core.push({
			id: key,
			path: value
		});
	}
}

var resolver = function resolver(_registry, _logger) {

	if(_registry) {
		registry = _.defaults(_registry, {
			files: [],
			directories: [],
			core: [],
		});
	}

	_logger && (logger = _logger);

	ensureCore("path", "/path");
	ensureCore("alloy", "/alloy");
	ensureCore("resolver", "/resolver");

	_.forEach(registry.core, function(core) {
		core.path = _resolve(core.path, "/");
		logger.info("core.path: " + core.path);
	});

	_.forEach(registry.fallback, function(fallback) {
		fallback.path = _resolve(fallback.path, "/");
		logger.info("fallback.path: " + fallback.path);
	});

	Object.defineProperty(this, "registry", {
		get: function() {
			// logger.info("******************");
			//logger.info(registry);
			return _.clone(registry);
		},
		enumerable: true,
		configurable: false
	});

};

module.exports = resolver;

//      require(X) from module at path Y
//      1. If X is a core module,
//          a. return the core module
//          b. STOP
//      2. If X begins with './' or '/' or '../'
//          a. LOAD_AS_FILE(Y + X)
//          b. LOAD_AS_DIRECTORY(Y + X)
//      3. LOAD_NODE_MODULES(X, dirname(Y))
//      4. THROW "not found"

//  LOAD_AS_FILE(X)
//      1. If X is a file, load X as JavaScript text.  STOP
//      2. If X.js is a file, load X.js as JavaScript text.  STOP
//      3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
//      4. If X.node is a file, load X.node as binary addon.  STOP

//  LOAD_AS_DIRECTORY(X)
//      1. If X/package.json is a file,
//          a. Parse X/package.json, and look for "main" field.
//          b. let M = X + (json main field)
//          c. LOAD_AS_FILE(M)
//      2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
//      3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
//      4. If X/index.node is a file, load X/index.node as binary addon.  STOP

//  LOAD_NODE_MODULES(X, START)
//      1. let DIRS=NODE_MODULES_PATHS(START)
//      2. for each DIR in DIRS:
//           a. LOAD_AS_FILE(DIR/X)
//          b. LOAD_AS_DIRECTORY(DIR/X)

//  NODE_MODULES_PATHS(START)
//      1. let PARTS = path split(START)
//      2. let I = count of PARTS - 1
//      3. let DIRS = []
//      4. while I >= 0,
//          a. if PARTS[I] = "node_modules" CONTINUE
//          c. DIR = path join(PARTS[0 .. I] + "node_modules")
//          b. DIRS = DIRS + DIR
//          c. let I = I - 1
//      5. return DIRS

// Alloy doesn't like it when you include the file extension...
function convert_to_alloy_path(resolved_path) {
	var parsed_path = path.posix.parse(resolved_path);
	return path.posix.join(parsed_path.dir, parsed_path.name);
}

function _resolve(request, basepath) {
	basepath = basepath || path.posix.sep;

	var core_module = load_core_modules(request);
	if(core_module) {
		return convert_to_alloy_path(core_module);
	}

	var start = request.substring(0, 1);
	if(start === '.' || start === '/') {

		var file_module = load_as_file(request, basepath);
		if(file_module) {
			return convert_to_alloy_path(file_module);
		}
		var directory_module = load_as_directory(request, basepath);
		if(directory_module) {
			return convert_to_alloy_path(directory_module);
		}
	}

	var node_module = load_node_modules(request, basepath);
	if(node_module) {
		return convert_to_alloy_path(node_module);
	}

	var fallback_module = load_fallback_modules(request);
	if(fallback_module) {
		return convert_to_alloy_path(fallback_module);
	}

	// if module id can't be resolved we are using the passed in module id as-is in order to
	// maintain backwards compatibility with older version of Alloy.
	return request;
};

resolver.prototype.resolve = _.memoize(_resolve, function(request, basepath) {
	return request + "::" + basepath;
});

function load_core_modules(request) {
	var module_path = _.find(registry.core, function(item) {
		return item.id === request;
	});
	if(module_path) {
		return module_path.path;
	}
};

function load_fallback_modules(request) {
	var module_path = _.find(registry.fallback, function(item) {
		return item.id === request;
	});
	if(module_path) {
		return module_path.path;
	}
};

//  LOAD_AS_FILE(X)
//      1. If X is a file, load X as JavaScript text.  STOP
//      2. If X.js is a file, load X.js as JavaScript text.  STOP
//      3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
//      4. If X.node is a file, load X.node as binary addon.  STOP

function load_as_file(request, startpath) {

	var module_path;
	var resolved_path = path.posix.resolve(startpath, request);
	_.includes(registry.files, resolved_path) && (module_path = resolved_path);
	if(module_path) {
		// logger.trace("file found: " + module_path);
		return module_path;
	}

	var extension = path.extname(request);
	if(!extension) {
		var exts = [".js", ".json"];
		_.forEach(exts, function(ext) {
			resolved_path = path.posix.resolve(startpath, request + ext);
			_.includes(registry.files, resolved_path) && (module_path = resolved_path);
			if(!module_path) {
				return !module_path;
			}
		});
	}
	return module_path;
};

//  LOAD_AS_DIRECTORY(X)
//      1. If X/package.json is a file,
//          a. Parse X/package.json, and look for "main" field.
//          b. let M = X + (json main field)
//          c. LOAD_AS_FILE(M)
//      2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
//      3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
//      4. If X/index.node is a file, load X/index.node as binary addon.  STOP

function load_as_directory(request, startpath) {
	var resolved_path = path.posix.resolve(startpath, request);
	var module_path = _.find(registry.directories, function(item) {
		return item.id === resolved_path;
	});
	if(module_path) {
		return module_path.path;
	}
};

//  LOAD_NODE_MODULES(X, START)
//      1. let DIRS=NODE_MODULES_PATHS(START)
//      2. for each DIR in DIRS:
//           a. LOAD_AS_FILE(DIR/X)
//          b. LOAD_AS_DIRECTORY(DIR/X)

function load_node_modules(request, startpath) {
	var resolved_path;
	var nodepaths = node_modules_paths(startpath);

	_.forEach(nodepaths, function(nodepath) {
		resolved_path = load_as_file(request, nodepath);
		return !resolved_path;
	});

	if(resolved_path) {
		return resolved_path;
	}

	_.forEach(nodepaths, function(nodepath) {
		resolved_path = load_as_directory(request, nodepath);
		return !resolved_path;
	});

	return resolved_path;
};

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var splitRe = /\//;

function node_modules_paths(from) {
	// guarantee that 'from' is absolute.
	from = path.posix.resolve(from);

	// note: this approach *only* works when the path is guaranteed
	// to be absolute.  Doing a fully-edge-case-correct path.split
	// that works on both Windows and Posix is non-trivial.
	var paths = [];
	var parts = from.split(splitRe);

	for(var tip = parts.length - 1; tip >= 0; tip--) {
		// don't search in .../node_modules/node_modules
		if(parts[tip] === 'node_modules') continue;
		var dir = parts.slice(0, tip + 1).concat('node_modules').join(path.posix.sep);
		paths.push(dir);
	}

	return paths;
};


// These helpers are based on Mocha's utils:
// https://github.com/mochajs/mocha/blob/master/lib/utils.js
// Copyright (c) 2011-2015 TJ Holowaychuk <tj@vision-media.ca>
// MIT License: https://github.com/mochajs/mocha/blob/master/LICENSE

/**
 * Stringify `value`. Different behavior depending on type of value:
 *
 * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
 * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
 * - If `value` is an *empty* object, function, or array, return result of function
 *   {@link emptyRepresentation}.
 * - If `value` has properties, call {@link canonicalize} on it, then return result of JSON.stringify().
 */
module.exports = function stringify(value) {
  var type = getType(value);

  if (!~['object', 'array', 'function'].indexOf(type)) {
    if (type !== 'buffer') {
      return jsonStringify(value);
    }
    var json = value.toJSON();
    // Based on the toJSON result
    return jsonStringify(json.data && json.type ? json.data : json, 2)
      .replace(/,(\n|$)/g, '$1');
  }

  for (var prop in value) {
    if (Object.prototype.hasOwnProperty.call(value, prop)) {
      return jsonStringify(canonicalize(value), 2).replace(/,(\n|$)/g, '$1');
    }
  }

  return emptyRepresentation(value, type);
};

/**
 * Takes some variable and asks `Object.prototype.toString()` what it thinks it is.
 *
 * type({}) // 'object'
 * type([]) // 'array'
 * type(1) // 'number'
 * type(false) // 'boolean'
 * type(Infinity) // 'number'
 * type(null) // 'null'
 * type(new Date()) // 'date'
 * type(/foo/) // 'regexp'
 * type('type') // 'string'
 * type(global) // 'global'
 */
function getType(value) {
  if (value === undefined) {
    return 'undefined';
  } else if (value === null) {
    return 'null';
  } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
    return 'buffer';
  }
  return Object.prototype.toString.call(value)
    .replace(/^\[.+\s(.+?)\]$/, '$1')
    .toLowerCase();
}

/**
 * like JSON.stringify but more sense.
 */
function jsonStringify(object, spaces, depth) {
  if (typeof spaces === 'undefined') {
    // primitive types
    return _stringify(object);
  }

  depth = depth || 1;
  var space = spaces * depth;
  var str = Array.isArray(object) ? '[' : '{';
  var end = Array.isArray(object) ? ']' : '}';
  var length = object.length || Object.keys(object).length;
  // `.repeat()` polyfill
  function repeat(s, n) {
    return new Array(n).join(s);
  }

  function _stringify(val) {
    switch (getType(val)) {
    case 'null':
    case 'undefined':
      val = '[' + val + ']';
      break;
    case 'array':
    case 'object':
      val = jsonStringify(val, spaces, depth + 1);
      break;
    case 'boolean':
    case 'regexp':
    case 'number':
      val = val === 0 && 1 / val === -Infinity // `-0`
        ? '-0'
        : val.toString();
      break;
    case 'date':
      var sDate = isNaN(val.getTime())        // Invalid date
        ? val.toString()
        : val.toISOString();
      val = '[Date: ' + sDate + ']';
      break;
    case 'buffer':
      var json = val.toJSON();
      // Based on the toJSON result
      json = json.data && json.type ? json.data : json;
      val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
      break;
    default:
      val = (val === '[Function]' || val === '[Circular]')
        ? val
        : JSON.stringify(val); // string
    }
    return val;
  }

  for (var i in object) {
    if (!object.hasOwnProperty(i)) {
      continue; // not my business
    }
    --length;
    str += '\n ' + repeat(' ', space)
      + (Array.isArray(object) ? '' : '"' + i + '": ') // key
      + _stringify(object[i])                     // value
      + (length ? ',' : '');                     // comma
  }

  return str
    // [], {}
    + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end);
}

/**
 * Return a new Thing that has the keys in sorted order. Recursive.
 *
 * If the Thing...
 * - has already been seen, return string `'[Circular]'`
 * - is `undefined`, return string `'[undefined]'`
 * - is `null`, return value `null`
 * - is some other primitive, return the value
 * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
 * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
 * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
 */
function canonicalize(value, stack) {
  var canonicalizedObj;
  /* eslint-disable no-unused-vars */
  var prop;
  /* eslint-enable no-unused-vars */
  var type = getType(value);
  function withStack(value, fn) {
    stack.push(value);
    fn();
    stack.pop();
  }

  stack = stack || [];

  if (stack.indexOf(value) !== -1) {
    return '[Circular]';
  }

  switch (type) {
    case 'undefined':
    case 'buffer':
    case 'null':
      canonicalizedObj = value;
      break;
    case 'array':
      withStack(value, function() {
        canonicalizedObj = value.map(function(item) {
          return canonicalize(item, stack);
        });
      });
      break;
    case 'function':
      /* eslint-disable guard-for-in */
      for (prop in value) {
        canonicalizedObj = {};
        break;
      }
      /* eslint-enable guard-for-in */
      if (!canonicalizedObj) {
        canonicalizedObj = emptyRepresentation(value, type);
        break;
      }
    /* falls through */
    case 'object':
      canonicalizedObj = canonicalizedObj || {};
      withStack(value, function() {
        Object.keys(value).sort().forEach(function(key) {
          canonicalizedObj[key] = canonicalize(value[key], stack);
        });
      });
      break;
    case 'date':
    case 'number':
    case 'regexp':
    case 'boolean':
      canonicalizedObj = value;
      break;
    default:
      canonicalizedObj = value.toString();
  }

  return canonicalizedObj;
};

/**
 * If a value could have properties, and has none, this function is called,
 * which returns a string representation of the empty value.
 *
 * Functions w/ no properties return `'[Function]'`
 * Arrays w/ length === 0 return `'[]'`
 * Objects w/ no properties return `'{}'`
 * All else: return result of `value.toString()`
 */
function emptyRepresentation(value, type) {
  type = type || getType(value);

  switch (type) {
    case 'function':
      return '[Function]';
    case 'object':
      return '{}';
    case 'array':
      return '[]';
    default:
      return value.toString();
  }
}


"use strict";

var dynode = require("dynode");
var addDynamoTypeAnnotations = require("dynode/lib/dynode/types").stringify;
var Q = require("q");
var _ = require("underscore");

// See http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide/API_BatchWriteItem.html
var BATCH_MAX_SIZE = 25;

function noop() {}              // Used to swallow returned metadata from dynode
function unary(x) { return x; } // Used with `spread` to transform (data, metadata) pairs from dynode into just data

function createDynodeOptions(dynamoAsPromisedOptions, key, extraDynodeOptions) {
    var dynodeOptions = _.clone(extraDynodeOptions || {});

    // If given an `onlyIfExists` option, assemble the `Expected` Dynode option value by looking at the key and values.
    // Example:
    //     var key = { hash: "H", range: 5 };
    //     var dapOptions = { onlyIfExists: { hash: "h", range: "r" } };
    //     createDynodeOptions(dapOptions, key) === {
    //         Expected: { h: { Value: { S: "H" }, r: { Value: { N: 5 } } }
    //     };
    if (typeof dynamoAsPromisedOptions === "object" && dynamoAsPromisedOptions.onlyIfExists) {
        dynodeOptions.Expected = {};

        var keyValues = typeof key === "string" ? { hash: key } : key;
        var keysThatMustExist = typeof dynamoAsPromisedOptions.onlyIfExists === "string" ?
                                    { hash: dynamoAsPromisedOptions.onlyIfExists } :
                                    dynamoAsPromisedOptions.onlyIfExists;
        Object.keys(keysThatMustExist).forEach(function (keyType) {
            var keyName = keysThatMustExist[keyType];
            var beforeTypeAnnotations = {};
            beforeTypeAnnotations[keyName] = keyValues[keyType];
            var withTypeAnnotations = addDynamoTypeAnnotations(beforeTypeAnnotations);
            dynodeOptions.Expected[keyName] = { Value: withTypeAnnotations[keyName] };
        });
    }

    return dynodeOptions;
}

exports.Client = function (options) {
    var that = this;

    var dynodeClient = new dynode.Client(options);

    var dynodeClientGetItem = Q.nbind(dynodeClient.getItem, dynodeClient);
    var dynodeClientPutItem = Q.nbind(dynodeClient.putItem, dynodeClient);
    var dynodeClientDeleteItem = Q.nbind(dynodeClient.deleteItem, dynodeClient);
    var dynodeClientUpdateItem = Q.nbind(dynodeClient.updateItem, dynodeClient);
    var dynodeClientScan = Q.nbind(dynodeClient.scan, dynodeClient);
    var dynodeClientQuery = Q.nbind(dynodeClient.query, dynodeClient);
    var dynodeClientBatchWriteItem = Q.nbind(dynodeClient.batchWriteItem, dynodeClient);

    that.get = function (table, key) {
        return dynodeClientGetItem(table, key).spread(unary);
    };

    that.put = function (table, values) {
        return dynodeClientPutItem(table, values).then(noop);
    };

    that.delete = function (table, key) {
        return dynodeClientDeleteItem(table, key).then(noop);
    };

    that.update = function (table, key, values, options) {
        var dynodeOptions = createDynodeOptions(options, key);

        return dynodeClientUpdateItem(table, key, values, dynodeOptions).then(noop);
    };

    that.updateAndGet = function (table, key, values, options) {
        var dynodeOptions = createDynodeOptions(options, key, { ReturnValues: "ALL_NEW" });

        return dynodeClientUpdateItem(table, key, values, dynodeOptions).get("Attributes");
    };

    that.query = function (table, hash) {
        return dynodeClientQuery(table, hash).get("Items");
    };

    that.scan = function (table, scanOptions) {
        return dynodeClientScan(table, scanOptions || {}).spread(unary);
    };

    that.deleteMultiple = function (table, keys) {
        var batches = [];
        for (var start = 0; start < keys.length; start += BATCH_MAX_SIZE) {
            batches.push(keys.slice(start, start + BATCH_MAX_SIZE));
        }

        var deletePromises = batches.map(function (batch) {
            var writes = {};
            writes[table] = batch.map(function (key) { return { del: key }; });

            return dynodeClientBatchWriteItem(writes);
        });

        return Q.allResolved(deletePromises).then(function (resultPromises) {
            var rejectedPromises = resultPromises.filter(function (promise) { return promise.isRejected(); });

            if (rejectedPromises.length > 0) {
                var error = new Error(rejectedPromises.length + "/" + deletePromises.length +
                                      " of the delete batches failed!");
                error.errors = rejectedPromises.map(function (promise) { return promise.valueOf().exception; });
                throw error;
            }
        });
    };

    var methodNames = Object.keys(that);

    that.table = function (table) {
        var tableObj = {};
        methodNames.forEach(function (methodName) {
            tableObj[methodName] = function () {
                return that[methodName].apply(that, [table].concat(Array.prototype.slice.call(arguments)));
            };
        });

        return tableObj;
    };
};


/**
 * @license
 * Copyright (c) 2016 Craig Monro (kodespace.com)
 * No part of this file may be copied or retransmitted without adhering to the GPL license.
 * This copyright notice may not be removed, without express permission of the copyright holder.
 *
 * This file is part of pl-copyfind, a Javascript fork of WCopyFind/CopyFind.
 *
 * pl-copyfind is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * pl-copyfind is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with pl-copyfind.  If not, see <http://www.gnu.org/licenses/> or
 * <https://github.com/cmroanirgo/pl-copyfind/blob/master/LICENSE.md/>
 */

"use strict";

var _ = require("./lib/not_");
var wordlib = require("./lib/words");
var TextReader = require("./lib/textreader");

;(function() {
	_.escape = _.htmlEncode;

	var root           = this;
	var previous_exports = root.copyfind;

	/*
	USAGE:
		textL: txt (the original), or an array of texts
		textR: txt (the 'other'), or an array
		options: { } // see 'defaultOptions' below
		callback: function(err, data) {
			if (err) throw "An error occurred: " + err.message;
			// where data contains:
			data.hashesL: {} cacheable object data for re-use. put this in options to re-use
			data.hashesR: {}  ''
			// matches is a 2 dimensioned array
			data.matches[0][0]: [  // first index is array of 'left texts', 2nd index is 'right texts'
					{
						textL { pos: 0..n, length: 0..n, wordCount: 0..n }
						textR { pos: 0..n, length: 0..n, wordCount: 0..n }
					}
			]
			//... if however, the source textL and textR are NOT arrays, then data.matches is reduced accordingly:
			data.matches: [
					{
						textL { pos: 0..n, length: 0..n, wordCount: 0..n }
						textR { pos: 0..n, length: 0..n, wordCount: 0..n }
					}
			]
			data.html:
				// this is available when options.bBuildReport = true
				// contains html "inner" text. You need to add styling and body tags. eg:
					<!DOCTYPE html>
					<html><head><meta charset="UTF-8"><style>html,body { height:100%;}
					.doc { display:inline-block;width:45%; overflow:scroll;height:90%;max-height: 100%; border:1px solid #888; margin:1em 1%;padding:1em;}
					.match { color:#ff0000 } .match-partial { color:#007F00 }
					</style></head><body>

			data.executionTime: // in milliseconds

		}

		)
	*/

	var defaultOptions = {
			PhraseLength: 6, // Shortest Phrase to Match
			WordThreshold: 100, // Fewest Matches to Report
			SkipLength: 20, // if bSkipLongWords, this number used
			MismatchTolerance: 2, // #Most Imperfections to Allow
			MismatchPercentage: 80, // Minimum % of Matching Words

			bIgnoreCase: false, // Ignore Letter Case
			bIgnoreNumbers: false, // Ignore Numbers
			bIgnoreOuterPunctuation: false, // Ignore Outer Punctuation
			bIgnorePunctuation: false, // Ignore Punctuation
			bSkipLongWords: false, // Skip Long Words
			bSkipNonwords: false, // Skip Non-Words

			bBuildReport: true,
			bBriefReport: true
			// , bTerseReport: false // show ONLY the matching text
	};

	function _error(callback, message, returnCode) {
		var err = { message: message };
		callback(err);
		//return _.isUndefined(returnCode) ? 100 : returnCode;
		return false;
	}

	function _buildHashes(text, options, callback) {

		var indoc = new TextReader(text);

		var QWordHash = [];
		var WordPos = [];  // positions of the start of each word
		while( !indoc.isFinished() )				// loop until an eof
		{
			var pos = indoc.getPos();
			var word = indoc.getWord();				// get the next word
			if(options.bIgnorePunctuation) word = wordlib.removePunct(word);	// if ignore punctuation is active, remove punctuation
			if(options.bIgnoreOuterPunctuation) word = wordlib.outerPunct(word);	// if ignore outer punctuation is active, remove outer punctuation
			if(options.bIgnoreNumbers) word = wordlib.removeNumbers(word);			// if ignore numbers is active, remove numbers
			if(options.bIgnoreCase) word = word.toLowerCase(word);				// if ignore case is active, remove case
			if(options.bSkipLongWords && (word.length > options.SkipLength) ) continue;	// if skip too-long words is active, skip them
			if(options.bSkipNonwords && !(wordlib.isWord(word)) ) continue;		// if skip nonwords is active, skip them

			WordPos.push(pos);
			QWordHash.push(wordlib.hash(word));					// hash-code the word and save that hash
		}

		var Words = QWordHash.length;
		var data = {};
		data.WordsTotal = Words;							// save number of words in document entry
		data.WordHash =	QWordHash;		// allocate array for hash-coded words in doc entry
		data.WordPos = WordPos;
		data.SortedWords = [];
		data.DocLength = text.length;

		for (var i=0;i<Words;i++)			// loop for all the words in the document
		{
			data.SortedWords.push({ number:i, hash: QWordHash[i]});	// copy over hash-coded words
		}

		data.SortedWords.sort(function(a,b) {
			return a.hash - b.hash;
		});

		if(options.PhraseLength == 1)
			data.FirstHash = 0;		// if phraselength is 1 word, compare even the shortest words
		else														// if phrase length is > 1 word, start at first word with more than 3 chars
		{
			var FirstLong=0;
			for (var i=0;i<Words;i++)									// loop for all the words in the document
			{
				if( (data.SortedWords[i].hash & 0xFFC00000) != 0 )	// if the word is longer than 3 letters, break
					{
						FirstLong=i;
						break;
					}
			}
			data.FirstHash = FirstLong;					// save the number of the first >3 letter word, or the first word
		}

		return data;
	}

var WORD_UNMATCHED 	= -1;
var WORD_PERFECT 	= 0;
var WORD_FLAW 		= 1;
var WORD_FILTERED 	= 2;

	function _comparePair(docL, docR, options, data)
	{
		var WordNumberL,WordNumberR;						// word number for left document and right document
		var WordNumberRedundantL,WordNumberRedundantR;		// word number of end of redundant words
		var iWordNumberL,iWordNumberR;						// word number counter, for loops
		var FirstL,FirstR;									// first matching word in left document and right document
		var LastL,LastR;									// last matching word in left document and right document
		var FirstLp,FirstRp;								// first perfectly matching word in left document and right document
		var LastLp,LastRp;									// last perfectlymatching word in left document and right document
		var FirstLx,FirstRx;								// first original perfectly matching word in left document and right document
		var LastLx,LastRx;									// last original perfectlymatching word in left document and right document
		var Flaws;											// flaw count
		var Hash;
		var MatchingWordsPerfect;							// count of perfect matches within a single phrase
		var Anchor;											// number of current match anchor
		var i;

		data.i = { // todo. change how this works (ie use ranges instead)
			MatchMarkL: [],
			MatchMarkR: [],
			MatchAnchorL: [],
			MatchAnchorR: [],
			MatchMarkTempL: [],
			MatchMarkTempR: [],
			MatchingDocumentPairs: 0,
			MatchingWordsPerfect: 0,
			MatchingWordsTotalL: 0,
			MatchingWordsTotalR: 0,
		};
		var matches = [];

		function PercentMatching(FirstL,FirstR,LastL,LastR,PerfectMatchingWords)
		{
			return (200*PerfectMatchingWords)/(LastL-FirstL+LastR-FirstR+2);
		}

		for(WordNumberL=0;WordNumberL<docL.WordsTotal;WordNumberL++)	// loop for all left words
		{
			data.i.MatchMarkL[WordNumberL]=WORD_UNMATCHED;		// set the left match markers to "WORD_UNMATCHED"
			data.i.MatchAnchorL[WordNumberL]=0;					// zero the left match anchors
		}
		for(WordNumberR=0;WordNumberR<docR.WordsTotal;WordNumberR++)	// loop for all right words
		{
			data.i.MatchMarkR[WordNumberR]=WORD_UNMATCHED;		// set the right match markers to "WORD_UNMATCHED"
			data.i.MatchAnchorR[WordNumberR]=0;					// zero the right match anchors
		}

		WordNumberL=docL.FirstHash;						// start left at first >3 letter word
		WordNumberR=docR.FirstHash;						// start right at first >3 letter word

		Anchor=0;											// start with no html anchors assigned

		while ( (WordNumberL < docL.WordsTotal)			// loop while there are still words to check
				&& (WordNumberR < docR.WordsTotal) )
		{
			// if the next word in the left sorted hash-coded list has been matched
			if( data.i.MatchMarkL[docL.SortedWords[WordNumberL].number] != WORD_UNMATCHED )
			{
				WordNumberL++;								// advance to next left sorted hash-coded word
				continue;
			}

			// if the next word in the right sorted hash-coded list has been matched
			if( data.i.MatchMarkR[docR.SortedWords[WordNumberR].number] != WORD_UNMATCHED )
			{
				WordNumberR++;								// skip to next right sorted hash-coded word
				continue;
			}

			// check for left word less than right word
			if( docL.SortedWords[WordNumberL].hash < docR.SortedWords[WordNumberR].hash )
			{
				WordNumberL++;								// advance to next left word
				if ( WordNumberL >= docL.WordsTotal) break;
				continue;									// and resume looping
			}

			// check for right word less than left word
			if( docL.SortedWords[WordNumberL].hash > docR.SortedWords[WordNumberR].hash )
			{
				WordNumberR++;								// advance to next right word
				if ( WordNumberR >= docR.WordsTotal) break;
				continue;									// and resume looping
			}

			// we have a match, so check redundancy of this words and compare all copies of this word
			Hash=docL.SortedWords[WordNumberL].hash;
			WordNumberRedundantL=WordNumberL;
			WordNumberRedundantR=WordNumberR;
			while(WordNumberRedundantL < (docL.WordsTotal - 1))
			{
				if( docL.SortedWords[WordNumberRedundantL + 1].hash == Hash ) WordNumberRedundantL++;
				else break;
			}
			while(WordNumberRedundantR < (docR.WordsTotal - 1))
			{
				if( docR.SortedWords[WordNumberRedundantR + 1].hash == Hash ) WordNumberRedundantR++;
				else break;
			}
			for(iWordNumberL=WordNumberL;iWordNumberL<=WordNumberRedundantL;iWordNumberL++)	// loop for each copy of this word on the left
			{
				if( data.i.MatchMarkL[docL.SortedWords[iWordNumberL].number] != WORD_UNMATCHED ) continue;	// skip words that have been matched already
				for(iWordNumberR=WordNumberR;iWordNumberR<=WordNumberRedundantR;iWordNumberR++)	// loop for each copy of this word on the right
				{
					if( data.i.MatchMarkR[docR.SortedWords[iWordNumberR].number] != WORD_UNMATCHED ) continue;	// skip words that have been matched already

					// look up and down the hash-coded (not sorted) lists for matches
					data.i.MatchMarkTempL[docL.SortedWords[iWordNumberL].number]=WORD_PERFECT;	// markup word in temporary list at perfection quality
					data.i.MatchMarkTempR[docR.SortedWords[iWordNumberR].number]=WORD_PERFECT;	// markup word in temporary list at perfection quality

					FirstL=docL.SortedWords[iWordNumberL].number-1;	// start left just before current word
					LastL=docL.SortedWords[iWordNumberL].number+1;	// end left just after current word
					FirstR=docR.SortedWords[iWordNumberR].number-1;	// start right just before current word
					LastR=docR.SortedWords[iWordNumberR].number+1;	// end right just after current word

					while( (FirstL >= 0) && (FirstR >= 0) )		// if we aren't at the start of either document,
					{

						// Note: when we leave this loop, FirstL and FirstR will always point one word before the first match

						// make sure that left and right words haven't been used in a match before and
						// that the two words actually match. If so, move up another word and repeat the test.

						if( data.i.MatchMarkL[FirstL] != WORD_UNMATCHED ) break;
						if( data.i.MatchMarkR[FirstR] != WORD_UNMATCHED ) break;

						if( docL.WordHash[FirstL] == docR.WordHash[FirstR] )
						{
							data.i.MatchMarkTempL[FirstL]=WORD_PERFECT;		// markup word in temporary list
							data.i.MatchMarkTempR[FirstR]=WORD_PERFECT;		// markup word in temporary list
							FirstL--;									// move up on left
							FirstR--;									// move up on right
							continue;
						}
						break;
					}

					while( (LastL < docL.WordsTotal) && (LastR < docR.WordsTotal) ) // if we aren't at the end of either document
					{

						// Note: when we leave this loop, LastL and LastR will always point one word after last match

						// make sure that left and right words haven't been used in a match before and
						// that the two words actually match. If so, move up another word and repeat the test.

						if( data.i.MatchMarkL[LastL] != WORD_UNMATCHED ) break;
						if( data.i.MatchMarkR[LastR] != WORD_UNMATCHED ) break;
						if( docL.WordHash[LastL] == docR.WordHash[LastR] )
						{
							data.i.MatchMarkTempL[LastL]=WORD_PERFECT;	// markup word in temporary list
							data.i.MatchMarkTempR[LastR]=WORD_PERFECT;	// markup word in temporary list
							LastL++;								// move down on left
							LastR++;								// move down on right
							continue;
						}
						break;
					}

					FirstLp=FirstL+1;						// pointer to first perfect match left
					FirstRp=FirstR+1;						// pointer to first perfect match right
					LastLp=LastL-1;							// pointer to last perfect match left
					LastRp=LastR-1;							// pointer to last perfect match right
					MatchingWordsPerfect=LastLp-FirstLp+1;	// save number of perfect matches

					if(options.MismatchTolerance > 0)				// are we accepting imperfect matches?
					{

						FirstLx=FirstLp;					// save pointer to word before first perfect match left
						FirstRx=FirstRp;					// save pointer to word before first perfect match right
						LastLx=LastLp;						// save pointer to word after last perfect match left
						LastRx=LastRp;						// save pointer to word after last perfect match right

						Flaws=0;							// start with zero flaws
						while( (FirstL >= 0) && (FirstR >= 0) )		// if we aren't at the start of either document,
						{

							// Note: when we leave this loop, FirstL and FirstR will always point one word before the first reportable match

							// make sure that left and right words haven't been used in a match before and
							// that the two words actually match. If so, move up another word and repeat the test.
							if( data.i.MatchMarkL[FirstL] != WORD_UNMATCHED ) break;
							if( data.i.MatchMarkR[FirstR] != WORD_UNMATCHED ) break;
							if( docL.WordHash[FirstL] == docR.WordHash[FirstR] )
							{
								MatchingWordsPerfect++;				// increment perfect match count;
								Flaws=0;							// having just found a perfect match, we're back to perfect matching
								data.i.MatchMarkTempL[FirstL]=WORD_PERFECT;			// markup word in temporary list
								data.i.MatchMarkTempR[FirstR]=WORD_PERFECT;			// markup word in temporary list
								FirstLp=FirstL;						// save pointer to first left perfect match
								FirstRp=FirstR;						// save pointer to first right perfect match
								FirstL--;							// move up on left
								FirstR--;							// move up on right
								continue;
							}

							// we're at a flaw, so increase the flaw count
							Flaws++;
							if( Flaws > options.MismatchTolerance ) break;	// check for maximum flaws reached

							if( (FirstL-1) >= 0 )					// check one word earlier on left (if it exists)
							{
								if( data.i.MatchMarkL[FirstL-1] != WORD_UNMATCHED ) break;	// make sure we haven't already matched this word

								if( docL.WordHash[FirstL-1] == docR.WordHash[FirstR] )
								{
									if( PercentMatching(FirstL-1,FirstR,LastLx,LastRx,MatchingWordsPerfect+1) < options.MismatchPercentage ) break;	// are we getting too imperfect?
									data.i.MatchMarkTempL[FirstL]=WORD_FLAW;	// markup non-matching word in left temporary list
									FirstL--;						// move up on left to skip over the flaw
									MatchingWordsPerfect++;			// increment perfect match count;
									Flaws=0;						// having just found a perfect match, we're back to perfect matching
									data.i.MatchMarkTempL[FirstL]=WORD_PERFECT;		// markup word in left temporary list
									data.i.MatchMarkTempR[FirstR]=WORD_PERFECT;		// markup word in right temporary list
									FirstLp=FirstL;					// save pointer to first left perfect match
									FirstRp=FirstR;					// save pointer to first right perfect match
									FirstL--;						// move up on left
									FirstR--;						// move up on right
									continue;
								}
							}

							if( (FirstR-1) >= 0 )					// check one word earlier on right (if it exists)
							{
								if( data.i.MatchMarkR[FirstR-1] != WORD_UNMATCHED ) break;	// make sure we haven't already matched this word

								if( docL.WordHash[FirstL] == docR.WordHash[FirstR-1] )
								{
									if( PercentMatching(FirstL,FirstR-1,LastLx,LastRx,MatchingWordsPerfect+1) < options.MismatchPercentage ) break;	// are we getting too imperfect?
									data.i.MatchMarkTempR[FirstR]=WORD_FLAW;	// markup non-matching word in right temporary list
									FirstR--;						// move up on right to skip over the flaw
									MatchingWordsPerfect++;			// increment perfect match count;
									Flaws=0;						// having just found a perfect match, we're back to perfect matching
									data.i.MatchMarkTempL[FirstL]=WORD_PERFECT;		// markup word in left temporary list
									data.i.MatchMarkTempR[FirstR]=WORD_PERFECT;		// markup word in right temporary list
									FirstLp=FirstL;					// save pointer to first left perfect match
									FirstRp=FirstR;					// save pointer to first right perfect match
									FirstL--;						// move up on left
									FirstR--;						// move up on right
									continue;
								}
							}

							if( PercentMatching(FirstL-1,FirstR-1,LastLx,LastRx,MatchingWordsPerfect) < options.MismatchPercentage ) break;	// are we getting too imperfect?
							data.i.MatchMarkTempL[FirstL]=WORD_FLAW;		// markup word in left temporary list
							data.i.MatchMarkTempR[FirstR]=WORD_FLAW;		// markup word in right temporary list
							FirstL--;								// move up on left
							FirstR--;								// move up on right
						}

						Flaws=0;							// start with zero flaws
						while( (LastL < docL.WordsTotal) && (LastR < docR.WordsTotal) ) // if we aren't at the end of either document
						{

							// Note: when we leave this loop, LastL and LastR will always point one word after last match

							// make sure that left and right words haven't been used in a match before and
							// that the two words actually match. If so, move up another word and repeat the test.
							if( data.i.MatchMarkL[LastL] != WORD_UNMATCHED ) break;
							if( data.i.MatchMarkR[LastR] != WORD_UNMATCHED ) break;
							if( docL.WordHash[LastL] == docR.WordHash[LastR] )
							{
								MatchingWordsPerfect++;				// increment perfect match count;
								Flaws=0;							// having just found a perfect match, we're back to perfect matching
								data.i.MatchMarkTempL[LastL]=WORD_PERFECT;	// markup word in temporary list
								data.i.MatchMarkTempR[LastR]=WORD_PERFECT;	// markup word in temporary list
								LastLp=LastL;						// save pointer to last left perfect match
								LastRp=LastR;						// save pointer to last right perfect match
								LastL++;							// move down on left
								LastR++;							// move down on right
								continue;
							}

							Flaws++;
							if( Flaws == options.MismatchTolerance ) break;	// check for maximum flaws reached

							if( (LastL+1) < docL.WordsTotal )		// check one word later on left (if it exists)
							{
								if( data.i.MatchMarkL[LastL+1] != WORD_UNMATCHED ) break;	// make sure we haven't already matched this word

								if( docL.WordHash[LastL+1] == docR.WordHash[LastR] )
								{
									if( PercentMatching(FirstLx,FirstRx,LastL+1,LastR,MatchingWordsPerfect+1) < options.MismatchPercentage ) break;	// are we getting too imperfect?
									data.i.MatchMarkTempL[LastL]=WORD_FLAW;		// markup non-matching word in left temporary list
									LastL++;						// move down on left to skip over the flaw
									MatchingWordsPerfect++;			// increment perfect match count;
									Flaws=0;						// having just found a perfect match, we're back to perfect matching
									data.i.MatchMarkTempL[LastL]=WORD_PERFECT;	// markup word in lefttemporary list
									data.i.MatchMarkTempR[LastR]=WORD_PERFECT;	// markup word in right temporary list
									LastLp=LastL;					// save pointer to last left perfect match
									LastRp=LastR;					// save pointer to last right perfect match
									LastL++;						// move down on left
									LastR++;						// move down on right
									continue;
								}
							}

							if( (LastR+1) < docR.WordsTotal )	// check one word later on right (if it exists)
							{
								if( data.i.MatchMarkR[LastR+1] != WORD_UNMATCHED ) break;	// make sure we haven't already matched this word

								if( docL.WordHash[LastL] == docR.WordHash[LastR+1] )
								{
									if( PercentMatching(FirstLx,FirstRx,LastL,LastR+1,MatchingWordsPerfect+1) < options.MismatchPercentage ) break;	// are we getting too imperfect?
									data.i.MatchMarkTempR[LastR]=WORD_FLAW;		// markup non-matching word in right temporary list
									LastR++;						// move down on right to skip over the flaw
									MatchingWordsPerfect++;			// increment perfect match count;
									Flaws=0;						// having just found a perfect match, we're back to perfect matching
									data.i.MatchMarkTempL[LastL]=WORD_PERFECT;	// markup word in left temporary list
									data.i.MatchMarkTempR[LastR]=WORD_PERFECT;	// markup word in right temporary list
									LastLp=LastL;					// save pointer to last left perfect match
									LastRp=LastR;					// save pointer to last right perfect match
									LastL++;						// move down on left
									LastR++;						// move down on right
									continue;
								}
							}

							if( PercentMatching(FirstLx,FirstRx,LastL+1,LastR+1,MatchingWordsPerfect) < options.MismatchPercentage ) break;	// are we getting too imperfect?
							data.i.MatchMarkTempL[LastL]=WORD_FLAW;		// markup word in left temporary list
							data.i.MatchMarkTempR[LastR]=WORD_FLAW;		// markup word in right temporary list
							LastL++;								// move down on left
							LastR++;								// move down on right
						}
					}
					if( MatchingWordsPerfect >= options.PhraseLength )	// check that phrase has enough perfect matches in it to mark
					{
						Anchor++;									// increment anchor count

						var getTextLen = function(doc, first, last) {
							var f = doc.WordPos[first];
							var l;
							if (last==doc.WordPos.length-1) //
								l = doc.DocLength;
							else
								l = doc.WordPos[last+1];
							return l-f;
						};

						var imperfectPosL = [];
						var imperfectPosR = [];

						for(i=FirstLp;i<=LastLp;i++)				// loop for all left matched words
						{
							data.i.MatchMarkL[i]=data.i.MatchMarkTempL[i];	// copy over left matching markup
							if(data.i.MatchMarkTempL[i]==WORD_PERFECT) data.i.MatchingWordsPerfect++;	// count the number of perfect matching words (same as for right document)
							else imperfectPosL.push({pos:docL.WordPos[i],
									length: getTextLen(docL,i,i)});
							data.i.MatchAnchorL[i]=Anchor;				// identify the anchor for this phrase
						}
						data.i.MatchingWordsTotalL += LastLp-FirstLp+1;	// add the number of words in the matching phrase, whether perfect or flawed matches
						for(i=FirstRp;i<=LastRp;i++)				// loop for all right matched words
						{
							data.i.MatchMarkR[i]=data.i.MatchMarkTempR[i];	// copy over right matching markup
							data.i.MatchAnchorR[i]=Anchor;				// identify the anchor for this phrase
							if(data.i.MatchMarkTempR[i]!=WORD_PERFECT)
								imperfectPosR.push({pos:docR.WordPos[i],
									length: getTextLen(docR,i,i)});
						}
						data.i.MatchingWordsTotalR += LastRp-FirstRp+1;	// add the number of words in the matching phrase, whether perfect or flawed matches

						var match = {
								textL:{
										pos:docL.WordPos[FirstLp],
										length:getTextLen(docL, FirstLp, LastLp),
										wordCount: LastLp-FirstLp+1,
										skipped:imperfectPosL },
								textR:{
										pos:docR.WordPos[FirstRp],
										length:getTextLen(docR, FirstRp, LastRp),
										wordCount: LastRp-FirstRp+1,
										skipped:imperfectPosR }
							};
						matches.push(match);
					}
				}
			}
			WordNumberL=WordNumberRedundantL + 1;			// continue searching after the last redundant word on left
			WordNumberR=WordNumberRedundantR + 1;			// continue searching after the last redundant word on right
		}

		/*
		data.i.Compares++;										// increment count of comparisons
		if( (data.i.Compares%data.i.CompareStep)	== 0 )				// if count is divisible by 1000,
		{
			fwprintf(data.i.fLog,L"Comparing Documents, %d Completed\n",data.i.Compares);
			fflush(data.i.fLog);
		}
		*/

		data.matches[docL.docNum][docR.docNum] = matches;
		return -1;
	}

	/*function _documentToHtml_old(text, MatchMark, MatchAnchor, words, hrefThis, hrefOther, options)
	{
		// delimiter types
		var DEL_TYPE_NONE 		= 0;
		var DEL_TYPE_WHITE 		= 1;
		var DEL_TYPE_NEWLINE 	= 2;
		var DEL_TYPE_EOF 		= 3;

		var wordcount=0;								// current word number

		var word;
		var DelimiterType=DEL_TYPE_WHITE;

		var xMatch;
		var xAnchor;

		var LastMatch=WORD_UNMATCHED;
		var LastAnchor=0;

		var iReturn;
		var m_fHtml = [];

		var indoc = new TextReader(text);

		for(wordcount=0;wordcount<words;wordcount++)	// loop for every word
		{
			xMatch=MatchMark[wordcount];
			xAnchor=MatchAnchor[wordcount];

			if((LastMatch!=xMatch) || (LastAnchor!=xAnchor))	// check for a change of markup or anchor
			{
				if(LastMatch==WORD_PERFECT) m_fHtml.push("</span>");	// close out red markups if they were active
				else if(LastMatch==WORD_FLAW) m_fHtml.push("</span>");	// close out green italics if they were active
				else if(LastMatch==WORD_FILTERED)  m_fHtml.push("</span>");	// close out blue markups if they were active

				if(LastAnchor!=xAnchor)
				{
					if(LastAnchor>0)
					{
						m_fHtml.push("</a>");	// close out any active anchor
						LastAnchor=0;
					}
					if(xAnchor>0)
					{
						if(options.bBriefReport && (wordcount>0) ) m_fHtml.push("</p>\n<p>");	// print a paragraph mark for a new line
						var a = MatchAnchor[wordcount] + "";
						m_fHtml.push("<a id='"+hrefThis+a+"' href='#"+hrefOther+a+"'>");	// start new anchor
					}
				}

				if(xMatch==WORD_PERFECT) m_fHtml.push("<span class='match'>");	// start red for perfection
				else if(xMatch==WORD_FLAW) m_fHtml.push("<span class='match-partial'>");	// start green italics for imperfection
				else if(xMatch==WORD_FILTERED)  m_fHtml.push("<span class='match-filtered'>");	// start blue for filtered
			}

			LastMatch=xMatch;
			LastAnchor=xAnchor;

			while(true)
			{
				if(indoc.isFinished()) return m_fHtml.join("");			// shouldn't happen unless document changed during scan
				word = indoc.getWord(); // if(word.length<1) return m_fHtml.join("");	// get next word

				var tword = word;								// copy word to a temporary
				if(options.bIgnorePunctuation) tword = wordlib.removePunct(tword);	// if ignore punctuation is active, remove punctuation
				if(options.bIgnoreOuterPunctuation) tword = wordlib.outerPunct(tword);	// if ignore outer punctuation is active, remove outer punctuation
				if(options.bIgnoreNumbers) tword = wordlib.removeNumbers(tword);			// if ignore numbers is active, remove numbers
				// no point if(options.bIgnoreCase) tword = word.toLowerCase(tword);				// if ignore case is active, remove case
				if((options.bSkipLongWords && (tword.length > options.SkipLength) ) ||	// if skip too-long words is active, skip them
					(options.bSkipNonwords && !(wordlib.isWord(tword)) )){		// if skip nonwords is active, skip them

					if( (!options.bBriefReport) || (xMatch == WORD_PERFECT) || (xMatch == WORD_FLAW) )
					{
						m_fHtml.push("<span class='match-filtered'>" + _.escape(word) + "</span>");			// This is a filtered word.
						if(indoc._delimiterType == DEL_TYPE_WHITE) m_fHtml.push(" ");					// print a blank for white space
						else if(indoc._delimiterType == DEL_TYPE_NEWLINE) m_fHtml.push("\n<br>\n");			// print a break for a new line
					}

					continue;
				}

				break;
			}

			if( (!options.bBriefReport) || (xMatch == WORD_PERFECT) || (xMatch == WORD_FLAW) )
			{
				m_fHtml.push(_.escape(word));			// print the character, using UTF8 translation
				if(indoc._delimiterType == DEL_TYPE_WHITE) m_fHtml.push(" ");					// print a blank for white space
				else if(indoc._delimiterType == DEL_TYPE_NEWLINE) m_fHtml.push("\n<br>\n");			// print a break for a new line
			}
		}
		if(LastMatch==WORD_PERFECT) m_fHtml.push("</span>");	// close out red markups if they were active
		else if(LastMatch==WORD_FLAW) m_fHtml.push("</span>");	// close out green italics if they were active
		else if(LastMatch==WORD_FILTERED)  m_fHtml.push("</span>");	// close out blue markups if they were active
		if(LastAnchor>0) m_fHtml.push("</a>");	// close out any active anchor
		return m_fHtml.join("");
	}

	function _reportMatchedPair_old(textL, textR, docL, docR, options, data)
	{
		var leftHtml  = _documentToHtml_old(textL[docL.docNum],data.i.MatchMarkL,data.i.MatchAnchorL,docL.WordsTotal,"docL", "docR", options);
		var rightHtml = _documentToHtml_old(textR[docR.docNum],data.i.MatchMarkR,data.i.MatchAnchorR,docR.WordsTotal,"docR", "docL", options);
		var html = "";
		if (textL.length>1 || textR.length>1)
			html += "<div class='doc-container'><h2>Comparison results of #" + docL.docNum + " and #"+docR.docNum+"</h2>";
		html += "<div class='doc' id='docL'>" + leftHtml + "</div>\n" +
				"<div class='doc' id='docR'>" + rightHtml + "</div>\n" +
					"";
		if (textL.length>1 || textR.length>1)
			html += "</div>";
		data.htmlmatches.push(html);
		return -1;
	}*/

	function ASSERT(bool, text) {
		if (!bool)
			throw "ASSERTION! " + text;
	}

	function _briefKeepRight(str) { // given a text with paragraphs, keep the last para
		var i = str.lastIndexOf("\n\n");
		if (i>0)
			return str.slice(i+2);
		return str;
	}
	function _briefKeepLeft(str) { // give a text with paragraphs, keep the first one
		var i = str.indexOf("\n\n");
		if (i>0)
			return str.slice(0, i);
		return str;

	}
	function _brief(text, brieflen, bFirst, bLast) {
		if (brieflen>0) {
			if (brieflen==1)
				return ""; // special case brieflen. (don't show any leadin/lead out)
			if (bFirst) return brieflen<(text.length+3) ? "..." + _briefKeepRight(text.slice(-brieflen)) : text;
			if (bLast) return brieflen<(text.length+3) ? _briefKeepLeft(text.slice(0,brieflen)) + "..." : text;
			if (text.length<(brieflen*2)) return text;
			return _briefKeepLeft(text.slice(0,brieflen)) + "...\n\n\n..." + _briefKeepRight(text.slice(-brieflen));
		}
		return text;
	}

	function _briefMatch(text, matchlen) {
		// shortens any matched text to something smaller, if it's particularly big
		if (matchlen>1 && text.length>matchlen*3) {
			var l = text.slice(0, matchlen);
			var r = text.slice(-matchlen);
			return _.escape(l) + "<span class='match-removed'>[ long matching section removed ]</span>" + _.escape(r);
		}
		return _.escape(text);
	}

	function _buildDoc(text, idbase, side, otherside, matches, options) {
		if (!matches.length)
			return '';

		// sort the relevant matches in document order
		var sorted = [];
		for (var i=0; i<matches.length; i++) {
			var match = matches[i]["text" + side]; // eg matches[i].textL
			match.num = i+1;
			sorted.push(match);
		}
		sorted.sort(function(a,b) {
			return a.pos - b.pos;
		});
		var brieflen = options.bBriefReport ? (options.bTerseReport ? 1 : options.PhraseLength*3*5) : 0; // = approx 100 words. (*3 is magic number, *5 is approx #letters per word)
		var matchlen = (options.PhraseLength*3*5) * 2;
		// split the text into sections
		var html = []; // it's more efficient this way (as a list of strings there's (apparently) less memory munging on big files)
		var lastpos = 0;
		for (var i=0; i<sorted.length; i++)
		{
			var m = sorted[i];
			ASSERT(m.pos>=lastpos, "sorted matches are out of order");
			ASSERT(m.length>0, "matches' length is invalid");
			var l = _.escape(_brief(text.slice(lastpos, m.pos),brieflen,lastpos==0, false)); // untreated text to the left of this match
			var t; // this matches' text
			if (!m.skipped || m.skipped.length==0) { // easy case. no skipped words
				t = '<span class="match">' + _briefMatch(text.slice(m.pos, m.pos+m.length), matchlen) + '</span>';
			} else { // need to further split into skipped words
				t = [];
				lastpos = m.pos;
				for (var j=0; j<m.skipped.length; j++) {
					var sk = m.skipped[j];
					ASSERT(sk.pos>=lastpos, "skipped matches are out of order");
					ASSERT(sk.pos>=m.pos && (sk.pos+sk.length)<=(m.pos+m.length), "sorted matches are outside expected range");
					var l2 = _briefMatch(text.slice(lastpos, sk.pos), matchlen); // untreated text to the left of this match
					var t2 = '</span><span class="match-partial">' + _.escape(text.slice(sk.pos, sk.pos+sk.length)) + '</span><span class="match">';
					lastpos = sk.pos + sk.length;
					t.push(l2 + t2);
				}
				if (lastpos < m.pos + m.length)
					t.push(_briefMatch(text.slice(lastpos,  m.pos + m.length), matchlen));

				t = '<span class="match">' + t.join("") + '</span>'; // flatten the list of strings
				//remove any empty spans (it can happen)
				t = t.split('<span class="match"></span>').join("");
			}
			//wrap this match in an anchor, pointing to the 'other side' (it will do the same in return)
			t = '<a href="#'+idbase+otherside+m.num + '" id="'+idbase+side + m.num + '" data-match="'+m.num+'">' + t + '</a>';
			html.push(l + t);

			lastpos = m.pos+m.length;
		}
		if (lastpos < text.length)
			html.push(_.escape(_brief(text.slice(lastpos),brieflen,false, true)));

		// flatten the list of strings
		html = html.join("").split("\n").join("\n<br>\n");
		return '\n\n<div class="doc" id="'+idbase+side+'">\n' + html + '</div>';
	}

	function _reportMatchedPair(textL, textR, docL, docR, options, data)
	{
		var idbase = 'doc';
		if (textL.length>1 || textR.length>1)
		 idbase += docL.docNum + "" +docR.docNum;

		var matches = data.matches[docL.docNum][docR.docNum];
		if (matches.length==0)
			return '';// nothing matched
		var numWords = 0;
		for (var i=0; i<matches.length; i++) {
			var match = matches[i]["textL"];
			numWords += match.wordCount;
		}
		var leftHtml  = _buildDoc(textL[docL.docNum], idbase, 'L', 'R', matches, options);
		var rightHtml = _buildDoc(textR[docR.docNum], idbase, 'R', 'L', matches, options);
		var html = "";
		if (textL.length>1 || textR.length>1)
			html += "\n\n<div class='doc-container'><h2>Comparison results of #" + docL.docNum + " and #"+docR.docNum+"</h2>\n";
		html += "<div class='stats'>"+numWords+' Words in '+matches.length+" incidents</div>\n";
		html += leftHtml + rightHtml +
					"\n";
		if (textL.length>1 || textR.length>1)
			html += "</div>";
		return html;
	}

	function _copyfind(textL, textR, options, callback) {
		if (!Date.now) {
			Date.now = function now() {
				return new Date().getTime();
			};
		}

		var startTime = Date.now();

		if (_.isFunction(options)) {
		    callback = options;
		    options = {};
		}

		options = _.extend({}, defaultOptions, options);

		// turn sources into an array
		var singleDimensionedParams = _.isString(textL) && _.isString(textR);
		if (_.isString(textL)) textL = [textL];
		if (_.isString(textR)) textR = [textR];

		// check inputs
		if (!_.isArray(textL) || textL.length==0 || !_.isString(textL[0])) { return _error(callback, "'Left' source text must be a string or an array of strings.") }
		if (!_.isArray(textR) || textR.length==0 || !_.isString(textR[0])) { return _error(callback, "'Right' source text must be a string or an array of strings.") }

		var data = {
			matches: [],
			hashesL: [],
			hashesR: []
		};

		// try an apply existing hashes now.
		if (options.hashesL) {
			if (options.hashesL.length!=textL.length) { return _error(callback, "options.hashesL is invalid"); }
			data.hashesL = options.hashesL;
		}
		if (options.hashesR) {
			if (options.hashesR.length!=textR.length) { return _error(callback, "options.hashesR is invalid"); }
			data.hashesR = options.hashesR;
		}

		// convert the text to hashes (aka CompareDocument::LoadDocument)
		if (!data.hashesL.length) {
			for (var i=0; i<textL.length; i++) {
				var hashdata = _buildHashes(textL[i], options, callback);
				if (!hashdata) return;
				data.hashesL.push(hashdata);

			}
		}
		if (!data.hashesR.length) {
			for (var i=0; i<textR.length; i++) {
				var hashdata = _buildHashes(textR[i], options, callback);
				if (!hashdata) return;
				data.hashesR.push(hashdata);

			}
		}

		// CompareDocument::SetupComparisons
		var htmlmatches = [];

		// compare each Left doc against each Right one:
		for (var l=0; l<data.hashesL.length; l++) {
			// build a 2d array for the matches
			data.matches[l] = [];

			for (var r=0; r<data.hashesR.length; r++) {
				data.hashesL[l].docNum = l;
				data.hashesR[r].docNum = r;
				data.matches[l][r] = [];
				var nRc = _comparePair(data.hashesL[l], data.hashesR[r], options, data);
				if (nRc>0) {
					// erm. some error
					return _error(callback, "Error "+nRc+" occurred while comparing Document "+l+" and Document "+r, nRc);
				}
				if(options.bBuildReport && (data.i.MatchingWordsPerfect>=options.WordThreshold))		// if there are enough matches to report,
				{
					data.i.MatchingDocumentPairs++;				// increment count of matched pairs of documents
					var html = _reportMatchedPair(textL, textR, data.hashesL[l], data.hashesR[r], options, data);
					if (html.length) htmlmatches.push(html);
				}
			}
		}
		if (options.bBuildReport)
		{
			data.html = htmlmatches.join("");
		}

		if (singleDimensionedParams) {
			var matches00 = data.matches[0][0];
			data.matches = matches00; // remove the 2d array aspect, if not needed.
		}
		data.executionTime = Date.now()-startTime;
		callback(false, data);
		return true;

	}

	var copyfind = root.copyfind = _copyfind;

	if( !_.isUndefined(exports)) {
		if( !_.isUndefined(module) && module.exports ) {
			exports = module.exports = copyfind;
		}
		exports.copyfind = copyfind;
	}
	else {
		root.copyfind = copyfind;
	}

}).call(this);


/*!
 * Coolony's Kiwi
 * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com>
 * MIT Licensed
 *
 * Based on Jade compile
 * @see https://github.com/visionmedia/jade/blob/master/support/compile.js
 */

/**
 * Module dependencies.
 */

var fs = require('fs');

/**
 * Arguments.
 */

var args = process.argv.slice(2)
  , pending = args.length
  , files = {};

console.log('');

// parse arguments

args.forEach(function(file){
  var mod = file.replace('lib/', '');
  fs.readFile(file, 'utf8', function(err, js){
    if (err) throw err;
    console.log('  \033[90mcompile : \033[0m\033[36m%s\033[0m', file);
    files[file] = parse(js);
    --pending || compile();
  });
});

/**
 * Parse the given `js`.
 */

function parse(js) {
  return parseInheritance(parseConditionals(js));
}

/**
 * Parse __proto__.
 */

function parseInheritance(js) {
  return js
    .replace(/^ *(\w+)\.prototype\.__proto__ * = *(\w+)\.prototype *;?/gm, function(_, child, parent){
      return child + '.prototype = new ' + parent + ';\n'
        + child + '.prototype.constructor = '+ child + ';\n';
    });
}

/**
 * Parse the given `js`, currently supporting:
 *
 *    'if' ['node' | 'browser']
 *    'end'
 *
 */

function parseConditionals(js) {
  var lines = js.split('\n')
    , len = lines.length
    , buffer = true
    , browser = false
    , buf = []
    , line
    , cond;

  for (var i = 0; i < len; ++i) {
    line = lines[i];
    if (/^ *\/\/ *if *(node|browser)/gm.exec(line)) {
      cond = RegExp.$1;
      buffer = browser = 'browser' == cond;
    } else if (/^ *\/\/ *end/.test(line)) {
      buffer = true;
      browser = false;
    } else if (browser) {
      buf.push(line.replace(/^( *)\/\//, '$1'));
    } else if (buffer) {
      buf.push(line);
    }
  }

  return buf.join('\n');
}

/**
 * Compile the files.
 */

function compile() {
  var buf = '';
  buf += '(function() {\n';
  buf += '\n// CommonJS require()\n\n';
  buf += browser.require + '\n\n';
  buf += 'require.modules = {};\n\n';
  buf += 'require.resolve = ' + browser.resolve + ';\n\n';
  buf += 'require.register = ' + browser.register + ';\n\n';
  buf += 'require.relative = ' + browser.relative + ';\n\n';
  args.forEach(function(file){
    var js = files[file];
    file = file.replace('lib/', '');
    buf += '\nrequire.register("' + file + '", function(module, exports, require){\n';
    buf += js;
    buf += '\n}); // module: ' + file + '\n';
  });
  buf += '\nwindow.kiwi = require("kiwi");\n';
  buf += '})();\n';
  fs.writeFile('kiwi.js', buf, function(err){
    if (err) throw err;
    console.log('  \033[90m create : \033[0m\033[36m%s\033[0m', 'kiwi.js');
    console.log();
  });
}

// refactored version of weepy's
// https://github.com/weepy/brequire/blob/master/browser/brequire.js

var browser = {

  /**
   * Require a module.
   */

  require: function require(p){
    var path = require.resolve(p)
      , mod = require.modules[path];
    if (!mod) throw new Error('failed to require "' + p + '"');
    if (!mod.exports) {
      mod.exports = {};
      mod.call(mod.exports, mod, mod.exports, require.relative(path));
    }
    return mod.exports;
  },

  /**
   * Resolve module path.
   */

  resolve: function(path){
    var orig = path
      , reg = path + '.js'
      , index = path + '/index.js';
    return require.modules[reg] && reg
      || require.modules[index] && index
      || orig;
  },

  /**
   * Return relative require().
   */

  relative: function(parent) {
    return function(p){
      if ('.' != p.charAt(0)) return require(p);

      var path = parent.split('/')
        , segs = p.split('/');
      path.pop();

      for (var i = 0; i < segs.length; i++) {
        var seg = segs[i];
        if ('..' == seg) path.pop();
        else if ('.' != seg) path.push(seg);
      }

      return require(path.join('/'));
    };
  },

  /**
   * Register a module.
   */

  register: function(path, fn){
    require.modules[path] = fn;
  }
};


(function fpInit() {

  // ['a.b.c', 'd'] -> ['a', 'b', 'c', 'd']
  function splitDots(list) {
    var result = [];
    list.forEach(function (x) {
      if (typeof x === 'string') {
        x.split('.').forEach(function (part) {
          result.push(part);
        });
      } else {
        result.push(x);
      }
    });
    return result;
  }

  function assemble() {

    // Creates an optimistic chain, no checks before calling a function
    // or accessing a property, or calling a method
    function fp() {
      var args = Array.prototype.slice.call(arguments, 0);
      if (args.length) {
        var fns = splitDots(args);
        return function applyPipe(d) {
          fns.forEach(function (fn) {
            if (typeof fn === 'string') {
              if (typeof d[fn] === 'function') {
                d = d[fn].call(d, d);
              } else {
                d = d[fn];
              }
            } else if (typeof fn === 'function') {
              d = fn(d);
            } else {
              throw new Error('Cannot apply ' + JSON.stringify(fn, null, 2) +
                ' to value ' + d + ' not a property name or function');
            }
          });
          return d;
        };
      }
    }

    return fp;
  }

  function register(value, name) {
    if (typeof window === 'object') {
      /* global window */
      window[name] = value;
    } else if (typeof module === 'object') {
      module.exports = value;
    } else {
      throw new Error('Do not know how to register ' + name);
    }
  }

  if (typeof global === 'object' && global.fpDebug) {
    module.exports = require('./fp-debug');
  } else {
    var fp = assemble();
    fp.version = {
      name: '<%= pkg.name %>',
      version: '<%= pkg.version %>',
      author: '<%= pkg.author %>',
      description: '<%= pkg.description %>',
      homepage: '<%= pkg.homepage %>'
    };
    register(fp, 'fp');
  }

}());


(function () {

  // ['a.b.c', 'd'] -> ['a', 'b', 'c', 'd']
  function splitDots(list) {
    var result = [];
    list.forEach(function (x) {
      if (typeof x === 'string') {
        x.split('.').forEach(function (part) {
          result.push(part);
        });
      } else {
        result.push(x);
      }
    });
    return result;
  }

  function assemble() {

    function isStringOrFunction(f) {
      return typeof f === 'string' ||
        typeof f === 'function';
    }

    function humanizeArgument(f, k) {
      if (typeof f === 'string') {
        return k + ': string ' + f;
      }
      if (typeof f === 'function') {
        return k + ': function ' + f.name;
      }
      if (typeof f === 'undefined') {
        return k + ': undefined argument';
      }
      return k + ': ' + (typeof f) + ' argument';
    }

    // Creates an optimistic chain, no checks before calling a function
    // or accessing a property, or calling a method
    function fp() {
      var args = Array.prototype.slice.call(arguments, 0);
      if (args.length) {
        if (!args.every(isStringOrFunction)) {
          var signature = args.map(humanizeArgument).join('\n\t');
          throw new Error('Invalid arguments to functional pipeline - not a string or function\n\t' +
            signature);
        }

        var fns = splitDots(args);
        return function (d) {
          var originalObject = d;
          fns.forEach(function (fn) {
            if (typeof fn === 'string') {
              if (typeof d[fn] === 'function') {
                d = d[fn].call(d, d);
              } else if (typeof d[fn] !== 'undefined') {
                d = d[fn];
              } else {
                var signature = args.map(humanizeArgument).join('\n\t');
                throw new Error('Cannot use property ' + fn + ' from object ' +
                  JSON.stringify(d, null, 2) + '\npipeline\n\t' + signature +
                  '\noriginal object\n' + JSON.stringify(originalObject, null, 2));
              }
            } else if (typeof fn === 'function') {
              d = fn(d);
            } else {
              throw new Error('Cannot apply ' + JSON.stringify(fn, null, 2) +
                ' to value ' + d + ' not a property name or a function');
            }
          });
          return d;
        };
      }
    }

    return fp;
  }

  function register(value, name) {
    if (typeof window === 'object') {
      /* global window */
      window[name] = value;
    } else if (typeof module === 'object') {
      module.exports = value;
    } else {
      throw new Error('Do not know how to register ' + name);
    }
  }

  var fp = assemble();
  fp.debug = true;
  register(fp, 'fp');

}());


/**
 * VATPeriod
 *
 * @param {Date} effectiveFrom
 * @param {number|undefined} [superReduced]
 * @param {array|undefined} [reduced]
 * @param {number|undefined} [standard]
 * @param {number|undefined} [parking]
 */
function VATPeriod(effectiveFrom, superReduced, reduced, standard, parking) {
  this._effectiveFrom = effectiveFrom;
  this._superReduced = superReduced;
  this._reduced = reduced;
  this._standard = standard;
  this._parking = parking;
}

/**
 * Get the Date from which this period is effective.
 *
 * @returns {Date}
 */
VATPeriod.prototype.getEffectiveFrom = function() {
  return this._effectiveFrom;
};

/**
 * Get the super reduced rate.
 *
 * @returns {number|undefined}
 */
VATPeriod.prototype.getSuperReducedRate = function() {
  return this._superReduced;
};

/**
 * Get the reduced rates.
 *
 * @returns {number[]|undefined}
 */
VATPeriod.prototype.getReducedRates = function() {
  return this._reduced;
};

/**
 * Get the standard rate.
 *
 * @returns {number|undefined}
 */
VATPeriod.prototype.getStandardRate = function() {
  return this._standard;
};

/**
 * Get the parking rate.
 *
 * @returns {number|undefined}
 */
VATPeriod.prototype.getParkingRate = function() {
  return this._parking;
};

module.exports = VATPeriod;


/*!
 * Coolony's Kiwi
 * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com>
 * MIT Licensed
 */

/*
 * Module dependencies
 */

// if node
var frame = require('frame');
var _ = require('underscore');
// end

/*
 * Apply `filters` to `input`, and return output.
 *
 * @param {String} input
 * @param {Mixed[]} filters
 * @return {String}
 * @api private
 */

function filter(input, filters) {
  var unknownFilter;
  filters.forEach(function(filterName) {
    var filterArgs = [];
    if(_.isArray(filterName)) {
      filterArgs = filterName.slice(1);
      filterName = filterName[0];
    }
    var filterFunction = module.exports.filters[filterName];
    if(!filterFunction) {
      unknownFilter = filterName;
      return false;
    }
    input = filterFunction.apply(this, [input].concat(filterArgs));
  });
  if(unknownFilter) {
    throw new Error('Rendering error: Unknown filter `' +
                    unknownFilter +
                    '`.'
                    );
  }
  return input;
}

/*
 * Module exports
 */

module.exports = filter;
var filters = module.exports.filters = {};

/*
 * Read tags from tags directory
 */

function loadFilters(loadedFiles) {
  loadedFiles = loadedFiles || frame.files.requireDir(__dirname + '/filters/');
  for(var file in loadedFiles) {
    var fileFilters = loadedFiles[file];
    for(var filter in fileFilters) {
      filters[filter] = fileFilters[filter];
    }
  }
}
// if node
loadFilters();
// end
// if browser
// var files = ['base', 'datetime'];
// var acc = {};
// _.each(files, function(file) {
//   acc[file] = require('./filters/' + file);
// });
// loadFilters(acc);
// endif


'use strict';

var events = require('events');
var inherits = require('inherits');
var NanoTimer = require('nanotimer');

/**
 * Construct a new Rieussec stopwatch
 *
 * @class Rieussec
 * @classdesc A rieussec stopwatch object.
 *
 * @param {Number} [tickRate=100] - How often (in milliseconds) to emit "tick" events
 */
var Rieussec = function(tickRate){
    events.EventEmitter.call(this);

    // Initialize private properties
    this._milliseconds = 0;
    this._setState('stopped');
    this._timer = new NanoTimer();

    tickRate = tickRate || 100;
    Object.defineProperty(this, 'tickRate', {
        enumerable: true,
        configurable: false,
        writable: false,
        value: tickRate
    });
};

inherits(Rieussec, events.EventEmitter);

/**
 * Start the timer.
 * @memberof Rieussec
 * @method start
 */
Rieussec.prototype.start = function() {
    if (this._state === 'stopped' || this._state === 'paused') {
        this._tick(this._milliseconds);
        this._startInterval();
        return true;
    } else {
        return false;
    }
};

/**
 * Pause the timer.
 * @memberof Rieussec
 * @method pause
 */
Rieussec.prototype.pause = function() {
    if (this._state === 'running') {
        this._stopInterval();
        this._setState('paused');
        this._milliseconds += this.hrtimeToMs(process.hrtime(this._lastTickHrtime));
        this._lastTickHrtime = process.hrtime();
        this._tick(this._milliseconds);
        return true;
    } else {
        return false;
    }
};

/**
 * Reset the timer.
 * @memberof Rieussec
 * @method reset
 */
Rieussec.prototype.reset = function() {
    this._stopInterval();
    this._milliseconds = 0;
    this._tick(0);
    this._setState('stopped');
};

/**
 * Manually set the timer (in milliseconds).
 * @memberof Rieussec
 * @method setMilliseconds
 * @param {Number} ms - The new duration of the timer
 * @param {Boolean} [keepCycle=false] - If true, retains the hundreds, tens, and ones places from the previous tick,
 * keeping the seconds cycle consistent.
 */
Rieussec.prototype.setMilliseconds = function(ms, keepCycle) {
    // Retains the decimal portion of the previous number, which keeps the tick cycle consistent
    if (keepCycle) {
        if (this._state !== 'paused') {
            this._milliseconds = this.hrtimeToMs(process.hrtime(this._lastTickHrtime));
        }

        var modThousands = this._milliseconds % 1000;
        this._milliseconds = ms - (ms % 1000) + modThousands;
    } else {
        this._milliseconds = ms;
    }

    this._tick(this._milliseconds);
};

Rieussec.prototype.hrtimeToMs = function(hrtime) {
    return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
};

Rieussec.prototype._tick = function(ms) {
    this._lastTickHrtime = process.hrtime();
    if (typeof (ms) !== 'undefined') {
        this.emit('tick', ms);
    } else {
        if (this._state === 'stopped') return;
        this._milliseconds += this.tickRate;
        this.emit('tick', this._milliseconds);
    }
};

Rieussec.prototype._startInterval = function() {
    if (this._state === 'running') {
        return false;
    } else {
        this._setState('running');
        this._timer.setInterval(this._tick.bind(this), null, this.tickRate + 'm');
        return true;
    }
};

Rieussec.prototype._stopInterval = function() {
    if (this._state === 'running') {
        this._timer.clearInterval();
        return true;
    } else {
        return false;
    }
};

Rieussec.prototype._setState = function(state) {
    this._state = state;
    this.emit('state', state);
};

module.exports = Rieussec;


/*!
 * Coolony's Kiwi
 * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com>
 * MIT Licensed
 */

/**
 * Module dependencies
 */

var token = require('./token');
var utils = require('./utils');

/**
 * Constants
 */

var TAG_OPENING_DELIMITER = '{{';
var TAG_CLOSING_DELIMITER = '}}';
var TAG_OPENING_DELIMITER_LENGTH = TAG_OPENING_DELIMITER.length;
var TAG_NAME_MATCH = /^(\/)?\s*([^\s\}\(]*)/;

/**
 * Initialize `Compiler` with the given `Template`, from the given `source`,
 * with `options`.
 *
 * @param {String} source
 * @api private
 */

var Compiler = module.exports = function(template) {
  this.template = template;
  this.source = template.template;
  this.options = template.options;
  this.helpers = {};
  this.__compilationEnd = [];
};

/**
 * Compile template to a native JS function, and invoke
 * `callback(err, compiled)`.
 *
 * @param {Function} callback
 * @api private
 */

Compiler.prototype.compile = function(callback) {
  var _this = this;

  // Callback for utils.applyAll
  function onProcessed(err, processed) {
    if(err) return callback(err);
    _this._tokenize(processed, onTokenized);
  }

  // Callback for Compiler#_tokenize
  function onTokenized(err, tokenized) {
    if(err) return callback(err);
    tokenized.compile(_this, onCompiled);
  }

  // Callback for Compiler#_compileTokens
  function onCompiled(err, compiled) {
    if(err) return callback(err);

    var func;

    try {
      func = new Function("$template",
                          "$tools",
                          "_",
                          "$data",
                          "$helpers",
                          "$callback",
                          compiled);
    } catch(err) {
      return callback(err);
    }

    func.$helpers = _this.helpers;

    callback(null, func);
  }

  // Apply tag before processors
  utils.applyAll(this.source, token.tagBeforeProcessors, [this], onProcessed);
};

/**
 * Tokenize template, and invoke `callback(err, tokenized)`.
 *
 * @param {Function} callback
 * @api private
 */

Compiler.prototype._tokenize = function(source, callback) {
  var workingSource = source;
  var currentToken = new token.RootToken();
  var rootToken = currentToken;
  var inToken = false;
  var currentPart;

  // Lookup templates in document, and build parse tree
  while(true) {
    var nextInflexion = workingSource.search(inToken ?
                                               TAG_CLOSING_DELIMITER :
                                               TAG_OPENING_DELIMITER
                                             );

    if(nextInflexion === -1) {
      this._pushLiteralToken(rootToken, currentToken, workingSource);
      break;
    } else {
      if(inToken) nextInflexion = nextInflexion + TAG_OPENING_DELIMITER_LENGTH;

      currentPart = workingSource.slice(0, nextInflexion);
      workingSource = workingSource.slice(nextInflexion);

      if(!inToken) {
        this._pushLiteralToken(rootToken, currentToken, currentPart);
      } else {
        currentPart = currentPart.slice(2, -2);
        try {
          currentToken = this._pushToken(rootToken, currentToken, currentPart);
        } catch(err) {
          return callback(err);
        }
      }

      inToken = !inToken;
    }
  }

  // Ensure we are at root level
  if(rootToken !== currentToken) {
    return callback(new Error('Tokenization error: unexpected end of file, ' +
                              'expected `' +
                              currentToken.tag.tagName +
                              '`.'
                              ));
  }

  callback(null, rootToken);
};

/**
 * Create `LiteralToken` with `literal`, and append it to `parent`.
 *
 * @param {BaseToken} parent
 * @param {String} literal
 * @api private
 */

Compiler.prototype._pushLiteralToken = function(root, parent, literal) {
  if(!literal.length) return;
  parent.children.push(new token.LiteralToken(literal, root));
};

/**
 * Create `BaseToken` from tag `tag`, and append it to `current`.
 * Return the new working token.
 *
 * @param {BaseToken} current
 * @param {String} tag
 * @return {BaseToken} New working token.
 * @api private
 */

Compiler.prototype._pushToken = function(root, current, tag) {
  var match = tag.match(TAG_NAME_MATCH);
  var tagName = match[2];
  var openingTag = match[1] !== '/';
  var tagType = current.lookupTag(tagName);
  var newToken;

  // Check whether we know this tag
  if(!tagType) {
    throw new Error('Tokenization error: Unknown tag `' + tagName + '`.');
  }

  // Handle intermediate tags
  if(tagType.isIntermediate) {

    // If we already are in an intermediate tag, close it
    if(current.tagType.isIntermediate) current = current.parent;

    // Push the tag to the stack
    newToken = new token.IntermediateToken(tag,
                                               tagType,
                                               root,
                                               current,
                                               [],
                                               this.options);
    current.intermediate.push(newToken);
    return newToken;

  }

  // Handle block tags
  else if(tagType.isBlock) {

    // Handle opening tags
    if(openingTag) {
      newToken = new token.BlockToken(tag,
                                      tagType,
                                      root,
                                      current,
                                      [],
                                      this.options);

      current.children.push(newToken);
      return newToken;

    // Handle closing tags
    } else {
      if(!current.parent) {
        throw new Error('Tokenization error: unexpected `' +
                        tagName +
                        '` tag at root level.'
                        );
      }

      if(current.tagType.isIntermediate) {
        current = current.parent;
      }

      if(current.tagType.tagName !== tagName) {
        throw new Error('Tokenization error: unexpected `' +
                        tagName +
                        '` tag, expected `' +
                        current.tag.tagName +
                        '`.'
                        );
      }
      return current.parent;
    }
  }

  // Handle leaf tags
  else {

    // Ensure we don't have a closing tag
    if(!openingTag) {
      throw new Error('Tokenization error: `' +
                      tagName +
                      '` is not a block tag.'
                      );
    }

    newToken = new token.LeafToken(tag,
                                   tagType,
                                   root,
                                   current,
                                   this.options);

    current.children.push(newToken);
    return current;
  }
};

Compiler.prototype.registerTemplateHelper = function(name, helper) {
  this.helpers[name] = helper;
};


/*!
 * Coolony's Kiwi
 * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com>
 * MIT Licensed
 */

/*
 * Module dependencies
 */

// if browser
var frame;
// end
// if node
var _ = require('underscore');
var frame = require('frame');
// end

var utils = require('./utils');
var inherits = frame ? frame.classes.extend : utils.inherits;

/*
 * Global variables
 */

var headDeclarations = [];
var footDeclarations = [];
var tags = {};
var tagBeforeProcessors = [];
var helpers = {};

/*
 * Base token
 */

/*
 * Initializes `BaseToken`, with `parent` and `root`.
 *
 * @api private
 * @constructor
 */

function BaseToken(root, parent, options) {
  this.parent = parent;
  this.children = [];
  this.tag = null;
  this.tagType = null;
  this.options = options;
  this.root = root;
}

/*
 * Stub for the `compile` method, to be overloaded, ivoking
 * `callback(err, compiled)`
 *
 * @param {Function} callback
 * @api private
 */

BaseToken.prototype.compile = function(compiler, callback) {
  callback(new Error('Stub method BaseToken#compile must be overloaded'));
};

/*
 * Look for `tag` definition in global tags, or in current token's special tags.
 * Return the first matched tag.
 *
 * @param {String} tag
 * @return {Object} Tag definition for `tag`
 * @api private
 */

BaseToken.prototype.lookupTag = function(tag) {
  if(tags[tag]) return tags[tag];
  if(this.tagType && this.tagType.intermediateTags &&
                     this.tagType.intermediateTags[tag]) {

    this.tagType.intermediateTags[tag].isIntermediate = true;
    return this.tagType.intermediateTags[tag];
  }
};

/*
 * Literal token
 */

/*
 * Initialize `LiteralToken`, with `parent`, `root` and `literal`
 *
 * @param {String} literal
 * @param {BaseToken} parent
 * @api private
 * @extends BaseToken
 * @constructor
 */

function LiteralToken(literal, root, parent, options) {
  LiteralToken._superclass.call(this, root, parent, options);
  this.literal = literal;
}
inherits(LiteralToken, BaseToken);

/*
 * Compile `LiteralToken` to JavaScript, and invoke `callback(err, compiled)`
 *
 * @param {Function} callback (Optional)
 * @api private
 */

LiteralToken.prototype.compile = function(compiler, callback) {
  this._compiled = '__acc.push("' +
                   utils.escapeCompiledString(this.literal) +
                   '");';

  callback(null, this._compiled);
};

/*
 * Root token
 */

/*
 * Initialize `RootToken`, with `children`
 *
 * @param {BaseToken[]} children [children]
 * @api private
 * @extends BaseToken
 * @constructor
 */

function RootToken(children, options) {
  RootToken._superclass.call(this, this, null, options);
  if(!children) children = [];
  this.head = [];
  this.children = children;
}
inherits(RootToken, BaseToken);

/*
 * Compile `RootToken` to JavaScript, and invoke `callback(err, compiled)`
 *
 * @param {Function} callback (Optional)
 * @api private
 */

RootToken.prototype.compile = function(compiler, callback) {
  var _this = this;

  function onCompiled(err, compiled) {
    if(err) return callback(err);
    compiled = 'var __this = this;' +
               'var __acc = [];' +
               'var __blocks = {};' +
               'if($template.options && $template.options._parent) {' +
                 '__blocks = $template.options._parent.blocks;' +
               '}' +
               'var __tmp;' +
               'var __err;' +
               '$data = $data || {};' +
               headDeclarations.join('') +
               'with($data) {' +
                 compiled +
                 footDeclarations.join('') +
                 'var __compiled = new String(__acc.join(""));' +
                 '__compiled.blocks = __blocks;' +
                 '$callback(null, __compiled);' +
                 compiler.__compilationEnd.join('') +
               '}';
    _this._compiled = compiled;
    callback(null, compiled);
  }

  utils.compileTokens(this.children, compiler, onCompiled);
};

/*
 * Block token
 */

/*
 * Initialize `BlockToken` for `tag` of `tagType` with `parent` and `children`
 *
 * @param {Object} tag
 * @param {BaseToken} parent
 * @param {BaseToken[]} [children]
 * @api private
 * @extends BaseToken
 * @constructor
 */

function BlockToken(tag, tagType, root, parent, children, options) {
  BlockToken._superclass.call(this, root, parent, options);
  this.tag = tag;
  this.tagType = tagType;
  if(!children) children = [];
  this.children = children;
  this.intermediate = [];
}
inherits(BlockToken, BaseToken);

/*
 * Compile `BlockToken` to JavaScript, and invoke `callback(err, compiled)`
 *
 * @param {Function} callback (Optional)
 * @api private
 */

BlockToken.prototype.compile = function(compiler, callback) {
  var _this = this;
  var compiledContents;
  var compiledIntermediate;
  var compile = this.tagType.compile;
  var compilationFunction = compile.joinCompilationResult !== false ?
                              'compileTokens' : 'compileTokenArray';

  function onContentsCompiled(err, compiled) {
    if(err) return callback(err);
    compiledContents = compiled;
    utils.compileTokenArray(_this.intermediate, compiler,
                            onIntermediateCompiled);
  }

  function onIntermediateCompiled(err, compiled) {
    if(err) return callback(err);
    compiledIntermediate = compiled;
    compile(_this, compiledContents,
            compiledIntermediate, compiler,
            onCompiled);
  }

  function onCompiled(err, compiled) {
    if(err) return callback(err);
    _this._compiled = compiled;
    callback(null, compiled);
  }

  utils[compilationFunction](this.children, compiler, onContentsCompiled);
};

/*
 * Leaf token
 */

/*
 * Initialize `LeafToken` for `tag` of `tagType` with `parent`
 *
 * @param {Object} tag
 * @param {BaseToken} parent
 * @param {BaseToken[]} [children]
 * @api private
 * @extends BaseToken
 * @constructor
 */

function LeafToken(tag, tagType, root, parent, options) {
  LeafToken._superclass.call(this, root, parent, options);
  this.tag = tag;
  this.tagType = tagType;
}
inherits(LeafToken, BaseToken);

/*
 * Compile `LeafToken` to JavaScript, and invoke `callback(err, compiled)`
 *
 * @param {Compiler} compiler
 * @param {Function} callback
 * @api private
 */

LeafToken.prototype.compile = function(compiler, callback) {
  var _this = this;

  function onCompiled(err, compiled) {
    if(err) return callback(err);
    _this._compiled = compiled;
    callback(null, compiled);
  }

  var compiled = this.tagType.compile(this, null, compiler, onCompiled);
};

/*
 * Untokenize `LeafToken`, and return the result.
 *
 * @param {Compiler} compiler
 * @return {String}
 * @api private
 */

LeafToken.prototype.untokenize = function(compiler) {
  return this.tagType.compile.untokenize ?
           this.tagType.compile.untokenize(this, compiler) :
           ('{{' + this.tag + '}}');
};

/*
 * Initialize `IntermediateToken` for `tag` of `tagType` with `parent`
 * and `children`
 *
 * @param {Object} tag
 * @param {BaseToken} parent
 * @param {BaseToken[]} [children]
 * @api private
 * @extends BaseToken
 * @constructor
 */

function IntermediateToken(tag, tagType, root, parent, children, options) {
  IntermediateToken._superclass.call(this, tag, tagType, root,
                              parent, children, options);
}
inherits(IntermediateToken, BlockToken);

/*
 * Compile `IntermediateToken` to JavaScript, and invoke
 * `callback(err, compiled)`
 *
 * @param {Function} callback (Optional)
 * @api private
 */

IntermediateToken.prototype.compile = function(compiler, callback) {
  var _this = this;

  function onContentsCompiled(err, compiledContents) {
    if(err) return callback(err);
    callback(null, compiledContents);
  }

  utils.compileTokens(this.children, compiler, onContentsCompiled);
};

/*
 * Look for `tag` definition in global tags, or in current token's special tags.
 * Return the first matched tag.
 *
 * @param {String} tag
 * @return {Object} Tag definition for `tag`
 * @api private
 */

IntermediateToken.prototype.lookupTag = function(tag) {
  return this.parent.lookupTag(tag);
};

/**
 * Register `tag` with name `name`.
 *
 * @param {String} name
 * @param {Function} tag
 * @api public
 */

function registerTag(name, tag) {
  if(tag.beforeProcessor) {
    if(tag.beforeProcessorPrepend) {
      tagBeforeProcessors.unshift(tag.beforeProcessor);
    } else {
      tagBeforeProcessors.push(tag.beforeProcessor);
    }
  }
  if(tag.headDeclarations) {
    headDeclarations.push(tag.headDeclarations);
  }
  if(tag.footDeclarations) {
    footDeclarations.push(tag.footDeclarations);
  }
  if(tag.helpers) {
    for(var helperName in tag.helpers) {
      registerHelper(helperName, tag.helpers[helperName]);
    }
  }
  tags[name] = tag;
  tag.tagName = name;
}

/**
 * Register `helper` with name `name`.
 *
 * @param {String} name
 * @param {Function} helper
 * @api public
 */

function registerHelper(name, helper) {
  helpers[name] = helper;
}

/*
 * Module exports
 */

module.exports = {
  BaseToken: BaseToken,
  RootToken: RootToken,
  BlockToken: BlockToken,
  IntermediateToken: IntermediateToken,
  LeafToken: LeafToken,
  LiteralToken: LiteralToken,
  headDeclarations: headDeclarations,
  footDeclarations: footDeclarations,
  tags: tags,
  tagBeforeProcessors: tagBeforeProcessors,
  helpers: helpers,
  registerTag: registerTag,
  registerHelper: registerHelper
};

/*
 * Read tags from tags directory
 */

function loadTags(loadedFiles) {
  loadedFiles = loadedFiles || frame.files.requireDir(__dirname + '/tags/');
  for(var file in loadedFiles) {
    var fileTags = loadedFiles[file].tags;
    var fileHelpers = loadedFiles[file].helpers;

    // Process tags
    for(var tag in fileTags) {
      registerTag(tag, fileTags[tag]);
    }

    // Process tags
    if(fileHelpers) {
      for(var helperName in fileHelpers) {
        registerHelper(helperName, fileHelpers[helperName]);
      }
    }
  }
}

// if node
loadTags();
// end
// if browser
// var files = [
//   'block', 'comment', 'each', 'if', 'print', 'raw', 'tmpl', 'ifblock', 'as', 'include', 'extend'
// ];
// var acc = {};
// _.each(files, function(file) {
//   acc[file] = require('./tags/' + file);
// });
// loadTags(acc);
// endif


/*!
 * Coolony's Kiwi
 * Copyright ©2012 Pierre Matri <pierre.matri@coolony.com>
 * MIT Licensed
 */

/**
 * Module dependencies
 */

// if node
var _ = require('underscore');
// end

/**
 * Constants
 */

var IF_PARSE_RE = /^if\s+(.*)$/;
var ELSE_PARSE_RE = /^else(?:\s+(.*))?$/;

/**
 * Global variables
 */

module.exports.tags = {};
var ifTag = module.exports.tags['if'] = {};
var intermediateTags = ifTag.intermediateTags = {};
var elseTag = intermediateTags['else'] = {};

/**
 * Global functions
 */

/**
 * Outputs `if` clause based on `condition`. If not `strict`,
 * actual test will be wrapped in a `try…catch` statement to catch
 * ReferenceErrors silently
 *
 * @param {String} condition
 * @param {Boolean} strict
 * @return {String}
 * @api private
 */

function createIfCondition(condition, strict) {
  var compiled;
  if(strict) {
    compiled = 'if(' + condition + ')';
  } else {
    compiled = 'try {' +
                 '__tmp = ' + condition +
               '} catch(__err) {' +
                 'if(__err instanceof ReferenceError) {' +
                   '__tmp = false;' +
                 '} else {' +
                   'throw __err;' +
                 '}' +
               '}' +
               'if(__tmp)';
  }
  return compiled;
}

/**
 * Basic tag settings
 */

ifTag.isBlock = true;

/**
 * Compile `token` with `compiledContents` to JavaScript, and invoke
 * `callback(err, compiled)`.
 *
 * @param {BlockToken} token
 * @param {String} compiledContents
 * @param {Function} callback
 * @api private
 */

ifTag.compile = function(token, compiledContents,
                            compiledIntermediate, compiler, callback) {
  var parsed = token.tag.match(IF_PARSE_RE);

  if(!parsed) {
    return callback(new Error('Compilation error: Unable to parse tag `' +
                              token.tag +
                              '`.'
                              ));
  }

  var condition = parsed[1];
  var appendEnd = ['}'];

  // Handle basic if
  var compiled = createIfCondition(condition, token.options.strict);
  compiled += '{' + compiledContents;

  // Handle else
  var err;
  _.each(token.intermediate, function(intermediate, index) {
    var intermediateTag = intermediate.tag;
    var compiledIntermediateTag = compiledIntermediate[index];
    var parsed = intermediateTag.match(ELSE_PARSE_RE);

    if(!parsed) {
      err = new Error('Compilation error: Unable to parse tag `' +
                      token.tag +
                      '`.'
                      );
      return;
    }

    var condition = parsed[1];
    if(!condition) {
      compiled += '} else {';
    } else {
      appendEnd.push('}');
      compiled += '} else {' +
                  createIfCondition(condition, token.options.strict) +
                  '{';
    }

    compiled += compiledIntermediate[index];
  });

  // Handle error
  if(err) return callback(err);

  // Return
  compiled += appendEnd.join('');
  callback(null, compiled);
};


/**
 * VATCountry
 *
 * @param {string} name
 * @param {string} code
 * @param {string} countryCode
 * @param {VATPeriod[]} periods
 * @param {Date} [date]
 */
function VATCountry(name, code, countryCode, periods, date) {
  this._name = name;
  this._code = code;
  this._countryCode = countryCode;
  this._periods = periods;

  this.setDate(date);
}

/**
 * @returns {string}
 */
VATCountry.prototype.getName = function() {
  return this._name;
};

/**
 *
 * @returns {string}
 */
VATCountry.prototype.getCode = function() {
  return this._code;
};

/**
 * @returns {string}
 */
VATCountry.prototype.getCountryCode = function() {
  return this._countryCode;
};

/**
 * @returns {VATPeriod[]}
 */
VATCountry.prototype.getPeriods = function() {
  return this._periods;
};

/**
 * Set the date for which to return VAT rates.
 *
 * @param {Date|null} date
 */
VATCountry.prototype.setDate = function(date) {
  if (!date) {
    date = new Date();
  }
  this._date = date;
};

/**
 * Get the VATPeriod matching the given date.
 *
 * @param date
 * @returns {VATPeriod|undefined}
 */
VATCountry.prototype.getPeriod = function(date) {
  var resultPeriod;

  for (var i = this._periods.length - 1; i >= 0; i--) {
    var period = this._periods[i];

    if (period.getEffectiveFrom() <= date) {
      resultPeriod = period;
    }
  }

  return resultPeriod;
};

/**
 * Get the VATPeriod matching the current date.
 *
 * @returns {VATPeriod||undefined}
 */
VATCountry.prototype.getCurrentPeriod = function() {
  return this.getPeriod(this._date);
};

/**
 * Did this country use VAT on the given date.
 *
 * @returns {boolean}
 */
VATCountry.prototype.usedVATOnDate = function(date) {
  var period = this.getPeriod(date);

  return !!(period && period.getStandardRate());
};

/**
 * Did this country use VAT on the current date.
 *
 * @returns {boolean}
 */
VATCountry.prototype.usedVATOnCurrentDate = function() {
  return this.usedVATOnDate(this._date);
};

/**
 * Get the super reduced rate for this country.
 *
 * @returns {number|undefined}
 */
VATCountry.prototype.getSuperReducedRate = function() {
  var period = this.getCurrentPeriod();

  return (period ? period.getSuperReducedRate() : undefined);
};

/**
 * Get the reduced rate for this country.
 *
 * @returns {number[]|undefined}
 */
VATCountry.prototype.getReducedRates = function() {
  var period = this.getCurrentPeriod();

  return (period ? period.getReducedRates() : undefined);
};

/**
 * Get the standard rate for this country.
 *
 * @returns {number|undefined}
 */
VATCountry.prototype.getStandardRate = function() {
  var period = this.getCurrentPeriod();

  return (period ? period.getStandardRate() : undefined);
};

/**
 * Get the parking rate for this country.
 *
 * @returns {number|undefined}
 */
VATCountry.prototype.getParkingRate = function() {
  var period = this.getCurrentPeriod();

  return (period ? period.getParkingRate() : undefined);
};

module.exports = VATCountry;