'use strict'

const sharedUtil = require('prettier/src/common/util-shared')
const {getNextNonSpaceNonCommentCharacter, isFunction} = require('./util')

const addLeadingComment = sharedUtil.addLeadingComment
const addTrailingComment = sharedUtil.addTrailingComment
const addDanglingComment = sharedUtil.addDanglingComment

function handleOwnLineComment(comment, text, options, ast, isLastComment) {
  const precedingNode = comment.precedingNode
  const enclosingNode = comment.enclosingNode
  const followingNode = comment.followingNode
  if (
    handleLastFunctionArgComments(
      text,
      precedingNode,
      enclosingNode,
      followingNode,
      comment,
      options
    ) ||
    handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
    handleIfStatementComments(
      text,
      precedingNode,
      enclosingNode,
      followingNode,
      comment
    ) ||
    handleTryStatementComments(enclosingNode, followingNode, comment) ||
    handleOnlyComments(enclosingNode, comment, isLastComment) ||
    handleAssignmentPatternComments(enclosingNode, comment)
  ) {
    return true
  }
  return false
}

function handleEndOfLineComment(comment, text, options, ast, isLastComment) {
  const precedingNode = comment.precedingNode
  const enclosingNode = comment.enclosingNode
  const followingNode = comment.followingNode
  if (
    handleLastFunctionArgComments(
      text,
      precedingNode,
      enclosingNode,
      followingNode,
      comment,
      options
    ) ||
    handleIfStatementComments(
      text,
      precedingNode,
      enclosingNode,
      followingNode,
      comment
    ) ||
    handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
    handleOnlyComments(enclosingNode, comment, isLastComment) ||
    handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
  ) {
    return true
  }
  return false
}

function handleRemainingComment(comment, text, options, ast, isLastComment) {
  const precedingNode = comment.precedingNode
  const enclosingNode = comment.enclosingNode
  const followingNode = comment.followingNode
  if (
    handleIfStatementComments(
      text,
      precedingNode,
      enclosingNode,
      followingNode,
      comment
    ) ||
    handleCommentInEmptyParens(text, enclosingNode, comment, options) ||
    handleOnlyComments(enclosingNode, comment, isLastComment) ||
    handleFunctionNameComments(text, enclosingNode, comment, options)
  ) {
    return true
  }
  return false
}

function handleVariableDeclaratorComments(
  enclosingNode,
  followingNode,
  comment
) {
  if (
    enclosingNode &&
    enclosingNode.type === 'AssignmentExpression' &&
    followingNode &&
    (followingNode.type === 'ObjectExpression' ||
      followingNode.type === 'ArrayExpression' ||
      followingNode.type === 'TemplateLiteral' ||
      followingNode.type === 'StringLiteral' ||
      followingNode.type === 'TaggedTemplateExpression')
  ) {
    addLeadingComment(followingNode, comment)
    return true
  }
  return false
}

function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
  if (
    enclosingNode &&
    enclosingNode.type === 'MemberExpression' &&
    followingNode &&
    (followingNode.type === 'Identifier' ||
      followingNode.type === 'CallExpression') // TODO: this is so that eg memberInAndOutWithCalls test won't break, but should probably correctly print as leading comment (here and for JS)
  ) {
    addLeadingComment(enclosingNode, comment)
    return true
  }

  return false
}

// function handleIfStatementComments(text, precedingNode, enclosingNode, followingNode, comment, options) {
function handleIfStatementComments(
  text,
  precedingNode,
  enclosingNode,
  followingNode,
  comment
) {
  if (
    !enclosingNode ||
    !(
      enclosingNode.type === 'IfStatement' ||
      enclosingNode.type === 'ConditionalExpression'
    ) ||
    !followingNode
  ) {
    return false
  }

  if (
    precedingNode === enclosingNode.consequent &&
    followingNode === enclosingNode.alternate
  ) {
    // if (precedingNode.type === 'BlockStatement') {
    //   addTrailingComment(precedingNode, comment)
    // } else {
    addDanglingComment(enclosingNode, comment)
    // }
    return true
  }

  return false
}

function handleTryStatementComments(enclosingNode, followingNode, comment) {
  if (
    !enclosingNode ||
    enclosingNode.type !== 'TryStatement' ||
    !followingNode
  ) {
    return false
  }

  if (followingNode.type === 'BlockStatement') {
    addBlockStatementFirstComment(followingNode, comment)
    return true
  }

  if (followingNode.type === 'TryStatement') {
    addBlockOrNotComment(followingNode.finalizer, comment)
    return true
  }

  if (followingNode.type === 'CatchClause') {
    addBlockOrNotComment(followingNode.body, comment)
    return true
  }

  return false
}

function addBlockStatementFirstComment(node, comment) {
  const body = node.body.filter(n => n.type !== 'EmptyStatement')
  if (body.length === 0) {
    addDanglingComment(node, comment)
  } else {
    addLeadingComment(body[0], comment)
  }
}

function addBlockOrNotComment(node, comment) {
  if (node.type === 'BlockStatement') {
    addBlockStatementFirstComment(node, comment)
  } else {
    addLeadingComment(node, comment)
  }
}

function handleLastFunctionArgComments(
  text,
  precedingNode,
  enclosingNode,
  followingNode,
  comment,
  options
) {
  if (
    precedingNode &&
    (precedingNode.type === 'Identifier' ||
      precedingNode.type === 'AssignmentPattern') &&
    enclosingNode &&
    (isFunction(enclosingNode) || enclosingNode.type === 'ClassMethod') &&
    getNextNonSpaceNonCommentCharacter(text, comment, options.locEnd) === ')'
  ) {
    addTrailingComment(precedingNode, comment)
    return true
  }
  return false
}

function handleCommentInEmptyParens(text, enclosingNode, comment, options) {
  if (
    getNextNonSpaceNonCommentCharacter(text, comment, options.locEnd) !== ')'
  ) {
    return false
  }

  if (
    enclosingNode &&
    (isFunction(enclosingNode) || enclosingNode.type === 'ClassMethod') &&
    enclosingNode.params.length === 0
  ) {
    addDanglingComment(enclosingNode, comment)
    return true
  }
  return false
}

function handleOnlyComments(enclosingNode, comment, isLastComment) {
  if (
    enclosingNode &&
    enclosingNode.type === 'Program' &&
    enclosingNode.body.length === 0 &&
    enclosingNode.directives &&
    enclosingNode.directives.length === 0
  ) {
    if (isLastComment) {
      addDanglingComment(enclosingNode, comment)
    } else {
      addLeadingComment(enclosingNode, comment)
    }
    return true
  }
  return false
}

function handleFunctionNameComments(text, enclosingNode, comment, options) {
  const nextChar = getNextNonSpaceNonCommentCharacter(
    text,
    comment,
    options.locEnd
  )
  if (nextChar !== '(' && nextChar !== '-') {
    return false
  }

  if (
    enclosingNode &&
    (isFunction(enclosingNode) || enclosingNode.type === 'ClassMethod')
  ) {
    // addTrailingComment(precedingNode, comment)
    addDanglingComment(enclosingNode, comment)
    return true
  }
}

function handleAssignmentPatternComments(enclosingNode, comment) {
  if (enclosingNode && enclosingNode.type === 'AssignmentPattern') {
    addLeadingComment(enclosingNode, comment)
    return true
  }
  return false
}

function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
  if (
    enclosingNode &&
    enclosingNode.type === 'CallExpression' &&
    precedingNode &&
    enclosingNode.callee === precedingNode &&
    enclosingNode.arguments.length > 0
  ) {
    addLeadingComment(enclosingNode.arguments[0], comment)
    return true
  }
  return false
}

function isBlockComment(comment) {
  return comment.type === 'CommentBlock'
}

module.exports = {
  handleOwnLineComment,
  handleEndOfLineComment,
  handleRemainingComment,
  isBlockComment,
}


'use strict'

// function parse(text, parsers, opts) {
function parse(text) {
  // Inline the require to avoid the module if we don't use it
  const coffeescript = require('coffeescript')

  const coffeescriptOptions = {
    ast: true,
  }

  const ast = coffeescript.compile(text, coffeescriptOptions)

  // TODO: Any cleanup of the AST?

  return ast
}

module.exports = parse


/**
 * Simple draggable component
 *
 * @module draggy
 */

//work with css
const css = require('mucss/css');
const parseCSSValue = require('mucss/parse-value');
const selection = require('mucss/selection');
const offsets = require('mucss/offset');
const getTranslate = require('mucss/translate');
const intersect = require('intersects');
const isFixed = require('mucss/is-fixed');

//events
const on = require('emmy/on');
const off = require('emmy/off');
const emit = require('emmy/emit');
const Emitter = require('events');
const getClientX = require('get-client-xy').x;
const getClientY = require('get-client-xy').y;

//utils
const isArray = require('mutype/is-array');
const isNumber = require('mutype/is-number');
const isString = require('mutype/is-string');
const isFn = require('mutype/is-fn');
const defineState = require('define-state');
const extend = require('xtend/mutable');
const round = require('mumath/round');
const between = require('mumath/clamp');
const loop = require('mumath/mod');
const getUid = require('get-uid');
const inherits =  require('inherits');

const win = window, doc = document, root = doc.documentElement;

/**
 * Draggable controllers associated with elements.
 *
 * Storing them on elements is
 * - leak-prone,
 * - pollutes element’s namespace,
 * - requires some artificial key to store,
 * - unable to retrieve controller easily.
 *
 * That is why weakmap.
 */
const draggableCache = Draggable.cache = new WeakMap;

/**
 * Make an element draggable.
 *
 * @constructor
 *
 * @param {HTMLElement} target An element whether in/out of DOM
 * @param {Object} options An draggable options
 *
 * @return {HTMLElement} Target element
 */
function Draggable(target, options) {
	if (!(this instanceof Draggable)) {
		return new Draggable(target, options);
	}

	var that = this;

	//ignore existing instance
	var instance = draggableCache.get(target);
	if (instance) {
		instance.state = 'reset';

		//take over options
		extend(instance, options);

		instance.update();

		return instance;
	}

	else {
		//get unique id for instance
		//needed to track event binders
		that.id = getUid();
		that._ns = '.draggy_' + that.id;

		//save element passed
		that.element = target;

		draggableCache.set(target, that);
	}

	//define state behaviour
	defineState(that, 'state', that.state);

	//preset handles
	that.currentHandles = [];

	//take over options
	extend(that, options);

	//define handle
	if (that.handle === undefined) {
		that.handle = that.element;
	}

	//setup droppable
	if (that.droppable) {
		that.initDroppable();
	}

	//try to calc out basic limits
	that.update();

	//go to initial state
	that.state = 'idle';
}

/** Inherit draggable from Emitter */
inherits(Draggable, Emitter);

//enable css3 by default
Draggable.prototype.css3 = true;

//both axes by default
Draggable.prototype.axis = null;

/** Init droppable "plugin" */
Draggable.prototype.initDroppable = function () {
	var that = this;

	on(that, 'dragstart', function () {
		var that = this;
		that.dropTargets = q(that.droppable);
	});

	on(that, 'drag', function () {
		var that = this;

		if (!that.dropTargets) {
			return;
		}

		var thatRect = offsets(that.element);

		that.dropTargets.forEach(function (dropTarget) {
			var targetRect = offsets(dropTarget);

			if (intersect(thatRect, targetRect, that.droppableTolerance)) {
				if (that.droppableClass) {
					dropTarget.classList.add(that.droppableClass);
				}
				if (!that.dropTarget) {
					that.dropTarget = dropTarget;

					emit(that, 'dragover', dropTarget);
					emit(dropTarget, 'dragover', that);
				}
			}
			else {
				if (that.dropTarget) {
					emit(that, 'dragout', dropTarget);
					emit(dropTarget, 'dragout', that);

					that.dropTarget = null;
				}
				if (that.droppableClass) {
					dropTarget.classList.remove(that.droppableClass);
				}
			}
		});
	});

	on(that, 'dragend', function () {
		var that = this;

		//emit drop, if any
		if (that.dropTarget) {
			emit(that.dropTarget, 'drop', that);
			emit(that, 'drop', that.dropTarget);
			that.dropTarget.classList.remove(that.droppableClass);
			that.dropTarget = null;
		}
	});
};

/**
 * Draggable behaviour
 * @enum {string}
 * @default is 'idle'
 */
Draggable.prototype.state = {
	//idle
	_: {
		before: function () {
			var that = this;

			that.element.classList.add('draggy-idle');

			//emit drag evts on element
			emit(that.element, 'idle', null, true);
			that.emit('idle');

			//reset keys
			that.ctrlKey = false;
			that.shiftKey = false;
			that.metaKey = false;
			that.altKey = false;

			//reset movement params
			that.movementX = 0;
			that.movementY = 0;
			that.deltaX = 0;
			that.deltaY = 0;

			on(doc, 'mousedown' + that._ns + ' touchstart' + that._ns, function (e) {
				//ignore non-draggy events
				if (!e.draggies) {
					return;
				}

				//ignore dragstart for not registered draggies
				if (e.draggies.indexOf(that) < 0) {
					return;
				}

				//if target is focused - ignore drag
				//FIXME: detect focused by whitelist of tags, name supposition may be wrong (idk, form elements have names, so likely to be focused by click)
				if (e.target.name !== undefined) {
					return;
				}

				//multitouch has multiple starts
				that.setTouch(e);

				//update movement params
				that.update(e);

				//go to threshold state
				that.state = 'threshold';
			});
		},
		after: function () {
			var that = this;

			that.element.classList.remove('draggy-idle');

			off(doc, that._ns);

			//set up tracking
			if (that.release) {
				that._trackingInterval = setInterval(function (e) {
					var now = Date.now();
					var elapsed = now - that.timestamp;

					//get delta movement since the last track
					var dX = that.prevX - that.frame[0];
					var dY = that.prevY - that.frame[1];
					that.frame[0] = that.prevX;
					that.frame[1] = that.prevY;

					var delta = Math.sqrt(dX * dX + dY * dY);

					//get speed as average of prev and current (prevent div by zero)
					var v = Math.min(that.velocity * delta / (1 + elapsed), that.maxSpeed);
					that.speed = 0.8 * v + 0.2 * that.speed;

					//get new angle as a last diff
					//NOTE: vector average isn’t the same as speed scalar average
					that.angle = Math.atan2(dY, dX);

					that.emit('track');

					return that;
				}, that.framerate);
			}
		}
	},

	threshold: {
		before: function () {
			var that = this;

			//ignore threshold state, if threshold is none
			if (isZeroArray(that.threshold)) {
				that.state = 'drag';
				return;
			}

			that.element.classList.add('draggy-threshold');

			//emit drag evts on element
			that.emit('threshold');
			emit(that.element, 'threshold');

			//listen to doc movement
			on(doc, 'touchmove' + that._ns + ' mousemove' + that._ns, function (e) {
				e.preventDefault();

				//compare movement to the threshold
				var clientX = getClientX(e, that.touchIdx);
				var clientY = getClientY(e, that.touchIdx);
				var difX = that.prevMouseX - clientX;
				var difY = that.prevMouseY - clientY;

				if (difX < that.threshold[0] || difX > that.threshold[2] || difY < that.threshold[1] || difY > that.threshold[3]) {
					that.update(e);
					that.state = 'drag';
				}
			});
			on(doc, 'mouseup' + that._ns + ' touchend' + that._ns + '', function (e) {
				e.preventDefault();

				//forget touches
				that.resetTouch();

				that.state = 'idle';
			});
		},

		after: function () {
			var that = this;

			that.element.classList.remove('draggy-threshold');

			off(doc, that._ns);
		}
	},

	drag: {
		before: function () {
			var that = this;

			//reduce dragging clutter
			selection.disable(root);

			that.element.classList.add('draggy-drag');

			//emit drag evts on element
			that.emit('dragstart');
			emit(that.element, 'dragstart', null, true);

			//emit drag events on that
			that.emit('drag');
			emit(that.element, 'drag', null, true);

			//stop drag on leave
			on(doc, 'touchend' + that._ns + ' mouseup' + that._ns + ' mouseleave' + that._ns, function (e) {
				e.preventDefault();

				//forget touches - dragend is called once
				that.resetTouch();

				//manage release movement
				if (that.speed > 1) {
					that.state = 'release';
				}

				else {
					that.state = 'idle';
				}
			});

			//move via transform
			on(doc, 'touchmove' + that._ns + ' mousemove' + that._ns, function (e) {
				that.drag(e);
			});
		},

		after: function () {
			var that = this;

			//enable document interactivity
			selection.enable(root);

			that.element.classList.remove('draggy-drag');

			//emit dragend on element, this
			that.emit('dragend');
			emit(that.element, 'dragend', null, true);

			//unbind drag events
			off(doc, that._ns);

			clearInterval(that._trackingInterval);
		}
	},

	release: {
		before: function () {
			var that = this;

			that.element.classList.add('draggy-release');

			//enter animation mode
			clearTimeout(that._animateTimeout);

			//set proper transition
			css(that.element, {
				'transition': (that.releaseDuration) + 'ms ease-out ' + (that.css3 ? 'transform' : 'position')
			});

			//plan leaving anim mode
			that._animateTimeout = setTimeout(function () {
				that.state = 'idle';
			}, that.releaseDuration);

			//calc target point & animate to it
			that.move(
				that.prevX + that.speed * Math.cos(that.angle),
				that.prevY + that.speed * Math.sin(that.angle)
			);

			that.speed = 0;
			that.emit('track');
		},

		after: function () {
			var that = this;

			that.element.classList.remove('draggy-release');

			css(this.element, {
				'transition': null
			});
		}
	},

	reset: function () {
		var that = this;

		that.currentHandles.forEach(function (handle) {
			off(handle, that._ns);
		});

		clearTimeout(that._animateTimeout);

		off(doc, that._ns);
		off(that.element, that._ns);

		return '_';
	}
};

/** Drag handler. Needed to provide drag movement emulation via API */
Draggable.prototype.drag = function (e) {
	var that = this;

	e.preventDefault();

	var mouseX = getClientX(e, that.touchIdx),
		mouseY = getClientY(e, that.touchIdx);

	//calc mouse movement diff
	var diffMouseX = mouseX - that.prevMouseX,
		diffMouseY = mouseY - that.prevMouseY;

	//absolute mouse coordinate
	var mouseAbsX = mouseX,
		mouseAbsY = mouseY;

	//if we are not fixed, our absolute position is relative to the doc
	if (!that._isFixed) {
		mouseAbsX += win.pageXOffset;
		mouseAbsY += win.pageYOffset;
	}

	//calc sniper offset, if any
	if (e.ctrlKey || e.metaKey) {
		that.sniperOffsetX += diffMouseX * that.sniperSlowdown;
		that.sniperOffsetY += diffMouseY * that.sniperSlowdown;
	}

	//save refs to the meta keys
	that.ctrlKey = e.ctrlKey;
	that.shiftKey = e.shiftKey;
	that.metaKey = e.metaKey;
	that.altKey = e.altKey;

	//calc movement x and y
	//take absolute placing as it is the only reliable way (2x proved)
	var x = (mouseAbsX - that.initOffsetX) - that.innerOffsetX - that.sniperOffsetX,
		y = (mouseAbsY - that.initOffsetY) - that.innerOffsetY - that.sniperOffsetY;

	//move element
	that.move(x, y);

	//save prevClientXY for calculating diff
	that.prevMouseX = mouseX;
	that.prevMouseY = mouseY;

	//emit drag
	that.emit('drag');
	emit(that.element, 'drag', null, true);
};

/** Current number of draggable touches */
var touches = 0;

/** Manage touches */
Draggable.prototype.setTouch = function (e) {
	if (!e.touches || this.isTouched()) return this;

	//current touch index
	this.touchIdx = touches;
	touches++;

	return this;
};
Draggable.prototype.resetTouch = function () {
	touches = 0;
	this.touchIdx = null;

	return this;
};
Draggable.prototype.isTouched = function () {
	return this.touchIdx !== null;
};

/** Index to fetch touch number from event */
Draggable.prototype.touchIdx = null;

/**
 * Update movement limits.
 * Refresh that.withinOffsets and that.limits.
 */
Draggable.prototype.update = function (e) {
	var that = this;

	that._isFixed = isFixed(that.element);

	//enforce abs position
	if (!that.css3) {
		css(this.element, 'position', 'absolute');
	}

	//update handles
	that.currentHandles.forEach(function (handle) {
		off(handle, that._ns);
	});

	var cancelEls = q(that.cancel);

	that.currentHandles = q(that.handle);

	that.currentHandles.forEach(function (handle) {
		on(handle, 'mousedown' + that._ns + ' touchstart' + that._ns, function (e) {
			//mark event as belonging to the draggy
			if (!e.draggies) {
				e.draggies = [];
			}

			//ignore draggies containing other draggies
			if (e.draggies.some(function (draggy) {
				return that.element.contains(draggy.element);
			})) {
				return;
			}
			//ignore events happened within cancelEls
			if (cancelEls.some(function (cancelEl) {
				return cancelEl.contains(e.target);
			})) {
				return;
			}

			//register draggy
			e.draggies.push(that);
		});
	});

	//update limits
	that.updateLimits();

	//preset inner offsets
	that.innerOffsetX = that.pin[0];
	that.innerOffsetY = that.pin[1];

	var thatClientRect = that.element.getBoundingClientRect();

	//if event passed - update acc to event
	if (e) {
		//take last mouse position from the event
		that.prevMouseX = getClientX(e, that.touchIdx);
		that.prevMouseY = getClientY(e, that.touchIdx);

		//if mouse is within the element - take offset normally as rel displacement
		that.innerOffsetX = -thatClientRect.left + getClientX(e, that.touchIdx);
		that.innerOffsetY = -thatClientRect.top + getClientY(e, that.touchIdx);
	}
	//if no event - suppose pin-centered event
	else {
		//take mouse position & inner offset as center of pin
		var pinX = (that.pin[0] + that.pin[2] ) * 0.5;
		var pinY = (that.pin[1] + that.pin[3] ) * 0.5;
		that.prevMouseX = thatClientRect.left + pinX;
		that.prevMouseY = thatClientRect.top + pinY;
		that.innerOffsetX = pinX;
		that.innerOffsetY = pinY;
	}

	//set initial kinetic props
	that.speed = 0;
	that.amplitude = 0;
	that.angle = 0;
	that.timestamp = +new Date();
	that.frame = [that.prevX, that.prevY];

	//set sniper offset
	that.sniperOffsetX = 0;
	that.sniperOffsetY = 0;
};

/**
 * Update limits only from current position
 */
Draggable.prototype.updateLimits = function () {
	var that = this;

	//initial translation offsets
	var initXY = that.getCoords();

	//calc initial coords
	that.prevX = initXY[0];
	that.prevY = initXY[1];
	that.initX = initXY[0];
	that.initY = initXY[1];

	//container rect might be outside the vp, so calc absolute offsets
	//zero-position offsets, with translation(0,0)
	var thatOffsets = offsets(that.element);

	that.initOffsetX = thatOffsets.left - that.prevX;
	that.initOffsetY = thatOffsets.top - that.prevY;
	that.offsets = thatOffsets;

	//handle parent case
	var within = that.within;
	if (that.within === 'parent' || that.within === true) {
		within = that.element.parentNode;
	}
	within = within || doc;

	//absolute offsets of a container
	var withinOffsets = offsets(within);

	if (within === win && that._isFixed) {
		withinOffsets.top -= win.pageYOffset;
		withinOffsets.left -= win.pageXOffset;
		withinOffsets.bottom -= win.pageYOffset;
		withinOffsets.right -= win.pageXOffset;
	}
	that.withinOffsets = withinOffsets;

	//calculate movement limits - pin width might be wider than constraints
	that.overflowX = that.pin.width - withinOffsets.width;
	that.overflowY = that.pin.height - withinOffsets.height;

	that.limits = {
		left: withinOffsets.left - that.initOffsetX - that.pin[0] - (that.overflowX < 0 ? 0 : that.overflowX),
		top: withinOffsets.top - that.initOffsetY - that.pin[1] - (that.overflowY < 0 ? 0 : that.overflowY),
		right: that.overflowX > 0 ? 0 : withinOffsets.right - that.initOffsetX - that.pin[2],
		bottom: (that.overflowY > 0 ? 0 : withinOffsets.bottom - that.initOffsetY - that.pin[3])
	};
};

/**
 * Update info regarding of movement
 */
Draggable.prototype.updateInfo = function (x, y) {
	var that = this;

	//provide delta from prev state
	that.deltaX = x - that.prevX;
	that.deltaY = y - that.prevY;

	//save prev coords to use as a start point next time
	that.prevX = x;
	that.prevY = y;

	//provide movement delta from initial state
	that.movementX = x - that.initX;
	that.movementY = y - that.initY;

}

/**
 * Way of placement:
 * - css3 === false (slower but more precise and cross-browser)
 * - css3 === true (faster but may cause blurs on linux systems)
 */
Draggable.prototype.getCoords = function () {
	if (!this.css3) {
		// return [this.element.offsetLeft, this.element.offsetTop];
		return [parseCSSValue(css(this.element,'left')), parseCSSValue(css(this.element, 'top'))];
	}
	else {
		return getTranslate(this.element).slice(0, 2) || [0,0];
	}
};
Draggable.prototype.setCoords = function (x, y) {
	if (this.css3) {
		if (x == null) x = this.prevX;
		if (y == null) y = this.prevY;

		x = round(x, this.precision);
		y = round(y, this.precision);

		css(this.element, 'transform', ['translate3d(', x, 'px,', y, 'px, 0)'].join(''));

		this.updateInfo(x, y);
	}
	else {
		if (x == null) x = this.prevX;
		if (y == null) y = this.prevY;

		x = round(x, this.precision);
		y = round(y, this.precision);

		css(this.element, {
			left: x,
			top: y
		});

		//update movement info
		this.updateInfo(x, y);
	}
};

/**
 * Restricting container
 * @type {Element|object}
 * @default doc.documentElement
 */
Draggable.prototype.within = doc;

/** Handle to drag */
Draggable.prototype.handle;

Object.defineProperties(Draggable.prototype, {
	/**
	 * Which area of draggable should not be outside the restriction area.
	 * @type {(Array|number)}
	 * @default [0,0,this.element.offsetWidth, this.element.offsetHeight]
	 */
	pin: {
		set: function (value) {
			if (isArray(value)) {
				if (value.length === 2) {
					this._pin = [value[0], value[1], value[0], value[1]];
				} else if (value.length === 4) {
					this._pin = value;
				}
			}

			else if (isNumber(value)) {
				this._pin = [value, value, value, value];
			}

			else {
				this._pin = value;
			}

			//calc pin params
			this._pin.width = this._pin[2] - this._pin[0];
			this._pin.height = this._pin[3] - this._pin[1];
		},

		get: function () {
			if (this._pin) return this._pin;

			//returning autocalculated pin, if private pin is none
			var pin = [0,0, this.offsets.width, this.offsets.height];
			pin.width = this.offsets.width;
			pin.height = this.offsets.height;
			return pin;
		}
	},

	/** Avoid initial mousemove */
	threshold: {
		set: function (val) {
			if (isNumber(val)) {
				this._threshold = [-val*0.5, -val*0.5, val*0.5, val*0.5];
			} else if (val.length === 2) {
				//Array(w,h)
				this._threshold = [-val[0]*0.5, -val[1]*0.5, val[0]*0.5, val[1]*0.5];
			} else if (val.length === 4) {
				//Array(x1,y1,x2,y2)
				this._threshold = val;
			} else if (isFn(val)) {
				//custom val funciton
				this._threshold = val();
			} else {
				this._threshold = [0,0,0,0];
			}
		},

		get: function () {
			return this._threshold || [0,0,0,0];
		}
	}
});

/**
 * For how long to release movement
 *
 * @type {(number|false)}
 * @default false
 * @todo
 */
Draggable.prototype.release = false;
Draggable.prototype.releaseDuration = 500;
Draggable.prototype.velocity = 1000;
Draggable.prototype.maxSpeed = 250;
Draggable.prototype.framerate = 50;

/** To what extent round position */
Draggable.prototype.precision = 1;

/** Droppable params */
Draggable.prototype.droppable = null;
Draggable.prototype.droppableTolerance = 0.5;
Draggable.prototype.droppableClass = null;

/** Slow down movement by pressing ctrl/cmd */
Draggable.prototype.sniper = true;

/** How much to slow sniper drag */
Draggable.prototype.sniperSlowdown = .85;

/**
 * Restrict movement by axis
 *
 * @default undefined
 * @enum {string}
 */
Draggable.prototype.move = function (x, y) {
	if (this.axis === 'x') {
		if (x == null) x = this.prevX;
		if (y == null) y = this.prevY;

		var limits = this.limits;

		if (this.repeat) {
			var w = (limits.right - limits.left);
			var oX = - this.initOffsetX + this.withinOffsets.left - this.pin[0] - Math.max(0, this.overflowX);
			x = loop(x - oX, w) + oX;
		} else {
			x = between(x, limits.left, limits.right);
		}

		this.setCoords(x);
	}
	else if (this.axis === 'y') {
		if (x == null) x = this.prevX;
		if (y == null) y = this.prevY;

		var limits = this.limits;

		if (this.repeat) {
			var h = (limits.bottom - limits.top);
			var oY = - this.initOffsetY + this.withinOffsets.top - this.pin[1] - Math.max(0, this.overflowY);
			y = loop(y - oY, h) + oY;
		} else {
			y = between(y, limits.top, limits.bottom);
		}

		this.setCoords(null, y);
	}
	else {
		if (x == null) x = this.prevX;
		if (y == null) y = this.prevY;

		var limits = this.limits;

		if (this.repeat) {
			var w = (limits.right - limits.left);
			var h = (limits.bottom - limits.top);
			var oX = - this.initOffsetX + this.withinOffsets.left - this.pin[0] - Math.max(0, this.overflowX);
			var oY = - this.initOffsetY + this.withinOffsets.top - this.pin[1] - Math.max(0, this.overflowY);
			if (this.repeat === 'x') {
				x = loop(x - oX, w) + oX;
			}
			else if (this.repeat === 'y') {
				y = loop(y - oY, h) + oY;
			}
			else {
				x = loop(x - oX, w) + oX;
				y = loop(y - oY, h) + oY;
			}
		}

		x = between(x, limits.left, limits.right);
		y = between(y, limits.top, limits.bottom);

		this.setCoords(x, y);
	}
};

/** Repeat movement by one of axises */
Draggable.prototype.repeat = false;

/** Check whether arr is filled with zeros */
function isZeroArray(arr) {
	if (!arr[0] && !arr[1] && !arr[2] && !arr[3]) return true;
}

/** Clean all memory-related things */
Draggable.prototype.destroy = function () {
	var that = this;

	that.currentHandles.forEach(function (handle) {
		off(handle, that._ns);
	});

	that.state = 'destroy';

	clearTimeout(that._animateTimeout);

	off(doc, that._ns);
	off(that.element, that._ns);

	that.element = null;
	that.within = null;
};

//little helpers

function q (str) {
	if (Array.isArray(str)) {
		return str.map(q).reduce(function(prev, curr) { return prev.concat(curr); }, [] );
	}
	else if (str instanceof HTMLElement) {
		return [str];
	}
	else {
		return [].slice.call(document.querySelectorAll(str));
	}
}

module.exports = Draggable;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

function Functor(name, args) {
  let _name = name;
  let _args = args;
  let _argsCount = 0;
  let _variableHash = null;

  if (typeof _args === 'undefined') {
    _args = [];
  } else {
    _argsCount = args.length;
  }

  this.getName = function getName() {
    return _name;
  };

  this.getId = function getId() {
    return _name + '/' + _argsCount;
  };

  this.evaluate = function evaluate() {
    return this.toString();
  };

  this.getGoal = function getGoal() {
    return this;
  };

  this.getArgumentCount = function getArgumentCount() {
    return _argsCount;
  };

  this.getVariables = function getVariables() {
    return Object.keys(this.getVariableHash());
  };

  this.getVariableHash = function getVariableHash(existingHash) {
    let hash = existingHash;
    if (_variableHash !== null) {
      if (hash === undefined) {
        return _variableHash;
      }
      Object.keys(_variableHash).forEach((v) => {
        hash[v] = true;
      });
      return hash;
    }

    if (hash === undefined) {
      hash = {};
    }
    let storedHash = {};

    _args.forEach((arg) => {
      arg.getVariableHash(hash);
      arg.getVariableHash(storedHash);
    });

    _variableHash = storedHash;
    return hash;
  };

  this.isGround = function isGround() {
    let result = true;

    for (let i = 0; i < _argsCount; i += 1) {
      let arg = _args[i];
      if (!arg.isGround()) {
        result = false;
        break;
      }
    }

    return result;
  };

  this.getArguments = function getArguments() {
    // content of _args is immutable
    return _args.concat();
  };

  this.substitute = function substitute(theta) {
    let newArgs = _args.map((arg) => {
      return arg.substitute(theta);
    });
    return new Functor(_name, newArgs);
  };

  this.toString = function toString() {
    let result = _name;
    if (_argsCount > 0) {
      result += '(';
      for (let i = 0; i < _argsCount; i += 1) {
        result += _args[i].toString();
        if (i < _argsCount - 1) {
          result += ', ';
        }
      }
      result += ')';
    }
    return result;
  };
}

module.exports = Functor;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const lpsRequire = require('../lpsRequire');
const LiteralTreeMap = lpsRequire('engine/LiteralTreeMap');

/**
 * Maps conjunctions to a value.
 * @constructor
 */
function ConjunctionMap() {
  let _conjunctions = [];

  /**
   * Add a mapping of a conjunction to a value to this map.
   * @param {Array} conjunction The conjunction to map the value
   * @param {any} value       The value to be mapped
   */
  this.add = function add(conjunction, value) {
    if (value === undefined) {
      return;
    }
    let map = new LiteralTreeMap();
    conjunction.forEach((conjunct) => {
      map.add(conjunct);
    });
    _conjunctions.push([map.size(), map, value]);
  };

  /**
   * Get the value of a conjunction.
   * @return {any} Return the value of the conjunction mapped if it exists. Otherwise if the
   *    conjunction is not mapped then undefined would be returned instead.
   */
  this.get = function get(conjunction) {
    let result;
    for (let i = 0; i < _conjunctions.length; i += 1) {
      let pair = _conjunctions[i];
      let containMismatch = false;
      if (conjunction.length !== pair[0]) {
        continue;
      }
      for (let j = 0; j < conjunction.length; j += 1) {
        let conjunct = conjunction[j];
        if (!pair[1].contains(conjunct)) {
          containMismatch = true;
          break;
        }
      }
      if (!containMismatch) {
        result = pair[2];
        break;
      }
    }
    return result;
  };
}

module.exports = ConjunctionMap;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

function Profiler() {
  let _values = {};

  this.reset = function reset(key) {
    _values[key] = 0;
  };

  this.increment = function increment(key) {
    if (_values[key] === undefined
        || typeof _values[key] !== 'number') {
      return;
    }
    _values[key] += 1;
  };

  this.increaseBy = function increaseBy(key, value) {
    if (_values[key] === undefined
        || typeof _values[key] !== 'number') {
      return;
    }
    _values[key] += value;
  };

  this.set = function set(key, value) {
    _values[key] = value;
  };

  this.add = function add(key, value) {
    if (_values[key] === undefined
        || !Array.isArray(_values[key])) {
      return;
    }
    _values[key].push(value);
  };

  this.get = function get(key) {
    return _values[key];
  };
}

module.exports = Profiler;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const lpsRequire = require('../lpsRequire');
const Variable = lpsRequire('engine/Variable');
const Functor = lpsRequire('engine/Functor');
const Value = lpsRequire('engine/Value');
const Timable = lpsRequire('engine/Timable');

const comparisonTerms = [
  '</2',
  '>/2',
  '<=/2',
  '>=/2',
  '=/2'
];

/**
 * Sort timables into set of earlyConjuncts and laterConjuncts
 * @param  {Array} conjunction The conjunction. Must be an array of functors or timables
 * @param  {number} forTime     A positive integer of the time to determine earlyConjuncts
 * @return {Array}             Return a pair of early conjuncts and later conjuncts
 */
function sortTimables(conjunction, forTime) {
  let earlyConjuncts = [];
  let laterConjuncts = [];

  let dependentTimeVariables = {};

  // determine the time dependent variables
  for (let k = 0; k < conjunction.length; k += 1) {
    let conjunct = conjunction[k];

    if (!(conjunct instanceof Timable)) {
      // skip over non-Timables
      if (conjunct instanceof Functor
          && comparisonTerms.indexOf(conjunct.getId()) !== -1) {
        conjunct.getVariables()
          .forEach((v) => {
            dependentTimeVariables[v] = true;
          });
      }
      continue;
    }

    let conjunctStartTime = conjunct.getStartTime();
    let conjunctEndTime = conjunct.getEndTime();

    if (conjunctEndTime instanceof Variable) {
      let endTimeName = conjunctEndTime.evaluate();
      if (conjunctStartTime instanceof Value
          || (conjunctStartTime instanceof Variable
            && conjunctStartTime.evaluate() !== endTimeName)) {
        // different start/end times
        dependentTimeVariables[endTimeName] = true;
      }
    }
  }

  // sort between early and later
  for (let k = 0; k < conjunction.length; k += 1) {
    let conjunct = conjunction[k];

    if (!(conjunct instanceof Timable)) {
      if (laterConjuncts.length > 0) {
        laterConjuncts.push(conjunct);
        continue;
      }
      earlyConjuncts.push(conjunct);
      continue;
    }

    if (!conjunct.isInRange(forTime)) {
      laterConjuncts.push(conjunct);
      continue;
    }

    let conjunctStartTime = conjunct.getStartTime();

    if (conjunctStartTime instanceof Variable) {
      if (dependentTimeVariables[conjunctStartTime.evaluate()] !== undefined) {
        laterConjuncts.push(conjunct);
        continue;
      }
    }

    earlyConjuncts.push(conjunct);
  }

  return [
    earlyConjuncts,
    laterConjuncts
  ];
}

module.exports = sortTimables;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

function AstNode(type, token) {
  let _type = type;
  let _token = token;
  let _children = [];

  this.getType = function getType() {
    return _type;
  };

  this.getToken = function getToken() {
    return _token;
  };

  this.getChildren = function getChildren() {
    return _children;
  };

  this.setToken = function setToken(t) {
    _token = t;
  };

  this.isLeaf = function isLeaf() {
    return _children.length === 0;
  };

  this.addChild = function addChild(childNode) {
    _children.push(childNode);
  };

  this.print = function print(nArg) {
    let n = nArg;
    if (!n) {
      n = 0;
    }
    console.log(' '.repeat(n) + String(_type) + (_token ? (': ' + _token.value) : ''));
    n += 1;
    _children.forEach((child) => {
      child.print(n);
    });
  };
}

module.exports = AstNode;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const path = require('path');

const lpsRequire = require('./lpsRequire');
const ProgramFactory = lpsRequire('parser/ProgramFactory');
const Engine = lpsRequire('engine/Engine');
const Value = lpsRequire('engine/Value');
const Variable = lpsRequire('engine/Variable');
const Functor = lpsRequire('engine/Functor');
const Tester = lpsRequire('engine/tester/Tester');
const List = lpsRequire('engine/List');
const Program = lpsRequire('engine/Program');
const stringLiterals = lpsRequire('utility/strings');

const programArgsPredicate = ProgramFactory.literal('lpsArgs(List)');

/**
 * The LPS type
 * @constructor
 */
function LPS() {

}

/**
 * Parse a string of a single literal into its appropriate type for lps.js
 * @param  {string} str The literal in string representation
 * @return {Functor}    Returns the functor representation
 */
LPS.literal = function literal(str) {
  return ProgramFactory.literal(str);
};

/**
 * Parse a string of literal conjunction into its appropriate type for lps.js
 * @param  {string} str The conjunction in string representation
 * @return {Array<Functor>}    Returns the array of functors.
 */
LPS.literalSet = function literalSet(str) {
  return ProgramFactory.literalSet(str);
};

/**
 * Construct the program arguments predicate
 * @private
 * @param  {array} programArgs The array of program arguments
 * @return {Functor}             The constructed predicate that contains the program arguments
 */
const buildProgramArgsPredicate = function (programArgs) {
  let _programArgs = programArgs;
  if (programArgs === undefined) {
    _programArgs = [];
  }
  // map to values
  _programArgs = _programArgs.map(arg => new Value(arg));
  let argsList = new List(_programArgs);
  let theta = {
    List: argsList
  };
  return programArgsPredicate.substitute(theta);
};

/**
 * Create a function to update a given program with the given program arguments
 * @param  {array} programArgs Array of program arguments
 * @return {[type]}             [description]
 */
const createProgramArgsUpdaterFunc = function createProgramArgsUpdaterFunc(programArgs) {
  return (program) => {
    let programArgsFact = buildProgramArgsPredicate(programArgs);
    program.getFacts().add(programArgsFact);
    return Promise.resolve(program);
  };
};

/**
 * Create an Engine instance from a string representation of a LPS program without initialisation.
 * @param  {string} source      The LPS program source code
 * @param  {array} programArgs  Optional, the array of program arguments to pass to the LPS program.
 *                              Defaults to an empty list
 * @return {Promise}            Returns a promise that when fulfilled, provides the Engine instance
 *                              that was created.
 */
LPS.createFromString = function createFromString(source, programArgs) {
  return ProgramFactory.fromString(source)
    .then(createProgramArgsUpdaterFunc(programArgs))
    .then((program) => {
      let engine = new Engine(program);
      return Promise.resolve(engine);
    });
};

/**
 * Create and initialise an Engine instance from a string representation of a LPS program
 * @param  {string} source      The LPS program source code
 * @param  {array} programArgs  Optional, the array of program arguments to pass to the LPS program.
 *                              Defaults to an empty list
 * @return {Promise}            Returns a promise that when fulfilled, provides the Engine instance
 *                              that was created.
 */
LPS.loadString = function loadString(source, programArgs) {
  return LPS.createFromString(source, programArgs)
    .then((engine) => {
      return engine.load();
    });
};

/**
 * Create an Engine instance from a LPS program file without initialisation.
 * @param  {string} fileArg     Pathname to the LPS program file on the file system.
 * @param  {array} programArgs  Optional, the array of program arguments to pass to the LPS program.
 *                              Defaults to an empty list
 * @return {Promise}            Returns a promise that when fulfilled, provides the Engine instance
 *                              that was created.
 */
LPS.createFromFile = function createFromFile(fileArg, programArgs) {
  if (process.browser) {
    return Promise.reject(new Error(stringLiterals('browserContext.loadProgramFromFile')));
  }
  let file = fileArg;
  file = path.resolve(file);

  return ProgramFactory.fromFile(file)
    .then(createProgramArgsUpdaterFunc(programArgs))
    .then((program) => {
      program.setWorkingDirectory(path.dirname(file));
      let engine = new Engine(program);
      return Promise.resolve(engine);
    });
};

/**
 * Create and initialise an Engine instance from a LPS program file.
 * @param  {string} fileArg     Pathname to the LPS program file on the file system.
 * @param  {array} programArgs  Optional, the array of program arguments to pass to the LPS program.
 *                              Defaults to an empty list
 * @return {Promise}            Returns a promise that when fulfilled, provides the Engine instance
 *                              that was created.
 */
LPS.loadFile = function loadFile(fileArg, programArgs) {
  return LPS.createFromFile(fileArg, programArgs)
    .then((engine) => {
      return engine.load();
    });
};

/**
 * The lps.js Value type
 * @type {Value}
 */
LPS.Value = Value;

/**
 * The lps.js Variable type
 * @type {Variable}
 */
LPS.Variable = Variable;

/**
 * The lps.js List type
 * @type {List}
 */
LPS.List = List;

/**
 * The lps.js Functor type
 * @type {Functor}
 */
LPS.Functor = Functor;

/**
 * The Tester class for access to testing API
 * @type {Tester}
 */
LPS.Tester = Tester;

/**
 * The lps.js Program data structure
 * @type {Program}
 */
LPS.Program = Program;

/**
 * The lps.js ProgramFactory API
 * @type {ProgramFactory}
 */
LPS.ProgramFactory = ProgramFactory;

module.exports = LPS;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const lpsRequire = require('../lpsRequire');
const Variable = lpsRequire('engine/Variable');

/**
 * A list data representation
 * @param       {Array} head The head of the list
 * @param       {List|Variable} [tail] The tail of the list. If not defined,
 *    an empty list is considered to be the tail.
 * @constructor
 */
function List(head, tail) {
  let _head = head;
  let _tail = tail;
  let _variableHash = null;

  if (tail === undefined) {
    // empty list for tail
    _tail = null;
  }

  /**
   * Get the head of the list
   * @return {Array} Return the array representing the head of the list
   */
  this.getHead = function getHead() {
    return _head.concat();
  };

  /**
   * Get the tail of the list
   * @return {Array|List|Variable} Return the tail representation of the list
   */
  this.getTail = function getTail() {
    return _tail;
  };

  /**
   * Determine if the term is ground
   * @return {Boolean} Return true if the term is ground, false otherwise.
   */
  this.isGround = function isGround() {
    if (_tail !== null) {
      return _tail.isGround();
    }

    for (let i = 0; i < _head.length; i += 1) {
      if (!_head[i].isGround()) {
        return false;
      }
    }

    return true;
  };

  /**
   * Get all unique variables that occur in this term
   * @return {Array} Return the array of unique variables occuring in this term
   */
  this.getVariables = function getVariables() {
    return Object.keys(this.getVariableHash());
  };

  this.getVariableHash = function getVariableHash(existingHash) {
    let hash = existingHash;
    if (_variableHash !== null) {
      if (hash === undefined) {
        return _variableHash;
      }
      Object.keys(_variableHash).forEach((v) => {
        hash[v] = true;
      });
      return hash;
    }

    // if we're writing into existing hash, don't store the hash
    if (hash === undefined) {
      hash = {};
    }
    let storedHash = {};

    const processArg = function processArg(arg) {
      arg.getVariableHash(storedHash);
      arg.getVariableHash(hash);
    };

    _head.forEach(processArg);

    if (_tail instanceof List) {
      processArg(_tail);
    } else if (_tail instanceof Variable) {
      storedHash[_tail.evaluate()] = true;
      hash[_tail.evaluate()] = true;
    }

    _variableHash = storedHash;
    return hash;
  };

  /**
   * Perform a substitution on this term.
   * @param  {Object} theta The substitution theta
   * @return {List}       Return the substituted list
   */
  this.substitute = function substitute(theta) {
    let newHead = head.map((element) => {
      return element.substitute(theta);
    });

    let newTail = _tail;
    if (newTail instanceof List || newTail instanceof Variable) {
      newTail = newTail.substitute(theta);
    }
    return new List(newHead, newTail);
  };

  /**
   * Create a flat representation of the list. If the tail of the list is a variable,
   * the empty list is assumed.
   * @return {Array} Return the flat array representation of the list
   */
  this.flatten = function flatten() {
    let result = [];
    if (_head.length > 0) {
      result = result.concat(_head);
      if (_tail instanceof List) {
        result = result.concat(_tail.flatten());
      }
    }
    return result;
  };

  /**
   * Determine if the list is empty
   * @return {Boolean} Return true if the list is empty, false otherwise.
   */
  this.isEmpty = function isEmpty() {
    return _head.length === 0
      && (_tail instanceof List && _tail.isEmpty());
  };

  /**
   * Create a string representation of the term
   * @return {string} Return the string representation of the term
   */
  this.toString = function toString() {
    let result = '';
    result += '[';
    for (let j = 0; j < _head.length; j += 1) {
      result += _head[j];
      if (j < _head.length - 1) {
        result += ', ';
      }
    }
    if (_tail !== null && (!(_tail instanceof List) || !_tail.isEmpty())) {
      result += '|' + _tail.toString();
    }
    result += ']';
    return result;
  };
}

module.exports = List;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const lpsRequire = require('../../lpsRequire');
const ProgramFactory = lpsRequire('parser/ProgramFactory');
const Value = lpsRequire('engine/Value');
const stringLiterals = lpsRequire('utility/strings');

const observeLiteral = ProgramFactory.literal('observe(O, ST, ET)');

// eslint-disable-next-line no-unused-vars
function observeProcessor(engine, program) {
  let result = engine.query(observeLiteral);
  result.forEach((r) => {
    if (r.theta.O === undefined
        || r.theta.ST === undefined
        || r.theta.ET === undefined) {
      // ignoring those undefined ones
      return;
    }
    let observation = r.theta.O;
    let startTime = r.theta.ST;
    let endTime = r.theta.ET;

    if (!(startTime instanceof Value)) {
      throw new Error(stringLiterals([
        'declarationProcessors',
        'observe',
        'invalidStartTimeValue'
      ]));
    }
    if (!(endTime instanceof Value)) {
      throw new Error(stringLiterals([
        'declarationProcessors',
        'observe',
        'invalidEndTimeValue'
      ]));
    }
    let sTime = startTime.evaluate();
    let eTime = endTime.evaluate();
    if (eTime < sTime) {
      throw new Error(stringLiterals([
        'declarationProcessors',
        'observe',
        'invalidTimeOrdering'
      ]));
    }
    engine.scheduleObservation(observation, sTime, eTime);
  });
}

module.exports = observeProcessor;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

function Manager() {
  let _events = {};

  this.addListener = function addListener(event, listener) {
    if (_events[event] === undefined) {
      _events[event] = [];
    }

    _events[event].push(listener);
  };

  this.clearListeners = function clearListeners(event) {
    delete _events[event];
  };

  this.notify = function notify(event, sender) {
    if (_events[event] === undefined) {
      return Promise.resolve();
    }

    _events[event].forEach((listener) => {
      listener(sender);
    });
    return Promise.resolve();
  };
}

module.exports = Manager;


/*
  This file is part of the lps.js project, released open source under
  the BSD 3-Clause license. For more info, please see https://github.com/mauris/lps.js
 */

const lpsRequire = require('../lpsRequire');
const LiteralTreeMap = lpsRequire('engine/LiteralTreeMap');
const FunctorProvider = lpsRequire('engine/FunctorProvider');
const StaticAnalyzer = lpsRequire('engine/StaticAnalyzer');

const processRules = lpsRequire('utility/processRules');
const goalTreeSorter = lpsRequire('utility/goalTreeSorter');
const EventManager = lpsRequire('utility/observer/Manager');
const Profiler = lpsRequire('utility/profiler/Profiler');
const constraintCheck = lpsRequire('utility/constraintCheck');
const stringLiterals = lpsRequire('utility/strings');
const evaluateGoalTrees = lpsRequire('utility/evaluateGoalTrees');
const updateStateWithFluentActors = lpsRequire('utility/updateStateWithFluentActors');

const builtinProcessor = lpsRequire('engine/builtin/builtin');
const observeProcessor = lpsRequire('engine/processors/observe');
const initiallyProcessor = lpsRequire('engine/processors/initially');
const ruleAntecedentProcessor = lpsRequire('engine/processors/ruleAntecedent');
const settingsProcessor = lpsRequire('engine/processors/settings');
const timableProcessor = lpsRequire('engine/processors/timable');
const coreModule = lpsRequire('engine/modules/core');

const forEachToString = (arr) => {
  return (item) => {
    arr.push(item.toString());
  };
};

const ERROR_EVENT = 'error';
const DONE_EVENT = 'done';

/**
 * Start a continuous execution that will start the next cycle as soon as the current cycle ends.
 */
const startContinuousExecution = (engine, eventManager, cycleInterval) => {
  const continuousExecutionFunc = () => {
    if (engine.isPaused() || engine.hasHalted()) {
      return;
    }

    // schedule next cycle ahead
    let timer = setTimeout(continuousExecutionFunc, cycleInterval);

    engine.step()
      .then(() => {
        if (engine.hasHalted()) {
          eventManager.notify(DONE_EVENT, engine);
          return;
        }
        clearTimeout(timer);
        setImmediate(continuousExecutionFunc);
      })
      .catch((err) => {
        engine.halt();
        clearTimeout(timer);
        eventManager.notify(ERROR_EVENT, err);
      });
  };
  setImmediate(continuousExecutionFunc);
};

/**
 * Start a normal execution that starts the cycle using the specified cycle interval.
 */
const startNormalExecution = (engine, eventManager, cycleInterval) => {
  let timer = setInterval(() => {
    if (engine.hasHalted()) {
      clearInterval(timer);
      eventManager.notify(DONE_EVENT, engine);
      return;
    }
    if (engine.isPaused()) {
      clearInterval(timer);
      return;
    }
    engine.step()
      .catch((err) => {
        clearInterval(timer);
        eventManager.notify(ERROR_EVENT, err);
      });
  }, cycleInterval);
};

const DEFAULT_MAX_TIME = 20;
const DEFAULT_CYCLE_INTERVAL = 100;

function Engine(programArg) {
  let _program = programArg;
  let _maxTime = DEFAULT_MAX_TIME;
  let _cycleInterval = DEFAULT_CYCLE_INTERVAL; // milliseconds
  let _isContinuousExecution = false;
  let _isInCycle = false;
  let _isPaused = false;
  let _isRunning = false;
  let _isLoaded = false;

  const _engineEventManager = new EventManager();
  const _profiler = new Profiler();

  let _observations = {};

  let _goals = [];

  let _currentTime = 0;

  let _nextCycleObservations = new LiteralTreeMap();
  let _nextCycleActions = new LiteralTreeMap();

  let _lastCycleActions = null;
  let _lastCycleObservations = null;

  const _functorProvider = new FunctorProvider(this);

  const checkConstraintSatisfaction = function checkConstraintSatisfaction(otherProgram) {
    let originalProgram = _program;
    _program = otherProgram;
    let result = constraintCheck(this, otherProgram);
    _program = originalProgram;
    return result;
  };

  /**
   * Process observations for the current cycle
   * @return {LiteralTreeMap} Returns the set of observed event for the current time.
   */
  const processCycleObservations = function processCycleObservations() {
    let activeObservations = new LiteralTreeMap();

    if (_observations[_currentTime] === undefined) {
      // no observations for current time
      return activeObservations;
    }
    let cloneProgram = _program.clone();
    cloneProgram.setExecutedActions(activeObservations);

    const nextTime = _currentTime + 1;

    // process observations for the current time
    _observations[_currentTime].forEach((ob) => {
      let action = ob.action;

      let tempTreeMap = new LiteralTreeMap();
      tempTreeMap.add(action);

      activeObservations.add(action);

      let postCloneProgram = cloneProgram.clone();
      let postState = postCloneProgram.getState();
      postCloneProgram.setExecutedActions(new LiteralTreeMap());
      updateStateWithFluentActors(
        this,
        tempTreeMap,
        postState
      );
      postCloneProgram.setState(postState);

      // perform pre-check and post-check
      if (!checkConstraintSatisfaction.call(this, cloneProgram)
          || !checkConstraintSatisfaction.call(this, postCloneProgram)) {
        // reject the observed event
        // to keep model true
        activeObservations.remove(action);

        // warning
        _engineEventManager.notify('warning', {
          type: 'observation.reject',
          message: stringLiterals(
            'engine.rejectObservationWarning',
            action,
            _currentTime,
            nextTime
          )
        });
      }

      // if the given observation endTime has not ended
      // propagate to the next cycle's
      if (ob.endTime > nextTime) {
        if (_observations[nextTime] === undefined) {
          _observations[nextTime] = [];
        }
        _observations[nextTime].push(ob);
      }
    });

    return activeObservations;
  };

  /**
   * Select the appropriate set of actions from the given goal trees such that
   * constraints are not violated for the current cycle.
   * @param  {Array<GoalTree>} goalTrees The goal trees to choose actions from
   * @return {LiteralTreeMap}            The set of actions chosen to execute
   */
  const actionsSelector = function actionsSelector(goalTrees) {
    const recursiveActionsSelector = (actionsSoFar, programSoFar, l) => {
      if (l >= goalTrees.length) {
        let actions = new LiteralTreeMap();
        actionsSoFar.forEach((map) => {
          map.forEach((literal) => {
            actions.add(literal);
          });
        });
        return actions;
      }
      let goalTree = goalTrees[l];
      let resultSet = null;
      goalTree.forEachCandidateActions(_currentTime, (candidateActions) => {
        let cloneProgram = programSoFar.clone();

        let cloneExecutedActions = cloneProgram.getExecutedActions();
        candidateActions.forEach((a) => {
          cloneExecutedActions.add(a);
        });

        // pre-condition check
        if (!checkConstraintSatisfaction.call(this, cloneProgram)) {
          return false;
        }

        // post condition checks
        let clonePostProgram = programSoFar.clone();
        clonePostProgram.setExecutedActions(new LiteralTreeMap());
        let postState = clonePostProgram.getState();
        updateStateWithFluentActors(
          this,
          candidateActions,
          postState
        );
        clonePostProgram.setState(postState);

        if (!checkConstraintSatisfaction.call(this, clonePostProgram)) {
          return false;
        }

        resultSet = recursiveActionsSelector(
          actionsSoFar.concat([candidateActions]),
          cloneProgram,
          l + 1
        );
        return true;
      });

      if (resultSet !== null) {
        return resultSet;
      }

      return recursiveActionsSelector(
        actionsSoFar,
        programSoFar,
        l + 1
      );
    };

    return recursiveActionsSelector([], _program, 0);
  };

  /**
   * Performs the state transition for a single cycle.
   * @return {Promise} Returns a promise that when fulfilled, indicates the completion of the
   *                   cycle processing.
   */
  const performCycle = function performCycle() {
    _currentTime += 1;

    let selectedAndExecutedActions = new LiteralTreeMap();
    let executedObservations = new LiteralTreeMap();

    // Step 0 - Updating database
    let updatedState = _program.getState().clone();
    updateStateWithFluentActors(
      this,
      _program.getExecutedActions(),
      updatedState
    );
    _program.setState(updatedState);

    _nextCycleObservations.forEach((obs) => {
      executedObservations.add(obs);
    });
    _nextCycleActions.forEach((act) => {
      selectedAndExecutedActions.add(act);
    });

    // Step 1 - Processing rules
    let newFiredGoals = processRules(this, _program, _currentTime, _profiler);
    _goals = _goals.concat(newFiredGoals);

    // Step 3 - Processing
    return evaluateGoalTrees(_currentTime, _goals, _profiler)
      .then((newGoals) => {
        _goals = newGoals;

        // Start preparation for next cycle

        // reset the set of executed actions
        _program.setExecutedActions(new LiteralTreeMap());
        _goals.sort(goalTreeSorter(_currentTime));

        // select actions from candidate actions
        return actionsSelector.call(this, _goals);
      })
      .then((nextCycleActions) => {
        _nextCycleActions = new LiteralTreeMap();
        nextCycleActions.forEach((l) => {
          _nextCycleActions.add(l);
        });
        _nextCycleObservations = new LiteralTreeMap();
        let cycleObservations = processCycleObservations.call(this);
        cycleObservations.forEach((observation) => {
          nextCycleActions.add(observation);
          _nextCycleObservations.add(observation);
        });

        _program.setExecutedActions(nextCycleActions);

        _lastCycleActions = selectedAndExecutedActions;
        _lastCycleObservations = executedObservations;

        // done with cycle
        return Promise.resolve();
      });
  };

  /**
   * Get the profiler object keeping track of statistics for this engine
   * @return {Profiler} Returns the profiler object
   */
  this.getProfiler = function getProfiler() {
    return _profiler;
  };

  /**
   * Get the current time of the LPS program under execution
   * @return {number} Returns the current time
   */
  this.getCurrentTime = function getCurrentTime() {
    return _currentTime;
  };

  /**
   * Get the maximum execution time for the LPS program
   * @return {number} Returns the maximum execution time.
   */
  this.getMaxTime = function getMaxTime() {
    return _maxTime;
  };

  /**
   * Set the maximum execution time for the LPS program.
   * Can only be set before the execution of the LPS program starts.
   * @param {number} newMaxTime The new maximum execution time. Must be positive non-zero integer.
   * @throws Throws an error when an invalid new maximum execution time is given or trying to set
   *         the maximum execution time after execution has started.
   */
  this.setMaxTime = function setMaxTime(newMaxTime) {
    if (_isRunning) {
      throw stringLiterals.error(
        'engine.updatingParametersWhileRunning',
        'max cycle time'
      );
    }
    if (newMaxTime <= 0
        || !Number.isInteger(newMaxTime)) {
      throw stringLiterals.error('engine.nonPositiveIntegerMaxTime', newMaxTime);
    }

    _maxTime = newMaxTime;
  };

  /**
   * Check if the program execution is in cycle.
   * @return {Boolean} Returns true if cycle processing is in progress.
   */
  this.isInCycle = function isInCycle() {
    return _isInCycle;
  };

  /**
   * Check if the LPS program is running.
   * @return {Boolean} Returns true if the program has started running, but not halted yet.
   */
  this.isRunning = function isRunning() {
    return _isRunning;
  };

  /**
   * Check if the LPS program is currently paused in execution.
   * @return {Boolean} Returns true if the program is currently paused.
   */
  this.isPaused = function isPaused() {
    return _isPaused;
  };

  /**
   * Get the amount of set time between start of cycles
   * @return {number} Returns the cycle interval in milliseconds.
   */
  this.getCycleInterval = function getCycleInterval() {
    return _cycleInterval;
  };

  /**
   * Set the cycle interval between start of cycles for the LPS program.
   * Can only be set before the execution of the LPS program starts.
   * @param {number} newCycleInterval The new cycle interval. Must be positive non-zero integer.
   * @throws Throws an error when an invalid new cycle interval is given or trying to set
   *         the cycle interval after execution has started.
   */
  this.setCycleInterval = function setCycleInterval(newCycleInterval) {
    if (_isRunning) {
      throw stringLiterals.error('engine.updatingParametersWhileRunning', 'cycle interval');
    }
    if (typeof newCycleInterval !== 'number') {
      throw stringLiterals.error(
        'engine.parameterInvalidType',
        1,
        'Engine.setCycleInterval',
        'number',
        typeof val
      );
    }
    if (newCycleInterval <= 0
        || !Number.isInteger(newCycleInterval)) {
      throw stringLiterals.error('engine.nonPositiveIntegerCycleInterval', newCycleInterval);
    }
    _cycleInterval = newCycleInterval;
  };

  this.isContinuousExecution = function isContinuousExecution() {
    return _isContinuousExecution;
  };

  this.setContinuousExecution = function setContinuousExecution(val) {
    if (_isRunning) {
      throw stringLiterals.error(
        'engine.updatingParametersWhileRunning',
        'continuous execution mode'
      );
    }
    if (typeof val !== 'boolean') {
      throw stringLiterals.error(
        'engine.parameterInvalidType',
        1,
        'Engine.setContinuousExecution',
        'boolean',
        typeof val
      );
    }
    _isContinuousExecution = val;
  };

  this.getLastCycleActions = function getLastCycleActions() {
    let actions = [];
    if (_lastCycleActions === null) {
      return actions;
    }
    _lastCycleActions.forEach(forEachToString(actions));
    return actions;
  };

  this.getLastCycleObservations = function getLastCycleObservations() {
    let observations = [];
    if (_lastCycleObservations === null) {
      return observations;
    }
    _lastCycleObservations.forEach(forEachToString(observations));
    return observations;
  };

  this.getTimelessFacts = function getTimelessFacts() {
    let facts = [];
    _program.getFacts()
      .forEach(forEachToString(facts));
    return facts;
  };

  this.getActiveFluents = function getActiveFluents() {
    let fluents = [];
    _program.getState()
      .forEach(forEachToString(fluents));
    return fluents;
  };

  this.query = function query(literalArg, type) {
    try {
      let literal = literalArg;
      if (type === 'fluent') {
        return _program.getState().unifies(literal);
      }

      if (type === 'action') {
        return _lastCycleActions.unifies(literal);
      }

      if (type === 'observation') {
        return _lastCycleObservations.unifies(literal);
      }

      return _program.query(literal, this);
    } catch (err) {
      this.halt();
      _engineEventManager.notify(ERROR_EVENT, err);
    }
    return [];
  };

  this.hasHalted = function hasHalted() {
    return _maxTime !== null
      && _currentTime >= _maxTime;
  };

  this.halt = function halt() {
    _maxTime = _currentTime;
    if (_isPaused) {
      _engineEventManager.notify(DONE_EVENT, this);
    }
    _isPaused = false;
  };

  /**
   * Perform a single cycle processing / state transition
   * @return {Promise} Returns a promise that when fulfilled indicates the processing completion
   */
  this.step = function step() {
    if (_isInCycle) {
      // previous cycle has not ended.
      this.halt();
      let error = stringLiterals.error(['engine', 'cycleIntervalExceeded'], [_cycleInterval]);
      return Promise.reject(error);
    }
    if (_isPaused) {
      // cancel execution since engine is paused.
      return Promise.resolve();
    }
    if (this.hasHalted()) {
      // cancel exection since engine has halted.
      return Promise.resolve();
    }
    _engineEventManager.notify('preCycle', this);

    // reset statistics
    _profiler.set('lastCycleNumFiredRules', 0);
    _profiler.set('lastCycleNumFailedGoals', 0);
    _profiler.set('lastCycleNumResolvedGoals', 0);
    _profiler.set('lastCycleNumNewRules', 0);
    _profiler.set('lastCycleNumDiscardedRules', 0);

    _isInCycle = true;
    let startTime = Date.now();
    // perform state transition using performCycle function
    return performCycle.call(this)
      .then(() => {
        _profiler.set('lastCycleExecutionTime', Date.now() - startTime);

        // update statistics
        _profiler.set('numState', _program.getState().size());
        _profiler.set('lastCycleNumUnresolvedGoals', _goals.length);
        _profiler.set('lastCycleNumActions', _lastCycleActions.size());
        _profiler.set('lastCycleNumObservations', _lastCycleObservations.size());

        _isInCycle = false;
        _engineEventManager.notify('postCycle', this);
      });
  };

  /**
   * Start the execution of the LPS program
   */
  this.run = function run() {
    if (_maxTime <= 0) {
      throw stringLiterals.error('engine.maxTimeInvalid', _maxTime);
    }
    if (this.hasHalted()) {
      return;
    }
    _isRunning = true;
    _engineEventManager.notify('run', this);

    if (_isContinuousExecution) {
      startContinuousExecution(this, _engineEventManager, _cycleInterval);
      return;
    }
    startNormalExecution(this, _engineEventManager, _cycleInterval);
  };

  this.define = function define(name, callback) {
    if (_isRunning) {
      throw stringLiterals.error('engine.definePredicatesWhileRunning');
    }
    _functorProvider.define(name, callback);
  };

  this.getFunctorProvider = function getFunctorProvider() {
    return _functorProvider;
  };

  this.on = function on(event, listener) {
    _engineEventManager.addListener(event, listener);
    return this;
  };

  this.observe = function observe(observation) {
    let scheduledTime = _currentTime;
    if (scheduledTime === 0) {
      scheduledTime = 1;
    }
    this.scheduleObservation(observation, scheduledTime);
  };

  /**
   * Pause the LPS program execution. If the program is in a cycle processing,
   * it will pause after the cycle processing ends.
   */
  this.pause = function pause() {
    if (this.hasHalted()) {
      return;
    }
    _isPaused = true;
    _engineEventManager.notify('paused', this);
    _profiler.increment('numPaused');
  };

  /**
   * Unpause and resume the LPS program execution from its current state.
   */
  this.unpause = function unpause() {
    if (this.hasHalted()) {
      return;
    }
    _isPaused = false;
    _engineEventManager.notify('unpaused', this);
    if (_isContinuousExecution) {
      startContinuousExecution(this, _engineEventManager, _cycleInterval);
      return;
    }
    startNormalExecution(this, _engineEventManager, _cycleInterval);
  };

  this.loadModule = function loadModule(module) {
    if (_isRunning) {
      throw stringLiterals.error('engine.definePredicatesWhileRunning');
    }
    if (typeof module !== 'function') {
      throw stringLiterals.error('engine.loadModuleInvalidType');
    }
    return module(this, _program);
  };

  this.scheduleObservation = function scheduleObservation(observation, startTimeArg, endTimeArg) {
    let startTime = startTimeArg;
    let endTime = endTimeArg;
    if (startTime === undefined
        || startTime < _currentTime) {
      throw stringLiterals.error(
        'engine.invalidStartTimeObservationScheduling',
        [observation, startTime, _currentTime]
      );
    }

    if (startTime === _currentTime && _isInCycle) {
      // already in a cycle, so process it in the next cycle
      startTime += 1;
    }

    if (endTime === undefined) {
      // default endTime is given by 1 after the startTime
      endTime = startTime + 1;
    }

    if (endTime <= startTime) {
      // invalid endTime given
      throw stringLiterals.error(
        'engine.invalidObservationScheduling',
        [observation, startTime, endTime]
      );
    }

    if (_observations[startTime] === undefined) {
      _observations[startTime] = [];
    }

    let processSingleObservation = (obsArg) => {
      let obs = obsArg;
      _observations[startTime].push({
        action: obs,
        endTime: endTime
      });
    };

    if (observation instanceof Array) {
      observation.forEach((obs) => {
        processSingleObservation(obs);
      });
      return;
    }
    processSingleObservation(observation);
  };

  // we preprocess some of the built-in processors by looking at the facts
  // of the _program.
  this.on('loaded', () => {
    StaticAnalyzer.analyze(_program, _engineEventManager);
    settingsProcessor(this, _program);
    timableProcessor(this, _program);
    initiallyProcessor(this, _program);
    observeProcessor(this, _program);
    ruleAntecedentProcessor(this, _program);
  });

  this.load = function load() {
    if (_isLoaded) {
      return Promise.reject(stringLiterals.error('engine.loadingLoadedEngine'));
    }
    // load once only
    _isLoaded = true;
    coreModule(this, _program);

    return builtinProcessor(this, _program)
      .then(() => {
        if (process.browser) {
          // skip consult processing for browser context
          return Promise.resolve();
        }
        const consultProcessor = lpsRequire('engine/processors/consult');
        // start processing consult/1, consult/2 and loadModule/1 declarations in main program
        return consultProcessor(this, _program);
      })
      .then(() => {
        return _engineEventManager.notify('loaded', this);
      })
      .then(() => {
        _engineEventManager.notify('ready', this);
        return Promise.resolve(this);
      });
  };

  _profiler.set('numPaused', 0);
}

module.exports = Engine;


// Javascript Library for Multi-step Asynchronous Logic
// Version 0.2.3
// Copyright (c) 2010 William R. Conant, WillConant.com
// Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php

(function(){
	// converts native arguments object to an array and applies function
	function applyArgs(func, thisObj, args) {
		return func.apply(thisObj, Array.prototype.slice.call(args));
	}

	// defines a flow given any number of functions as arguments
	function define() {
		var thisFlow = function() {
			applyArgs(thisFlow.exec, thisFlow, arguments);
		}

		thisFlow.blocks = arguments;

		thisFlow.exec = function() {
			// The flowState is the actual object each step in the flow is applied to. It acts as a
			// callback to the next function. It also maintains the internal state of each execution
			// and acts as a place for users to save values between steps of the flow.
			var flowState = function() {
				if (flowState.__frozen) return;

				if (flowState.__timeoutId) {
					clearTimeout(flowState.__timeoutId);
					delete flowState.__timeoutId;
				}

				var blockIdx = flowState.__nextBlockIdx ++;
				var block = thisFlow.blocks[blockIdx];

				if (block === undefined) {
					return;
				}
				else {
					applyArgs(block, flowState, arguments);
				}
			}

			// __nextBlockIdx specifies which function is the next step in the flow.
			flowState.__nextBlockIdx = 0;

			// __multiCount is incremented every time MULTI is used to createa a multiplexed callback
			flowState.__multiCount = 0;

			// __multiOutputs accumulates the arguments of each call to callbacks generated by MULTI
			flowState.__multiOutputs = [];

			// REWIND signals that the next call to thisFlow should repeat this step. It allows you
			// to create serial loops.
			flowState.REWIND = function() {
				flowState.__nextBlockIdx -= 1;
			}

			// MULTI can be used to generate callbacks that must ALL be called before the next step
			// in the flow is executed. Arguments to those callbacks are accumulated, and an array of
			// of those arguments objects is sent as the one argument to the next step in the flow.
			// @param {String} resultId An identifier to get the result of a multi call.
			flowState.MULTI = function(resultId) {
				flowState.__multiCount += 1;
				return function() {
					flowState.__multiCount -= 1;
					flowState.__multiOutputs.push(arguments);

          if (resultId) {
            var result = arguments.length <= 1 ? arguments[0] : arguments
            flowState.__multiOutputs[resultId] = result;
          }

					if (flowState.__multiCount === 0) {
						var multiOutputs = flowState.__multiOutputs;
						flowState.__multiOutputs = [];
						flowState(multiOutputs);
					}
				}
			}

			// TIMEOUT sets a timeout that freezes a flow and calls the provided callback. This
			// timeout is cleared if the next flow step happens first.
			flowState.TIMEOUT = function(milliseconds, timeoutCallback) {
				if (flowState.__timeoutId !== undefined) {
					throw new Error("timeout already set for this flow step");
				}

				flowState.__timeoutId = setTimeout(function() {
					flowState.__frozen = true;
					timeoutCallback();
				}, milliseconds);
			}

			applyArgs(flowState, this, arguments);
		}

		return thisFlow;
	}

	// defines a flow and evaluates it immediately. The first flow function won't receive any arguments.
	function exec() {
		var flow = typeof exports != 'undefined' ? exports : window.flow;
		applyArgs(flow.define, flow, arguments)();
	}

	// a very useful flow for serial execution of asynchronous functions over a list of values
	// (idea suggested by John Wright, http://github.com/mrjjwright)
	var serialForEach = define(
		function(items, job, between, finish) {
			this.items = items;
			this.curItem = 0;
			this.job = job;
			this.between = between;
			this.finish = finish;
			this();

		},function() {
			if (this.curItem > 0 && this.between) {
				applyArgs(this.between, this, arguments);
			}

			if (this.curItem >= this.items.length) {
				this();
			}
			else {
				this.REWIND();
				this.curItem += 1;
				this.job(this.items[this.curItem - 1]);
			}

		},function() {
			if (this.finish) this.finish();
		}
	);

	// helper methods
	function anyError(results) {
		var r, _i, _len;
		for (_i = 0, _len = results.length; _i < _len; _i++) {
			r = results[_i];
			if (r[0]) {
				return r[0];
			}
		}
		return null;
	}

	// export our functions
	if (typeof exports !== "undefined") {
		exports.define = define;
		exports.exec = exec;
		exports.serialForEach = serialForEach;
		exports.anyError = anyError;
	}
	else if (typeof window !== "undefined") {
		window.flow = {
			define: define,
			exec: exec,
			serialForEach: serialForEach,
			anyError: anyError
		};
	}
})();


/**
 * Utils for SM2 and SM3 module
 */

var utils = exports
var BN = require('bn.js');
var crypto = require('crypto');

utils.strToBytes = strToBytes;
utils.hashToBN = hashToBN;
utils.random = random;
utils.padStart = padStart;

function strToBytes(s) {
  var ch, st, re = [];
  for (var i = 0; i < s.length; i++ ) {
    ch = s.charCodeAt(i);  // get char
    st = [];                 // set up "stack"
    do {
      st.push( ch & 0xFF );  // push byte to stack
      ch = ch >> 8;          // shift value down by 1 byte
    }
    while ( ch );
    // add stack contents to result
    // done because chars have "wrong" endianness
    re = re.concat( st.reverse() );
  }
  return re;
}

function hashToBN(hash) {
  if (typeof hash == 'string') {
    return new BN(hash, 16);
  } else {
    var hex = '';
    for (var i = 0; i < hash.length; i++) {
      var b = hash[i].toString(16);
      if (b.length == 1) {
        hex += '0';
      }
      hex += b;
    }
    return new BN(hex, 16);
  }
}

/**
 * Pads supplied string with character to fill the desired length.
 *
 * @param {*} str String to pad
 * @param {*} length Desired length of result string
 * @param {*} padChar Character to use as padding
 */
function padStart(str, length, padChar) {
  if (str.length >= length) {
      return str;
  } else {
      return padChar.repeat(length - str.length) + str;
  }
}

/**
 * Generate cryptographic random value.
 *
 * @param {Number} n: byte length of the generated value
 */
function random(n) {
  return crypto.randomBytes(n).toString('hex')
}


/**
 * SM2 elliptic curve
 *
 * Support SM2 key pair generation and signature.
 */

var sm3 = require('./sm3');
var utils = require('./utils');
var elliptic = require('elliptic');
var BN = require('bn.js');
var DRBG = require('hmac-drbg');
var hash = require('hash.js');
var inherits = require('inherits');

var _drbg = new DRBG({
  hash: hash.sha256,
  entropy: 'UQi4W3Y2bJfzleYy+oEZ2kA9A+9jrmwewST9vmBZNgMmFyzzH0S9Vol/UK',
  nonce: '0123456789avcdef',
  pers: '0123456789abcdef'
});

/**
 * The SM2 elliptic curve
 */
function SM2Curve(params) {
  if (!(this instanceof SM2Curve)) {
    return new SM2Curve(params);
  }

  elliptic.curve.short.call(this, params);
}
inherits(SM2Curve, elliptic.curve.short);

var _sm2Params = {
  type: 'SM2',
  prime: null,
  p: 'FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF',
  a: 'FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC',
  b: '28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93',
  n: 'FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123',
  hash: sm3,
  gRed: false,
  g: [
    '32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7',
    'BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0'
  ]

}

exports.curve = SM2 = SM2Curve(_sm2Params);

/**
 * Return a point on the curve.
 * Will throw error if (x,y) is not on curve.
 *
 * @param {string} x - coordinate x in hex string, should not be null
 * @param {string} y - coordinate y in hex string
 * @param {string='even'} parity - determine the value of y, could be 'odd' or 'even', ignored when y is not null
 */
function _sm2Point(x, y, parity) {
  if (x == null) {
    return SM2.point();
  }

  var pt;
  if (y != null) {
    pt = SM2.point(x, y);
    if (!SM2.validate(pt)) {
      throw 'point is not on curve';
    }
  } else {
    var px = new BN(x, 16).toRed(SM2.red);
    var py = px.redSqr().redMul(px);
    py = py.redIAdd(px.redMul(SM2.a)).redIAdd(SM2.b).redSqrt();
    if ((parity === 'odd') != py.fromRed().isOdd()) {
      py = py.redNeg();
    }
    pt = SM2.point(px, py);
  }

  return pt;
}

/**
 * SM2 public and private key pair
 *
 * Either `pub` and `pri` can be a hex string or byte array or null.
 * If `pub` is a string, it should be the same format as output of pubToString().
 */
function SM2KeyPair(pub, pri) {
  if (!(this instanceof SM2KeyPair)) {
    return new SM2KeyPair(pub, pri);
  }
  this.curve = SM2; // curve parameter
  this.pub = null; // public key, should be a point on the curve
  this.pri = null; // private key, should be a integer

  var validPub = false;
  var validPri = false;

  if (pub != null) {
    if (typeof pub === 'string') {
      this._pubFromString(pub);
    } else if (Array.isArray(pub)) {
      this._pubFromBytes(pub);
    } else if ('x' in pub && pub.x instanceof BN &&
               'y' in pub && pub.y instanceof BN) {
      // pub is already the Point object
      this.pub = pub;
      validPub = true;
    } else {
      throw 'invalid public key';
    }
  }
  if (pri != null) {
    if (typeof pri === 'string') {
      this.pri = new BN(pri, 16);
    } else if (pri instanceof BN) {
      this.pri = pri;
      validPri = true;
    } else {
      throw 'invalid private key';
    }

    // calculate public key
    if (this.pub == null) {
      this.pub = SM2.g.mul(this.pri);
    }
  }

  if (!(validPub && validPri) && !this.validate()) {
    throw 'invalid key';
  }
}
exports.SM2KeyPair = SM2KeyPair;

/**
 * Generate a SM2 key pair
 */
exports.genKeyPair = function _genKeyPair() {
  var pri = 0;
  var limit = SM2.n.sub(new BN(2));
  // generate 32 bytes private key in range [1, n-1]
  do {
    pri = new BN(_drbg.generate(32, 'hex', utils.random(64)));
  } while (pri.cmp(limit) > 0);

  return new SM2KeyPair(null, pri);
}

/**
 * @private
 * Parse public key from hex string.
 */
SM2KeyPair.prototype._pubFromString = function (s) {
  var err = 'invalid key string';
  if (s.length < 66) {
    throw err;
  }
  var x = s.slice(2, 66);
  switch (s.slice(0, 2)) {
    case '00':
      throw 'public key should not be infinity';
    case '02':
      this.pub = _sm2Point(x, null, 'even');
      break;
    case '03':
      this.pub = _sm2Point(x, null, 'odd');
      break;
    case '04':
    case '06':
    case '07':
      if (s.length < 130) {
        throw err;
      }
      this.pub = _sm2Point(x, s.slice(66, 130));
      break;
    default:
      throw err;
  }
}

/**
 * @private
 * Parse public key from byte array.
 */
SM2KeyPair.prototype._pubFromBytes = function (b) {
  var err = 'unrecognized key';
  if (b.length < 33) {
    throw err;
  }
  var x = b.slice(1, 33);
  switch (b[0]) {
    case 0x00:
      throw 'public key should not be infinity';
    case 0x02:
      this.pub = _sm2Point(x, null, 'even');
      break;
    case 0x03:
      this.pub = _sm2Point(x, null, 'odd');
      break;
    case 0x04:
    case 0x06:
    case 0x07:
      if (b.length < 65) {
        throw err;
      }
      this.pub = _sm2Point(x, b.slice(33, 65));
      break;
    default:
      throw err;
  }
}

/**
 * Check whether the public key is valid.
 *
 * @return {bool}
 */
SM2KeyPair.prototype.validate = function() {
  if (this.pub != null) {
    if (this.pub.isInfinity()) {
      return false;
    }

    if (!this.curve.validate(this.pub)) {
      return false;
    }

    if (!this.pub.mul(this.curve.n).isInfinity()) {
      return false;
    }
  }

  if (this.pri != null) {
    if (this.pri.cmp(this.curve.n.sub(new BN(2))) > 0) {
      return false;
    }

    if (this.pub != null && !this.pub.eq(this.curve.g.mul(this.pri))) {
      return false;
    }
  }

  return true;
}

/**
 * Convert the public key to the hex string format
 *
 * @param {Number} [mode='nocompress'] - compressing mode, available values:
 *    'compress', 'nocompress', 'mix'
 */
SM2KeyPair.prototype.pubToString = function(mode) {
  var s = '';
  switch (mode) {
    case 'compress':
      if (this.pub.getY().isEven()) {
        s = '02';
      } else {
        s = '03';
      }
      return s + this.pub.getX().toString(16, 32);
    case 'mix':
      if (this.pub.getY().isEven()) {
        s = '06';
      } else {
        s = '07';
      }
      break;
    default:
      s = '04'
  }
  return s + this.pub.getX().toString(16, 32) + this.pub.getY().toString(16, 32);
}

/**
 * Convert the public key to a byte array.
 * The value of X and Y will be stored in big endian.
 *
 * @param {string} mode - compressing mode, same as pubToString.
 */
SM2KeyPair.prototype.pubToBytes = function(mode) {
  var a = [];
  switch (mode) {
    case 'compress':
      if (this.pub.getY().isEven()) {
        a.push(0x02);
      } else {
        a.push(0x03);
      }
      return a.concat(this.pub.getX().toArray('be', 32));
    case 'mix':
      if (this.pub.getY().isEven()) {
        a.push(0x06);
      } else {
        a.push(0x07);
      }
      break;
    default:
      a.push(0x04);
  }
  return a.concat(this.pub.getX().toArray('be', 32)).concat(this.pub.getY().toArray('be', 32));
}

/**
 * Generate signature to the message
 *
 * The input message will combine with extras(a constant user id, the
 * curve parameters and public key), and use SM3 hashing function to
 * generate digest.
 *
 * @param {string|byte array} msg
 *
 * @return {SM2KeyPair} Signature (r, s). Both part is a hex string.
 */
SM2KeyPair.prototype.sign = function(msg) {
  if (this.pri == null) {
    throw 'cannot sign message without private key';
  }
  if (typeof msg === 'string')
    return this.signDigest(new sm3().sum(this._combine(utils.strToBytes(msg))));
  else
    return this.signDigest(new sm3().sum(this._combine(msg)));
}

/**
 * Verify the signature (r,s)
 *
 * @param {string|byte array} msg
 * @param {string} r - signature.r part in hex string
 * @param {string} s - signature.s part in hex string
 *
 * @return {bool} true if verification passed.
 */
SM2KeyPair.prototype.verify = function(msg, r, s) {
  if (this.pub == null) {
    throw 'cannot verify signature without public key';
  }
  return this.verifyDigest(new sm3().sum(this._combine(msg)), r, s);
}

/**
 * Generate signature to the message without combination with extras.
 */
SM2KeyPair.prototype.signRaw = function(msg) {
  return this.signDigest(new sm3().sum(msg));
}

/**
 * Verify signature (r, s) generated by signRaw()
 */
SM2KeyPair.prototype.verifyRaw = function(msg, r, s) {
  return this.verifyDigest(new sm3().sum(msg), r, s);
}

/**
 * Generate signature for the message digest
 *
 * The input data should be a 256bits hash digest.
 *
 * @param {string|byte array} digest - the digest of the message
 * @return {object}  signature with r and s parts
 */
SM2KeyPair.prototype.signDigest = function(digest) {
  var signature = {
    r: "",
    s: ""
  }
  while (true) {
    var k = new BN(_drbg.generate(32, 'hex', utils.random(64)), 16).umod(this.curve.n);
    var kg = this.curve.g.mul(k);
    var r = utils.hashToBN(digest).add(kg.getX()).umod(this.curve.n);

    console.log("k =", k.toString());

    // r = 0
    if (r.isZero()) {
      continue;
    }
    // r + k = n
    if (r.add(k).eq(this.curve.n)) {
      continue;
    }

    var t1 = new BN(1).add(this.pri).invm(this.curve.n);
    var t2 = k.sub(r.mul(this.pri)).umod(this.curve.n);
    var s = t1.mul(t2).umod(this.curve.n);
    if (!s.isZero()) {
      signature.r = utils.padStart(r.toString(16), 64, '0');
      signature.s = utils.padStart(s.toString(16), 64, '0');
      break;
    }
  }

  return signature;
}

/**
 * Verify the signature to the digest
 *
 * @param {string|byte array} digest - digest of the message
 * @param {string} r - hex string of signature.r
 * @param {string} s - hex string of signature.s
 *
 * @return {bool} true if verification passed
 */
SM2KeyPair.prototype.verifyDigest = function(digest, r, s) {
  var bnr = new BN(r, 16);
  if (bnr.cmp(this.curve.n) >= 0) {
    return false;
  }

  var bns = new BN(s, 16);
  if (bns.cmp(this.curve.n) >= 0) {
    return false;
  }

  var t = bnr.add(bns).umod(this.curve.n);
  if (t.isZero()) {
    return false;
  }

  var q = this.curve.g.mul(bns).add(this.pub.mul(t));
  var R = utils.hashToBN(digest).add(q.getX()).umod(this.curve.n);
  if (!R.eq(bnr)) {
    return false;
  }

  return true;
}

SM2KeyPair.prototype._combine = function(msg) {
  var za = [0x00, 0x80, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38];
  za = za.concat(this.curve.a.fromRed().toArray());
  za = za.concat(this.curve.b.fromRed().toArray());
  za = za.concat(this.curve.g.getX().toArray());
  za = za.concat(this.curve.g.getY().toArray());
  za = za.concat(this.pub.getX().toArray());
  za = za.concat(this.pub.getY().toArray());

  h = new sm3();
  za = h.sum(za);

  console.log(za.join())
  if (typeof msg === 'string')
    return za.concat(utils.strToBytes(msg))
  else
    return za.concat(msg);
}

SM2KeyPair.prototype.toString = function() {
  var s = "public: ";
  if (this.pub) {
    s += "(" + this.pub.getX().toString(16) + ", " + this.pub.getY().toString(16) + ")";
  } else {
    s += "null";
  }
  s += ", private: ";
  if (this.pri) {
    s += utils.padStart(this.pri.toString(16), 64, '0');
  } else {
    s += "null";
  }
  return s;
}