var pluralForms = (function () {
	'use strict';

	var PRECEDENCE, OPENERS, TERMINATORS, PATTERN;

	/**
	 * Operator precedence mapping.
	 *
	 * @type {Object}
	 */
	PRECEDENCE = {
		'(': 9,
		'!': 8,
		'*': 7,
		'/': 7,
		'%': 7,
		'+': 6,
		'-': 6,
		'<': 5,
		'<=': 5,
		'>': 5,
		'>=': 5,
		'==': 4,
		'!=': 4,
		'&&': 3,
		'||': 2,
		'?': 1,
		'?:': 1,
	};

	/**
	 * Characters which signal pair opening, to be terminated by terminators.
	 *
	 * @type {string[]}
	 */
	OPENERS = [ '(', '?' ];

	/**
	 * Characters which signal pair termination, the value an array with the
	 * opener as its first member. The second member is an optional operator
	 * replacement to push to the stack.
	 *
	 * @type {string[]}
	 */
	TERMINATORS = {
		')': [ '(' ],
		':': [ '?', '?:' ],
	};

	/**
	 * Pattern matching operators and openers.
	 *
	 * @type {RegExp}
	 */
	PATTERN = /<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;

	/**
	 * Given a C expression, returns the equivalent postfix (Reverse Polish)
	 * notation terms as an array.
	 *
	 * If a postfix string is desired, simply `.join( ' ' )` the result.
	 *
	 * @example
	 *
	 * ```js
	 * import postfix from '@tannin/postfix';
	 *
	 * postfix( 'n > 1' );
	 * // ⇒ [ 'n', '1', '>' ]
	 * ```
	 *
	 * @param {string} expression C expression.
	 *
	 * @return {string[]} Postfix terms.
	 */
	function postfix( expression ) {
		var terms = [],
			stack = [],
			match, operator, term, element;

		while ( ( match = expression.match( PATTERN ) ) ) {
			operator = match[ 0 ];

			// Term is the string preceding the operator match. It may contain
			// whitespace, and may be empty (if operator is at beginning).
			term = expression.substr( 0, match.index ).trim();
			if ( term ) {
				terms.push( term );
			}

			while ( ( element = stack.pop() ) ) {
				if ( TERMINATORS[ operator ] ) {
					if ( TERMINATORS[ operator ][ 0 ] === element ) {
						// Substitution works here under assumption that because
						// the assigned operator will no longer be a terminator, it
						// will be pushed to the stack during the condition below.
						operator = TERMINATORS[ operator ][ 1 ] || operator;
						break;
					}
				} else if ( OPENERS.indexOf( element ) >= 0 || PRECEDENCE[ element ] < PRECEDENCE[ operator ] ) {
					// Push to stack if either an opener or when pop reveals an
					// element of lower precedence.
					stack.push( element );
					break;
				}

				// For each popped from stack, push to terms.
				terms.push( element );
			}

			if ( ! TERMINATORS[ operator ] ) {
				stack.push( operator );
			}

			// Slice matched fragment from expression to continue match.
			expression = expression.substr( match.index + operator.length );
		}

		// Push remainder of operand, if exists, to terms.
		expression = expression.trim();
		if ( expression ) {
			terms.push( expression );
		}

		// Pop remaining items from stack into terms.
		return terms.concat( stack.reverse() );
	}

	/**
	 * Operator callback functions.
	 *
	 * @type {Object}
	 */
	var OPERATORS = {
		'!': function( a ) {
			return ! a;
		},
		'*': function( a, b ) {
			return a * b;
		},
		'/': function( a, b ) {
			return a / b;
		},
		'%': function( a, b ) {
			return a % b;
		},
		'+': function( a, b ) {
			return a + b;
		},
		'-': function( a, b ) {
			return a - b;
		},
		'<': function( a, b ) {
			return a < b;
		},
		'<=': function( a, b ) {
			return a <= b;
		},
		'>': function( a, b ) {
			return a > b;
		},
		'>=': function( a, b ) {
			return a >= b;
		},
		'==': function( a, b ) {
			return a === b;
		},
		'!=': function( a, b ) {
			return a !== b;
		},
		'&&': function( a, b ) {
			return a && b;
		},
		'||': function( a, b ) {
			return a || b;
		},
		'?:': function( a, b, c ) {
			if ( a ) {
				throw b;
			}

			return c;
		},
	};

	/**
	 * Given an array of postfix terms and operand variables, returns the result of
	 * the postfix evaluation.
	 *
	 * @example
	 *
	 * ```js
	 * import evaluate from '@tannin/evaluate';
	 *
	 * // 3 + 4 * 5 / 6 ⇒ '3 4 5 * 6 / +'
	 * const terms = [ '3', '4', '5', '*', '6', '/', '+' ];
	 *
	 * evaluate( terms, {} );
	 * // ⇒ 6.333333333333334
	 * ```
	 *
	 * @param {string[]} postfix   Postfix terms.
	 * @param {Object}   variables Operand variables.
	 *
	 * @return {*} Result of evaluation.
	 */
	function evaluate( postfix, variables ) {
		var stack = [],
			i, j, args, getOperatorResult, term, value;

		for ( i = 0; i < postfix.length; i++ ) {
			term = postfix[ i ];

			getOperatorResult = OPERATORS[ term ];
			if ( getOperatorResult ) {
				// Pop from stack by number of function arguments.
				j = getOperatorResult.length;
				args = Array( j );
				while ( j-- ) {
					args[ j ] = stack.pop();
				}

				try {
					value = getOperatorResult.apply( null, args );
				} catch ( earlyReturn ) {
					return earlyReturn;
				}
			} else if ( variables.hasOwnProperty( term ) ) {
				value = variables[ term ];
			} else {
				value = +term;
			}

			stack.push( value );
		}

		return stack[ 0 ];
	}

	/**
	 * Given a C expression, returns a function which can be called to evaluate its
	 * result.
	 *
	 * @example
	 *
	 * ```js
	 * import compile from '@tannin/compile';
	 *
	 * const evaluate = compile( 'n > 1' );
	 *
	 * evaluate( { n: 2 } );
	 * // ⇒ true
	 * ```
	 *
	 * @param {string} expression C expression.
	 *
	 * @return {(variables?:{[variable:string]:*})=>*} Compiled evaluator.
	 */
	function compile( expression ) {
		var terms = postfix( expression );

		return function( variables ) {
			return evaluate( terms, variables );
		};
	}

	/**
	 * Given a C expression, returns a function which, when called with a value,
	 * evaluates the result with the value assumed to be the "n" variable of the
	 * expression. The result will be coerced to its numeric equivalent.
	 *
	 * @param {string} expression C expression.
	 *
	 * @return {Function} Evaluator function.
	 */
	function pluralForms( expression ) {
		var evaluate = compile( expression );

		return function( n ) {
			return +evaluate( { n: n } );
		};
	}

	return pluralForms;

}());
