/**
 * MissMatch (v1.0.1)
 * ---------------------------------------------------------------------
 */
var mm = { version: '1.0.1' };

/**
 * makeParser
 * generates a new parser for a pattern. A parser transforms a pattern
 * to an Abstract Syntax Tree that can be compiled to a matcher
 * function.
 *
 * Usage:
 * var AST = makeParser('o(.x, .y)').parse();
 */

/*--------------------------------------------------------------------*/
mm.makeParser = function (src) {
  "use strict";
  var index = 0;

  function next()     { return src[index++]; }
  function hasNext()  { return index < src.length; }
  function consume()  { index ++; }
  function peek()     { return src[index]; }
  function clear()    { while(peek() === ' ') { consume(); } }
  function once(i)    { var a=!i; return function () { return a==!i ? (a = !a) : !i } }
  function newNode(type,AST) {
    var node = {
      type: type,
      nodes: [],
      binding: false
    };

    AST.push(node);
    return node;
  }

  /**
   * Match valid JavaScript variable names. Letters, numbers (if not in
   * first position), $ and _ are allowed.
   */
  function validChar(c,isFirst) {
    return isFirst ? /[a-zA-Z$_]/.test(c) : /[0-9a-zA-Z$_]/.test(c);
  }

   // Match characters that are valid for numeric expressions.
  function validNum(c) {
    return /[0-9eE\+\-\.]/.test(c);
  }

  // Default exception
  function unexpectedTokenException (expected) {
    throw "Unexpected token at index " + index
      + " expected '"+ expected +"' but found "
      + (peek() || "''");
  }

  /**
   * Parses @ bindings. (Patterns bound to variables that are available
   * in the handler function if the pattern matches).
   * e.g. 'n@x' matches a numeric value and binds it to 'x'.
   */
  function parseBinding(node) {
    consume(); // '@'
    var binding = [], first = once(true);

    while (hasNext()) {
      if (validChar(peek(), first())) {
        binding[binding.length] = next();
      } else {
        break;
      }
    }

    if (binding.length > 0) {
      node.binding = binding.join('');
    } else {
      throw "No binding name given at " + index + " for " + node.type;
    }
  }

   // Parses the (...) component of an array pattern.
  function parseArrayList(node) {
    consume(); // '('

    // a() matches the empty list.
    if(peek() === ')') {
      node.type = '()';
      return;
    }

    while (true) {
      // The whole array may be matched with 'a(|)' or (preferably) 'a'.
      if(peek() === '|') { break; }
      /**
       * Use clear() to allow whitespace on certain locations:
       * (n,n@x), (n, n@x), ( n , n@x ) are all accepted.
       * (n, n @x) is not accepted: bindings do not allow
       * any whitespace.
       */
      clear(); stage1(node.nodes); clear();
      if(peek() !== ',') {
        break;
      }
      consume(); // ','
    }
  }

  function parseArray(AST) {
    var rest, node = newNode(next(), AST);

    if(hasNext()) {
      /**
       * Array may be matched by either
       * a@name: Array bound to name
       * a(...): Array pattern
       */
      switch(peek()) {
        case '@':
          parseBinding(node);
          return;
        case '(':
          parseArrayList(node);
          break;
        default:
          /**
           * Allow patterns like a(a) to match nested arrays without
           * binding them to names.
           */
          return;
      }

       // Rest of an array matched?
      if(peek() === '|') {
        rest = newNode(next(), node.nodes);
        clear();

        // Rest of an array bound to a name?
        if(peek() === '@') {
          parseBinding(rest);
          clear();
        }
      }

      // Ensure that there is a closing parentheses in a(...)
      if(peek() !== ')') {
        unexpectedTokenException(')');
      } else {
        consume(); // ')'
        // The whole array might be bound to a name.
        if(hasNext() && peek() === '@') {
          parseBinding(node);
        }
      }
    }
  }

  /**
   * Parse a single property. Valid property names can contain
   * upper- and lowercase letters, _, $ and numbers.
   */
  function parseProperty(node,proto) {
    consume(); // '.'
    var name = [], property, first = once(true);

    while(hasNext() && validChar(peek(), first())) {
      name[name.length] = next();
    }

    if(name.length > 0) {
      property = newNode(proto ? ':' : '.', node.nodes);
      property.name = name.join('');

      /**
       * Properties may have type specifiers. This is the way to go
       * to match nested objects.
       *
       * e.g. 'o(.coord:o(.x, .y))' matches objects like
       *  '{coord: {x: 5, y: 7} }'
       */
      if(hasNext() && peek() === ':') {
        consume(); // '('
        stage1(property.nodes);
      }

      // The property value might be bound to a name
      if(hasNext() && peek() === '@') {
        parseBinding(property);
      }
    } else {
      throw "No property name given at " + index + " for " + node.type;
    }
  }

  // Parse the property list of an object pattern.
  function parseProperties(node) {
    consume(); // '('

    while(true) {
      clear();
      /**
       * Properties always have to start with '.' or ':'
       * o(.x, :y) matches an object with at least an owned property
       * 'x' and a owned or inherited property 'y'.
       */
      if(peek() === '.') {
        parseProperty(node,false);  // own property
      } else if(peek() === ':') {
        parseProperty(node,true);   // prototype property
      } else {
        unexpectedTokenException('. or :');
      }
      clear();

      if(peek() !== ',') {
        break;
      }
      consume(); // ','
    }
  }

  function parseObject(AST) {
    var node = newNode(next(), AST);

    if(hasNext()) {
     /**
       * An object may be matched by either
       * o@name: Object bound to name
       * o(...): Object pattern
       */
      switch(peek()) {
        case '@':
          parseBinding(node);
          return;
        case '(':
          parseProperties(node);
          break;
        default:
          /**
           * Allow patterns like a(o) to match nested objects without
           * binding them to names.
           */
          return;
      }

      // Ensure that there is a closing parentheses in o(...)
      if(peek() !== ')') {
        unexpectedTokenException(')');
      } else {
        consume(); // ')'
         // The whole object might be bound to a name
        if(hasNext() && peek() === '@') {
          parseBinding(node);
        }
      }
    }
  }

  /**
   * Parse a list of literals. The type of literal is determined by
   * the second parameter: a function to parse a certain type
   * (string, numeric, boolean, date) of literals
   */
  function parseLiteralList(AST,parseFunction) {
    consume(); // '('

    while(true) {
      clear(); parseFunction(AST); clear();

      if(peek() !==  ',') {
        break;
      }
      consume(); // ','
    }
  }

  // String literals might start with ' or "
  function extractStringLiteral() {
    var literal = [], enclosing = next();

    if(!(enclosing === '"' || enclosing === "'")) {
      throw "Unexpected token at index " + index +
        " expected 'string' but found " + enclosing;
    }

    while(hasNext() && peek() !== enclosing) {
      literal[literal.length] = next();
    }

    consume(); // ' or "
    return literal.join('');
  }

  function extractRegex() {
    var literal = [];
    consume(); // '/'

    while(hasNext() && peek() !== '/') {
      literal[literal.length] = next();
    }

    consume(); // '/'
    return new RegExp(literal.join(''));
  }

  // A string literal list may contain strings or regular expressions.
  function parseStringLiteral(AST) {
    if(peek() === '/') {
      newNode(extractRegex(), newNode('r=', AST).nodes);
    } else {
      newNode(extractStringLiteral(), newNode('=', AST).nodes);
    }
  }

   // Parse numeric literals like 1, 1.05, .05, 8e5...
  function parseNumericLiteral(AST) {
    var literal = [], value;

    while(hasNext() && validNum(peek())) {
      literal[literal.length] = next();
    }

    value = parseFloat(literal.join(''));

    /**
     * Thanks to CMS's answer on StackOverflow:
     * http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
     */
    if(!isNaN(value) && isFinite(value)) {
      newNode(value, newNode('=', AST).nodes);
    } else {
        unexpectedTokenException('numeric');
    }
  }

  function parseBooleanLiteral(AST) {
    var literal = [], code, lc = /[a-z]/;

    while(hasNext() && lc.test(peek())) {
      literal[literal.length] = next();
    }

    literal = literal.join('');
    if(literal === 'true' || literal === 'false') {
      newNode(literal[0] === 't' ? true : false, newNode('=', AST).nodes);
    } else {
        unexpectedTokenException('boolean');
    }
  }

  function parseDateLiteral(AST) {
    var literal = extractStringLiteral(), date = new Date(literal);

    if(isNaN(date.getTime())) {
      unexpectedTokenException('date');
    } else {
      newNode(date, newNode('d=', AST).nodes);
    }
  }

   // Matches any of (a,o,n,s,b,f,d,r,_)
  function parseGeneric(AST,type) {
    var node = newNode(next(),AST);

    // Parse literal lists, e.g. s("a", "b", ...)
    if(peek() === '(') {
      switch(type) {
        case "s":
          parseLiteralList(node.nodes, parseStringLiteral);
          break;
        case "n":
          parseLiteralList(node.nodes, parseNumericLiteral);
          break;
        case "b":
          parseLiteralList(node.nodes, parseBooleanLiteral);
          break;
        case "d":
          parseLiteralList(node.nodes, parseDateLiteral);
          break;
      }

      node.type = '||';
      consume(); // ')'
    }

    if(peek() === '@') { parseBinding(node); }
  }

  // Parser entry point
  function stage1(AST) {
    if(hasNext()) {
      switch (peek()) {
        case 'a':
          parseArray(AST);
          break;
        case 'o':
          parseObject(AST);
          break;
        default:
          if (/[nsSbfdr_]/.test(peek())) {
            parseGeneric(AST,peek());
          } else {
            unexpectedTokenException('one of (a,o,n,s,S,b,f,d,r,_)');
          }
      }
    }
    return AST;
  }

  return {
    parse: function () {
      if(!src || !src.length) {
        throw "Cannot parse empty pattern";
      }

      var AST = stage1([]);
      // Clear trailing whitespace
      clear();

      if(index !== src.length) {
        throw "Expected end of input but tokens found: " + src.substring(index);
      }
      return AST;
    }
  };
};
/*--------------------------------------------------------------------*/

/**
 * makeCompiler
 * generates a matcher function from a previously generated AST.
 */

/*--------------------------------------------------------------------*/
mm.makeCompiler = function (AST) {
  "use strict";
  var bindingContext = {};

  /**
   * curry takes a function, and a partial list of arguments and returns
   * a function that can be executed with the rest of the arguments.
   *
   * --> var max = curry(Math.max, [2,5]);
   * --> max(4);
   * --> 5
   */
  function curry(fun, args) {
    return function (x) {
      return fun.apply(bindingContext, args.concat([x]));
    };
  }

  /**
   * Thanks to Kangax for the isArray function.
   * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
   */
  function is(o, type) {
    return Object.prototype.toString.call(o) === type;
  }

  /**
   * bind acts like a predicate but it also binds a variable to a
   * name and puts it in the binding context. The parameters are:
   * n: the binding name
   * p: the actual predicate associated with this binding
   * v: the value that this function is executed on.
   *
   * The first two parameters have to be parameterized by
   * the comiler (curry). The fourth parameter is passed in at
   * runtime.
   */
  function bind(n,p,v) {
    var m = p(v);
    if(m.result) {
       // Throw an exception if the same name is bound multiple times.
      if(bindingContext.hasOwnProperty(n)) {
        throw "Name '" + n + "' is already used in another binding.";
      }

      bindingContext[n] = m.obj ? m.obj[m.param] : m.param;
    }

    /**
     * When the rest of an array is matched, the binding value has to
     * be changed after executing bind. Thats because at bind time the
     * rest of the array is not known. Therefore the name of the last
     * binding is stored and can be retrieved in the continuing function.
     */
    m.this_binding = n;
    return m;
  }

  /* Predicates */

  /* matches anything */
  function any(x) {
    return {
      result: true,
      param: x
    };
  }

  /* matches the rest of an array */
  function rest(x) {
    return {
      result: true,
      param: x,
      rest: true
    };
  }

  function matchEmptyArray(a) {
    return {
      result: a.length === 0,
      param: a
    };
  }

  /* matches exactly the same value */
  function equals(x,o) {
    return {
      result: x === o,
      param: x
    };
  }

  /* matches a value (o) against a regex (x) */
  function matchesRegex(x,o) {
    return {
      result: x.test(o),
      param: x
    };
  }

  /* matches exactly the same date object */
  function equalsDate(x,o) {
    return {
      result: x.getTime() === o.getTime(),
      param: x
    };
  }

  function matchInstanceOf(t,o) {
    return {
      result: is(o, t),
      param: o
    };
  }

  function matchType(t,o) {
    return {
      result: typeof o === t,
      param: o
    };
  }

  function matchNonBlankString(x) {
    return {
      result: typeof x === 'string' && x,
      param: x
    };
  }

  /**
   * Tests wether an object has a given property.
   *
   * The arguments are:
   * m: (optional) type predicate
   * x: the name of the property
   * o: the object to test
   * r: true if the property could be found, false if not.
   */
  function testProperty(m,x,o,r) {
    /**
      * If m.length is > 0, then an additional type descriptor
      * for a property was specified. [0] because a property
      * can only be characterized by one type descriptor.
      */
    return {
      result: m.length ? r && m[0](o[x]).result : r,
      param: x,
      obj: o
    };
  }

  /* Match a property that is owned by the object */
  function hasProperty(m,x,o) {
    return testProperty(m,x,o, o.hasOwnProperty(x));
  }

  /* Match a property somewhere in the prototype chain */
  function hasPrototypeProperty(m,x,o) {
    return testProperty(m,x,o, x in o);
  }

  /**
   * or takes a list of predicate functions (m) and executes them on
   * it's second parameter (o). If one of the predicates return true
   * then or returns true. Otherwise false.
   */
  function or(m,o) {
    var index, result = {
      result: false,
      param: o
    };

    for(index=0;index<m.length;index++) {
      if(m[index](o).result) {
        result.result = true;
        break;
      }
    }
    return result;
  }

  // m is an array of predicates, a is the array to match
  function matchArray(m,a) {
    var from = 0, rest = false, restBindingResult, index, matcher, item,
    matchResult, restOfArray = [], i, result = {
      result: false,
      param: a
    };

    // If this isn't an array then it can't match
    if (!is(a, '[object Array]')) {
      return result;
    }

    // If there are more matchers than array elements it also can't match unless the
    // last matcher is a rest matcher
    if (m.length > a.length && !m[m.length - 1].name) {
      return result;
    }

    /**
     * If there are no predicates at all, this matches because it is
     * already ensured that argument a is an array.
     */
    if(m.length === 0) {
      result.result = true;
      return result;
    }

    for(index=0;index<a.length;index++) {
      matcher = m[index];
      item = a[index];

      if(!matcher) {
        return result;
      }

      matchResult = matcher(item);
      if(!matchResult.result) {
        return result;
      }

      /**
       * If the rest of an array is matched, the predicate will
       * return an object that has a'rest' parameter. We can't
       * recognize the rest predicate by it's function name, because
       * it might be hidden behind a 'bind' call.
       */
      if(matchResult.rest) {
        restBindingResult = matchResult;
        from = index;
        rest = true;
        break;
      }
    }

    if(rest && restBindingResult.this_binding) {
      for(i = from; i < a.length; i++) {
        restOfArray[restOfArray.length] = a[i];
      }
      bindingContext[restBindingResult.this_binding] = restOfArray;
    }

    result.result = true;
    return result;
  }

  function matchObject(m,o) {
    var result = {
      result: true,
      param: o
    },
    index;

    if(is(o, '[object Array]')) {
      result.result = false;
      return result;
    }

    if(typeof o !== 'object') {
      result.result = false;
      return result;
    }

    for(index=0;index<m.length;index++) {
      if(!m[index](o).result) {
        result.result = false;
        return result;
      }
    }
    return result;
  }

  // Compile a single level of the AST
  function compileNode(ast) {
    var result = [], index, node, matcher;

    for(index=0;index<ast.length;index++) {
      node = ast[index];

      switch(node.type) {
        case 'a':
          matcher = curry(matchArray, [compileNode(node.nodes)]);
          break;
        case 'o':
          matcher = curry(matchObject, [compileNode(node.nodes)]);
          break;
        case '.':
          matcher = curry(hasProperty, [compileNode(node.nodes), node.name ]);
          break;
        case ':':
          matcher = curry(hasPrototypeProperty, [compileNode(node.nodes), node.name ]);
          break;
        case '=':
          matcher = curry(equals, [node.nodes[0].type]);
          break;
        case 'd=':
          matcher = curry(equalsDate, [node.nodes[0].type]);
          break;
        case 'r=':
          matcher = curry(matchesRegex, [node.nodes[0].type]);
          break;
        case '||':
          matcher = curry(or, [compileNode(node.nodes)]);
          break;
        case 'n':
          matcher = curry(matchType, ['number']);
          break;
        case 's':
          matcher = curry(matchType, ['string']);
          break;
        case 'S':
          matcher = matchNonBlankString;
          break;
        case 'b':
          matcher = curry(matchType, ['boolean']);
          break;
        case 'f':
          matcher = curry(matchType, ['function']);
          break;
        case '_':
          matcher = any;
          break;
        case '|':
          matcher = rest;
          break;
        case '()':
          matcher = matchEmptyArray;
          break;
        case 'd':
          matcher = curry(matchInstanceOf, ['[object Date]']);
          break;
        case 'r':
          matcher = curry(matchInstanceOf, ['[object RegExp]']);
          break;
        default:
          throw "Unknown AST entity: " + node.type;
      }

      // Bind requested. Wrap the matcher function with a call to bind.
      if (node.binding) {
        matcher = curry(bind, [node.binding, matcher]);
      }

      result[result.length] = matcher;
    }
    return result;
  }

  return {
    compile: function () {
      var matchFunction = compileNode(AST)[0];

      return function (obj) {
        // Each pattern function call must start with an empty context.
        bindingContext = {};
        return {
          result: matchFunction (obj).result,
          context: bindingContext
        };
      };
    }
  };
};
/*--------------------------------------------------------------------*/
mm.target = (typeof window === 'undefined' && exports) ? exports : mm;
mm.target.version = mm.version;

mm.target.match = (function () {
  "use strict";
  var buffer = {}, matchFunction, AST, result, handler;

  // ['o', 42] => {o: 42}
  function arrayToObject(array) {
    var obj = {}, i;
    if (array.length % 2 !== 0) {
      throw "Missing handler for pattern";
    }

    for (i = 0; i < array.length; i+=2) {
      obj[array[i]] = array[i+1];
    }
    return obj;
  }

  function match(candidate, patterns, opts) {
    // >=0.1.2: patterns and handlers may also be supplied as array
    if (({}).toString.call(patterns) === '[object Array]') {
      patterns = arrayToObject(patterns);
    }

    var pattern, result;
    for (pattern in patterns) {
      if(patterns.hasOwnProperty(pattern)) {
        if (buffer.hasOwnProperty(pattern)) {
          matchFunction = buffer[pattern];
        } else {
          AST = mm.makeParser(pattern).parse();
          matchFunction = mm.makeCompiler(AST).compile();
          buffer[pattern] = matchFunction;
        }

        result = matchFunction (candidate);
        if(result.result) {
          handler = patterns[pattern];
          return typeof handler === 'function'
            ? ((opts && opts.arrow) ? handler(result.context) : handler.call(result.context))
            : handler;
        }
      }
    }

    /**
     * An exceptions is thrown if no patterns matched. Can be prevented
     * by matching '_'.
     */
     throw "Non-exhaustive patterns";
  }

  return function (c,p,o) {
    return match(c,p,o);
  };
})();

mm.target.compile = function (p) {
  "use strict";
  return mm.makeCompiler(mm.makeParser(p).parse()).compile();
};

mm.target.matchJSON = function (c,p,o) {
  "use strict";
  return mm.match(JSON.parse(c), p,o);
};


'use strict';

import StreamReader from '@emmetio/stream-reader';
import Node from './lib/node';
import tag from './lib/tag';
import comment from './lib/comment';
import cdata from './lib/cdata';
import { eatArray, toCharCodes } from './lib/utils';

export const defaultOptions = {
	/**
	 * Expect XML content in searching content. It alters how should-be-empty
	 * elements are treated: for example, in XML mode parser will try to locate
	 * closing pair for `<br>` tag
	 * @type {Boolean}
	 */
	xml: false,

	special: ['script', 'style'],

	/**
	 * List of elements that should be treated as empty (e.g. without closing tag)
	 * in non-XML syntax
	 * @type {Array}
	 */
	empty: ['img', 'meta', 'link', 'br', 'base', 'hr', 'area', 'wbr','col', 'embed', 'input', 'param', 'source', 'track']
};

/**
 * Parses given content into a DOM-like structure
 * @param  {String|StreamReader} content
 * @param  {Object} options
 * @return {Node}
 */
export default function parse(content, options) {
	options = Object.assign({}, defaultOptions, options);
	const stream = typeof content === 'string'
		? new StreamReader(content)
		: content;

	const root = new Node(stream, 'root');
	const empty = new Set(options.empty);
	const special = options.special.reduce(
		(map, name) => map.set(name, toCharCodes(`</${name}>`)), new Map());
	const isEmpty = (token, name) =>
		token.selfClosing || (!options.xml && empty.has(name))

	let m, node, name, stack = [root];

	while (!stream.eof()) {
		if (m = match(stream)) {
			name = getName(m);

			if (m.type === 'open') {
				// opening tag
				node = new Node(stream, 'tag', m);
				last(stack).addChild(node);
				if (special.has(name)) {
					node.close = consumeSpecial(stream, special.get(name));
				} else if (!isEmpty(m, name)) {
					stack.push(node);
				}
			} else if (m.type === 'close') {
				// closing tag, find it’s matching opening tag
				for (let i = stack.length - 1; i > 0; i--) {
					if (stack[i].name.toLowerCase() === name) {
						stack[i].close = m;
						stack = stack.slice(0, i);
						break;
					}
				}
			} else {
				last(stack).addChild(new Node(stream, m.type, m));
			}
		} else {
			stream.next();
		}
	}

	return root;
}

/**
 * Matches known token in current state of given stream
 * @param  {ContentStreamReader} stream
 * @return {Token}
 */
export function match(stream) {
	// fast-path optimization: check for `<` code
	if (stream.peek() === 60 /* < */) {
		return comment(stream) || cdata(stream) || tag(stream);
	}
}

/**
 * @param  {StreamReader} stream
 * @param  {Number[]} codes
 * @return {Token}
 */
function consumeSpecial(stream, codes) {
	const start = stream.pos;
	let m;

	while (!stream.eof()) {
		if (eatArray(stream, codes)) {
			stream.pos = stream.start;
			return tag(stream);
		}
		stream.next();
	}

	stream.pos = start;
	return null;
}

/**
 * Returns name of given matched token
 * @param  {Token} tag
 * @return {String}
 */
function getName(tag) {
	return tag.name ? tag.name.value.toLowerCase() : `#${tag.type}`;
}

function last(arr) {
	return arr[arr.length - 1];
}


'use strict';

import { eatQuoted, isSpace, isQuote } from '@emmetio/stream-reader-utils';
import eatPaired from './paired';
import token from './token';

const SLASH        = 47;  // /
const EQUALS       = 61;  // =
const RIGHT_ANGLE  = 62;  // >

/**
 * Consumes attributes from given stream
 * @param {StreamReader} stream
 * @return {Array} Array of consumed attributes
 */
export default function(stream) {
	const result = [];
	let name, value, attr;

	while (!stream.eof()) {
		stream.eatWhile(isSpace);
		attr = { start: stream.pos };

		// A name could be a regular name or expression:
		// React-style – <div {...props}>
		// Angular-style – <div [ng-for]>
		if (attr.name = eatAttributeName(stream)) {
			// Consumed attribute name. Can be an attribute with name
			// or boolean attribute. The value can be React-like expression
			if (stream.eat(EQUALS)) {
				attr.value = eatAttributeValue(stream);
			} else {
				attr.boolean = true;
			}
			attr.end = stream.pos;
			result.push(attr);
		} else if (isTerminator(stream.peek())) {
			// look for tag terminator in order to skip any other possible characters
			// (maybe junk)
			break;
		} else {
			stream.next();
		}
	}

	return result;
}

/**
 * Consumes attribute name from current location
 * @param  {StreamReader} stream
 * @return {Token}
 */
function eatAttributeName(stream) {
	return eatPaired(stream) || token(stream, isAttributeName);
}

/**
 * Consumes attribute value from given location
 * @param  {StreamReader} stream
 * @return {Token}
 */
function eatAttributeValue(stream) {
	const start = stream.pos;
	if (eatQuoted(stream)) {
		// Should return token that points to unquoted value.
		// Use stream readers’ public API to traverse instead of direct
		// manipulation
		const current = stream.pos;
		let valueStart, valueEnd;

		stream.pos = start;
		stream.next();
		valueStart = stream.start = stream.pos;

		stream.pos = current;
		stream.backUp(1);
		valueEnd = stream.pos;

		const result = token(stream, valueStart, valueEnd);
		stream.pos = current;
		return result;
	}

	return eatPaired(stream) || eatUnquoted(stream);
}

/**
 * Check if given code belongs to attribute name.
 * NB some custom HTML variations allow non-default values in name, like `*ngFor`
 * @param  {Number}  code
 * @return {Boolean}
 */
function isAttributeName(code) {
	return code !== EQUALS && !isTerminator(code) && !isSpace(code);
}

/**
 * Check if given code is tag terminator
 * @param  {Number}  code
 * @return {Boolean}
 */
function isTerminator(code) {
	return code === RIGHT_ANGLE || code === SLASH;
}

/**
 * Eats unquoted value from stream
 * @param  {StreamReader} stream
 * @return {Token}
 */
function eatUnquoted(stream) {
	return token(stream, isUnquoted);
}

/**
 * Check if given character code is valid unquoted value
 * @param  {Number}  code
 * @return {Boolean}
 */
function isUnquoted(code) {
	return !isNaN(code) && !isQuote(code) && !isSpace(code) && !isTerminator(code);
}


/*
 * Emotional
 *
 * Node.js subjectivity and polarity/sentiment analysis tool
 * Partial port from the pattern.en python library: http://www.clips.ua.ac.be/pages/pattern-en of the University of Antwerp
 * All credits go to the original writer: Tom de Smedt
 */

var _            = require("underscore");
var _str         = require("underscore.string");

var fs           = require("fs");
var xml2js       = require("xml2js");
var xmlParser    = new xml2js.Parser();
var find_tokens  = require("tokepi");
var EMOTICONS    = require("emotional-emoticons");

var MOOD  = "mood";  // emoticons, emojis
var IRONY = "irony"; // sarcasm mark (!)

/*
 * Sentiment Constructor
 * To be instantiated for a certain language
 */
function Sentiment(args) {
  this.path = args.path || "";
  this.language = args.language;
  this.confidence = args.confidence || null;
  this.synset = args.synset;
  this.synsets = {};
  this.labeler = {};
  this.negations = def(args.negations, ["no", "not", "n't", "never"]);
  this.modifiers = def(args.modifiers, ["RB"]);
  this.modifier = def(args.modifier, function (w) { return _str.endsWith(w, "ly"); });
  this.tokenizer = def(args.tokenizer, find_tokens);
}

// Load sentiment db path if given, otherwise load standard file provided by the language
Sentiment.prototype.load = function(path, finish) {
  if (_.isFunction(path)) {
    finish = path;
    path = undefined;
  }

  var self = this;
  path = path || this.path;
  getXml(path, function (xml) {
    xml = xml.sentiment;
    var words = {};
    var synsets = {};
    var labels = {};
    xml.word.forEach(function (word) {
      word = word.$;

      // skip if confidence threshold set and word is not belong threshold
      if (_.isNull(self.confidence) || self.confidence <= parseFloat(def(word.confidence, 0.0))) {
        var w = word.form;
        var pos = word.pos;
        var p = def(word.polarity, 0.0);
        var s = def(word.subjectivity, 0.0);
        var i = def(word.intensity, 1.0);
        var label = word.label;
        var synset = word[self.synset]; // wordnet_id, cornetto_id, ...
        var psi = [parseFloat(p), parseFloat(s), parseFloat(i)];

        if (!_.isUndefined(w)) {
          setDefault(setDefault(words, w, {}), pos, []).push(psi);
        }
        if ((!_.isUndefined(w)) && (!_.isUndefined(label))) {
          labels[w] = label;
        }
        if (!_.isUndefined(synset)) {
          setDefault(synsets, synset, []).push(psi);
        }
      }
    });
    self.language = xml.$.language || self.language;

    // Average scores of all word senses per part-of-speech tag.
    Object.keys(words).forEach(function (w) {
      Object.keys(words[w]).forEach(function (pos) {
        words[w][pos] = _.zip.apply(_, words[w][pos]).map(avg);
      });
    });

    // Average scores of all part-of-speech tags.
    Object.keys(words).forEach(function (w) {
      words[w][null] = _.zip.apply(_, _.values(words[w])).map(avg);
    });

    // Average scores of all synonyms per synset.
    Object.keys(synsets).forEach(function (id) {
      var psi = synsets[id];
      synsets[id] = _.zip.apply(_, psi).map(avg);
    });

    self.words = words;
    self.labeler = labels;
    self.synsets = synsets;
    finish();
  });
};

// Add given word with part-of-speach to the database with given polarity, subjectivity and intensity.
Sentiment.prototype.annotate = function (w, pos, p, s, i, label) {
  var self = this;
  var entry = setDefault(self.words, w, {});
  entry[pos] = entry[null] = [p, s, i];
  if (!_.isUndefined(label)) {
    self.labeler[w] = label;
  }
};

// Sentiment.prototype.getSynset = function (id, pos) {
//   var self = this;
//   pos = pos || ADJECTIVE;
//   if (_.keys(self.words).length === 0) {
//       throw Error("No sentiment corpus loaded");
//     }

//   id = _str.pad(id.toString(), 8, "0");
//   if (! (_str.startsWith(id, "n-") &&
//          _str.startsWith(id, "v-") &&
//          _str.startsWith(id, "a-") &&
//          _str.startsWith(id, "r-"))) {
//     switch (pos) {
//       case NOUN:
//         id = "n-" + id;
//         break;
//       case VERB:
//         id = "v-" + id;
//         break;
//       case ADJECTIVE:
//         id = "a-" + id;
//         break;
//       case ADVERB:
//         id = "r-" + id;
//         break;
//     }

//     var syn = self.synsets[id];
//     if (_.isUndefined(syn)) {
//       syn = def(self.synsets[id.replace(/-0+/, "-")], [0.0, 0.0]);
//     }
//     return syn.slice(0,2);
//   }

// };

function avgAssessment(assessments, weighted) {
    var w;
    var s = 0;
    var n = 0;
    assessments.forEach(function (ws) {
      w = weighted(ws[0]);
      s += w * ws[1];
      n += w;
    });
    if (n === 0) {
      return 0;
    } else {
      return s / n;
    }
  }

// Return the subjectivity and polarity/sentiment of given string
Sentiment.prototype.get = function (s, negation, weight) {
  var self = this;
  weight = def(weight, (function () { return 1; }));
  var a;

  if (_.keys(self.words).length === 0) {
    throw Error("No sentiment database is loaded, please call 'load' first.");
  }

  if (!_.isString(s)) {
    throw new Error("unknown input " + s + " only know sentences of type string");
  }

  var tokens = self.tokenizer(s);
  a = self.assessments(tokens.join(" ").split(" ").map(function (w) { return [w.toLowerCase(), null]; }), negation);

  return {
    polarity: avgAssessment(a.map(function (w) { return [w[0], w[1]]; }), weight),
    subjectivity: avgAssessment(a.map(function (w) { return [w[0], w[2]]; }), weight),
    assessments: a
  };
};

// Returns an array of [chunk, polarity, subjectivity, label] arrays for the given vector of words:
// where chunk is a vector of successive words: a known word optionally
// preceded by a modifier ("very good") or a negation ("not good").
Sentiment.prototype.assessments = function (words, negation) {
  var self = this;
  var prev, w, p, s, x;
  negation = _.isUndefined(negation) ?  true : negation;
  var a = [];
  var m = null; // Preceding modifier (i.e., adverb or adjective).
  var n = null; // Preceding negation (e.g., "not beautiful").
  words.forEach(function (wp) {
    var w = wp[0];
    var pos = wp[1];

    // will return arrays where indexes are as follows:
    // 0 = "w", 1 = "p", 2 = "s", 3 = "i", 4 = "n", 5 = "x"

    // Only assess known words, preferably by part-of-speech tag.
    // Including unknown words (polarity 0.0 and subjectivity 0.0) lowers the average.
    if (_.isNull(w)) return;

    // If we know the word from the sentimental corpus
    var entry = self.words[w];
    if ((!_.isUndefined(entry)) && (!_.isUndefined(entry[pos]))) {
      var p = entry[pos][0];
      var s = entry[pos][1];
      var i = entry[pos][2];

      // Known word not preceded by a modifier, e.g "good".
      if (_.isNull(m)) {
        a.push([[w], p, s, i, 1, self.labeler[w]]);
      }

      prev = a[a.length-1];

      // Known word preceded by a modifier, e.g. "really good".
      if (!_.isNull(m)) {
        prev[0].push(w);
        prev[1] = Math.max(-1.0, Math.min(p * prev[3], +1.0));
        prev[2] = Math.max(-1.0, Math.min(s * prev[3], +1.0));
        prev[3] = i;
        prev[5] = self.labeler[w];
      }

      // Known word preceded by a negation, e.g. "not really good".
      if (!_.isNull(n)) {
        prev[0] = [n].concat(prev[0]);
        prev[3] = 1.0 / prev[3];
        prev[4] = -1;
      }

      // Known word may be a negation.
      // Known word may be modifying the next word (i.e., it is a known adverb).
      m = null;
      n = null;
      if ((!_.isUndefined(pos)) &&
          (!_.isUndefined(self.modifiers[pos])) ||
          _.any(self.modifiers.map(function (modifier) { return !_.isUndefined(entry[modifier]); }))) {
        m = [w, pos];
      }
      if (negation && (!_.isUndefined(self.negations[w]))) {
        n = w;
      }

    // Unknown word
    } else {

      // negation
      if (negation && (!_.isUndefined(self.negations[w]))) {
        n = w;

      // Retain negation across small words ("not a good").
      } else if ((!_.isNull(n)) && _str.strip(w, "'").length > 1) {
        n = null;
      }

      // May be a negation preceded by a modifier ("really not good").
      if ((!_.isNull(n)) && (!_.isNull(m)) &&
                ((!_.isUndefined(self.modifiers[pos])) || (!_.isUndefined(self.modifier(m[0]))))) {
        prev = a[a.length-1];
        prev[0].push(n);
        prev[4] = -1;

      // Retain modifier across small words ("really is a good").
      } else if ((!_.isNull(m)) && (w.length > 2)) {
        m = null;
      }

      // Exclamation mark boosts previous word
      if (w == "!" && a.length > 0) {
        prev = a[a.length-1];
        prev[0].push("!");
        prev[1] = Math.max(-1.0, Math.min(prev[1] * 1.25, +1.0));
      }

      // Exclamation marks in parentheses indicate sarcasm.
      if (w == "(!)") {
        a.push([[w], 0.0, 1.0, 1.0, 1, IRONY]);
      }

      // EMOTICONS: {("grin", +1.0): set((":-D", ":D"))}
      // if ((!w.match(/^[0-9]+$/i)) && (w.length <= 5) && _str.include(PUNCTUATION, w)) {
        Object.keys(EMOTICONS).forEach(function (type) {
          if (_.contains(EMOTICONS[type].e, w.toLowerCase())) {
            a.push([[w], EMOTICONS[type].p, 1.0, 1.0, 1, MOOD]);
          }
        });
      // }

    }
  });

  for (var i=0; i<a.length; i++) {
    w = a[i][0];
    p = a[i][1];
    s = a[i][2];
    n = a[i][4];
    x = a[i][5];
    // "not good" = slightly bad, "not bad" = slightly good.
    a[i] = [w, (n < 0 ? (p * -0.5) : p), s, x];
  }

  return a;
};

Sentiment.prototype.positive = function (s, threshold) {
  threshold = def(threshold, 0.1);
  var result = this.get(s);
  return (result.polarity >= threshold);
};

// Initialize the sentiment analyser for english text
var sentiment = new Sentiment({path: __dirname + "/en/en-sentiment.xml", synset: "wordnet_id"});
sentiment.load = function load(callback) {
  var self = this;
  Sentiment.prototype.load.call(self, function () {
    Object.keys(self.words).forEach(function (w) {
      Object.keys(self.words[w]).forEach(function (pos) {
        var nw = w;
        if (pos === "JJ") {
          if (_str.endsWith(w, "y")) {
            nw = w.slice(0, w.length-1) + "i";
          }
          if (_str.endsWith(w, "le")) {
            nw = w.slice(0, w.length-2);
          }
          var entry = self.words[w][pos];
          var p = entry[0];
          var s = entry[1];
          var i = entry[2];
          self.annotate(nw+"ly", "RB", p, s, i);
        }
      });
    });
    callback();
  });
};

/*
 * Auxiliary Functions
 */

// Average of number vector (0 if empty)
function avg(vct) {
  if (vct.length === 0) {
    return 0;
  }
  return (vct.reduce(function (a, c) { return a + c; }, 0) / vct.length);
}

// Returns value if value is defined, otherwise defValue.
function def(value, defValue) {
  if (_.isUndefined(value)) {
    return defValue;
  }
  return value;
}

// If given key is set in the object it returns the associated value,
// otherwise it sets the value to val and returns it.
function setDefault(obj, key, val) {
  if (_.isUndefined(obj[key])) {
    obj[key] = val;
    return val;
  }
  return obj[key];
}

// Read and Parse XML file from given path, pass result to finish
// Any error that occurs is simpy thrown.
function getXml(path, finish) {
  fs.readFile(path, function(err, data) {
      if (err) throw err;
      xmlParser.parseString(data, function (err, result) {
        if (err) throw err;
        finish(result);
      });
  });
}

module.exports = sentiment;


/*
 * grunt-htmlcompressor
 * https://github.com/jney/grunt-htmlcompressor
 *
 * Copyright (c) 2012 Jean-Sébastien Ney
 * Licensed under the MIT license.
 */

/*global _:true, __dirname */

var _ = require('lodash');

module.exports = function(grunt) {
  'use strict';

  grunt.registerMultiTask('htmlcompressor', 'Compress html files', function() {
    var options = this.options();

    grunt.verbose.writeflags(options, 'Options');

    var done = this.async();
    var jar = __dirname + '/../ext/htmlcompressor-1.5.3.jar';
    var processName = options.processName;
    var concurrent = parseInt(options.concurrentProcess, 10) > 0 ? options.concurrentProcess : 50;
    var running = 0;
    var srcFiles = [];

    delete options.processName;
    delete options.concurrentProcess;

    var compress = function (done) {
      if (srcFiles.length > 0) {
        var file = srcFiles.pop();
        var args = _.flatten(['-jar', jar, _.map(options, toParameter), file.src]);
        running++;
        grunt.util.spawn({
          cmd: 'java',
          args: args
        }, function(err, output, code) {
          if (err) {
            grunt.log.error(err);
            grunt.fail.warn('htmlcompressor failed to compress html.');
            done(err);
          } else {
            var html = output.stdout;
            var dest = _.isFunction(processName) ?
              processName(file.src, html) : file.dest;
            grunt.file.write(dest, html);
            grunt.log.writeln('File "' + dest + '" created.');
            running--;
            compress(done);
          }
        });
      } else {
        if (running < 1) {
          done();
        }
      }
    };

    this.files.forEach(function(file) {
      var src;

      if (_.isFunction(file.src)) {
        src = file.src();
        src.forEach(function(inFile){
          srcFiles.push({src: inFile, dest: file.dest});
        });
      } else {
        file.src.forEach(function(inFile){
          srcFiles.push({src: inFile, dest: file.dest});
        });
      }
    });
    for (var p = 0; p <= concurrent; p++) {
      compress(done);
    }
  });

  // Convert a pair of key/value to an array
  // if the value is `true` only the key is kept
  //
  // Example:
  //
  //   toParameter('lineBreak', 2)
  //   // => ['--line-break', 2]
  //
  //   toParameter('preserveComments', true)
  //   // => ['--preserve-comments']
  function toParameter(val, key) {
    var str = '--' + key.replace(/([A-Z])/g, function(a) {
      return '-' +  a.toLowerCase();
    });

    return (val === true) ? [str] : [str, val];
  }
};


var path= require("path"),traverse = require("traverse"),hooks= require("hooks"), Q = require("q");
var fs = require("fs"),logging = require("log4js"), _ =require("underscore");
var domain = require("domain");
var toolbox = require("./util/toolbox");
var async = toolbox.async;
var logger = logging.getLogger('angoose');
var Context = require("./Context");
var Exception = require("./Exception");

/**@todo: move to mongoose plugin */
toolbox.patchMongoCallback();

function Pipeline(){}
for (var k in hooks) { Pipeline[k] = hooks[k];}

var proto = Pipeline.prototype;

proto.authorize= function authroize(invocation, authorizeBack){
    logger.debug("STEP 100: pipline.authorize");

    if(invocation.allowed !== undefined) authorizeBack && authorizeBack();
    /** default implementation always grant access */
    var defaultMode = angoose().config()['authorization-mode'];
    invocation.allowed =  (defaultMode !== 'deny-all');
    authorizeBack && authorizeBack();
}

proto.resolveTarget = function resolveTarget(invocation,  resolveBack){
    logger.debug("STEP 200: pipline.resolveTarget");
    /**logger.debug("Preparing target from JSON");*/

    if(invocation.target){
        logger.debug("Skipping default: resolveTarget");
        return resolveBack();
    }

    if(!invocation.static && typeof(invocation.instance) != 'object')
        return resolveBack(  new Error("Missing invocation instance property" ));

    var modelClass = angoose().getClass( invocation.clazz );
    if(invocation.static){
        invocation.target = modelClass;
        return resolveBack();
    }

    invocation.target = new modelClass(invocation.instance);
    return resolveBack( );
}

proto.resolveArguments = function resolveArguments(invocation, resolveArgBack){
    logger.debug("STEP 300: pipline.resolveArguments");
    //async(resolveArgBack)(false, invocation.args);
    if(invocation.arguments !== undefined){
        logger.debug("Skipping default: resolveArguments");
        return resolveArgBack();
    }
    invocation.arguments = invocation.args;
    resolveArgBack ();
}

proto.invoke = function invoke(invocation , invokeBack){
    //logger.debug("STEP 400: pipline.invoke");
    if(invocation.results !== undefined){
        logger.debug("Skipping default: invoke");
        return invokeBack();
    }
    var method =  invocation.method;
    var seqnum = invocation.seqnumber;
    var target = invocation.target;
    var args = invocation.arguments;

    var useCallback  = false;

    function callbackHandler(err, result){
        /** converting to Q promise */
        logger.trace("In invoke callback handler #"+seqnum, err);
        if(err){
            logger.debug("Method invocation returned error:", err);
            //return deferred.reject(err);
            err = err instanceof Error? err: new Error(err);
            return invokeBack( err);
        }
        logger.trace("Method invocation result:", result)
        invocation.result = result;
        invokeBack();
    }
    if( invocation.method == 'save' && args.indexOf("$callback")<0  ){
        args.push('$callback'); // hack for now
    }

    useCallback =  injectDependencies(target[method], args,  callbackHandler);
    logger.debug("invoking method ",  method, "arguments", args );

    if(!target[method]) return invokeBack(new Error('Method '+ method+" is not defined on module "+ invocation.clazz));
    var ret = target[method].apply(target,  args);

    logger.trace("invoking result", ret);
    if(ret instanceof Error) return callbackHandler(ret);

    if(Q.isPromise(ret) || (ret && typeof(ret.done) == 'function')){
        ret.done(function(result){
            callbackHandler(false, result);
        }, function(err){
            callbackHandler(err);
        });
    }
    else if(!useCallback || ret !== undefined) {
        /** if method returns data directly or returned undefined */
        return callbackHandler(false, ret);
    }
}; /**  end invoke */

proto.redact = function redact(invocation, redactBack){
    logger.debug("STEP 500: pipline.redact");
    if(invocation.redacted !== undefined){
        logger.debug("Skipping default: redact");
        return redactBack();
    }
    invocation.redacted = invocation.result;
    redactBack();
}

proto.pack = function pack(invocation,  packBack){
    logger.debug("STEP 600: pipline.pack");
    if(invocation.packed !== undefined){
        logger.debug("Skipping default: pack");
        return packBack();
    }

    var target = invocation.target;
    var redacted = invocation.redacted;
    var valueType = getValueType(redacted);  /** model, models, object, string*/
    var retdata = { success:true,
                    exception:null,
                    retval:redacted,
                    datatype: valueType,
                    seqnumber:invocation.seqnumber
    };
    if( !invocation.static && (! _.isEqual(target.toJSON(), invocation.target)))
    {
        /** return the instance data if it has changed */
        retdata.instance = target.toJSON();
    }
    invocation.packed = retdata;
    packBack();
}

proto.formatError = function formatError(invocation, formatBack){
    if(invocation.packed) return formatBack();
    var ex = invocation.exception;
    ex.invocation = invocation.clazz +"."+ invocation.method;
    ex.seqnumber = invocation.seqnumber;
    invocation.packed = {
         success:false,
         exception:ex,
         seqnumber: invocation.seqnumber
    }
    formatBack();
}
function getValueType(obj){
    if(!obj || typeof(obj) == 'string') return 'string';
    return 'object';
}
//hookIt('authorize', proto.authorize);

function injectDependencies(func, args, callback ){

    var declaredArguments = toolbox.parseDeclaredArguments(func);
    declaredArguments = declaredArguments.replace(/\s+/g, "").split(",");
    logger.trace("injectDependencies() declaredArguments: ", declaredArguments, "provided args:", args);
    var useCallback = false;
    var len = args.length > declaredArguments.length? args.length:declaredArguments.length;
    for(var i=0;i< len ;i++){ // this is problematic: functions may be wrapped and lost the arguments data
        /** if client did not provide enough number of arguments, we fill it with null*/
        if(typeof( args[i]) == 'undefined')
            args[i] = undefined;
        else if(args[i] === "$callback"){
            useCallback = true;
            args[i] = callback;
        }
        switch(declaredArguments[i]){
            case '$callback':
                logger.debug("Injecting callback handler");
                args[i] = callback;
                useCallback = true;
                break;
             case '$context':
                logger.debug("Injecting angoose context");
                args[i] = angoose().getContext();
                break;
             default:
                break;
        }
    }
    return useCallback;
}

function angoose(){
    return require("./angoose");
}

/**
 * Main execution method
 *
 * @param invocation Invocation object
 */
Pipeline.execute = function(ctx, invocation, callback){
        ['allowed', 'target', 'arguments','result','redacted'].forEach(function(key){
            delete invocation[key];
        });

        var dmain = domain.create();
        dmain.context = ctx;
        //var excutionStorage = storageFactory.createNamespace("angoose");
        /** here comes the main body of the processing */
        dmain.run(function(){
                var pipeline = new Pipeline();
                var seqnum = invocation.seqnumber;
                var sent = false;
                logger.debug("------ BEGIN RMI #", seqnum, invocation.clazz, invocation.method );
                logger.trace("Invocation object: ",invocation);
                //invocation.method = req.params.method;  /** method must be part of the path for routes permission etc */
                ctx.seqnum = seqnum;
                ctx.invocation = invocation;

                function handleError(err, exName){
                    if(sent){
                        logger.debug("Response already sent")
                        return;
                    }
                    /**@todo: err may cause Circular issue when converting to JSON */
                    var ex = new Exception(err, exName);
                    invocation.exception = ex;

                    pipeline.formatError(invocation, function(er){
                        logger.debug("====== END RMI with Error  #", seqnum,  ex);
                        callback(invocation.packed);
                        sent  = true;
                    });
                }
                dmain.on('error',  function uncaught(ex){
                    logger.error("Uncaught error in invocation #"+seqnum, ex);
                    /**@todo: restart server if too many errors */
                    handleError(  "Unexpected Server Error", 'RuntimeError');
                });

                /** setting execution context */
                var sent = false;

                var modelName = invocation.clazz;
                var seqnum = invocation.seqnumber;
                //excutionStorage.set("context", ctx);
                pipeline.authorize(invocation,   function(err){
                    if(err) return handleError(err);
                    if(! invocation.allowed) return handleError("Access Denied", "AuthError");

                    pipeline.resolveTarget(invocation,   function(err){
                        if(err) return handleError(err);

                        if(!invocation.target) return handleError("Target not resolved.", "ModuleNotFoundError");

                        pipeline.resolveArguments(invocation,function(err){
                            if(err) return handleError(err);

                            if(!invocation.arguments) return handleError("Arguments not resolved.");
                            pipeline.invoke(invocation, function(ex){

                                if(ex) return handleError(ex);
                                pipeline.redact(invocation, function(err){
                                    if(err) return handleError(err);
                                    pipeline.pack(invocation, function(err){
                                        if(err) return handleError(err);
                                        //res.send(200, packedData);
                                        callback(invocation.packed);
                                        sent = true;
                                        logger.debug("====== END RMI Succeeded #", seqnum);
                                        if(Array.isArray(invocation.packed && invocation.packed.retval) && invocation.packed.retval.length>0){
                                            logger.trace("Response is an array with length:",  invocation.packed.retval.length, "First element:",invocation.packed.retval[0] );
                                        }
                                        else logger.trace("Response data is ", invocation.packed);

                                    });
                                });
                            });
                        });
                    });
                });
                /** catch the strayed handling, returns error in 5 seconds if response not sent */
               var tmout = setTimeout(function(){
                   if(sent) return;

                   var msg = "Timeout occurred when processing call "+invocation.clazz +"." + invocation.method;
                   logger.error(msg,  seqnum);
                   handleError(msg);
                   //res.send(200, packError( {message: msg, value: msg, code:500 }, invocation.seqnumber ));
               }, angoose().config().request_timeout || 5000);
               tmout.unref();

        });/** end context closure */
}

Pipeline.accept = function accept(req, res){
    var invocation = decode(req.body);
    var ctx = new  Context({request:req, response:res} )
    Pipeline.execute(ctx, invocation, function(data){
        // traverse(data).forEach(function(){
            // //if(this.circular) this.remove(); // circular ref will cause JSON.stringify fail
        // });
        res.send(200, data);
    });
}

Pipeline.hookables = 'authorize,resolveTarget,resolveArguments,invoke,redact,pack,formatError'.split(",");
toolbox.addHookPoints(Pipeline);
module.exports =  Pipeline;

function decode(obj){
    /** due to Angular HTTP library's stupidity, keys starting with $ are ignored. hence _mongo_$ to work around this*/
    if(!obj || typeof obj != 'object') return obj;
    if(Array.isArray(obj)){
        for(var i=0;i<obj.length;i++){
            obj[i] = decode(obj[i]);
        };
    }
    else{
        Object.keys(obj).forEach(function(key){
            var val = decode(obj[key]);
            if(key.indexOf('_mongo_$')==0){
                delete obj[key];
                key = key.substring(7)
            }
            obj[key] = val;
        })
    }
    return obj;
}


/**
    This extension is responsible to piggy back the angoose.ui angular sources to the angoose-client.js.
    This is meant to be temporary - should use grunt task to do these concatenations.
 */

var path = require("path"), fs= require("fs");
var ui = {
    afterCreateBundle: appendUISource
};

var angoose = require("../../lib/angoose");
angoose.extension("AngooseUI", ui);

// append angoose-ui sources
//@todo: call grunt task api to concat/minify the source & templates
function appendUISource(client){
    angoose.getLogger('angoose').debug("Appending angoose-ui sources");
//    client.source += " \n;console.log('####################----------- angoose-ui -----------##############');\n";
    var output="";
    output += readFile(path.resolve(__dirname, 'angular-modules.js'));
    output += concatFilesInDirectory(['services',  'controllers', 'directives', 'filters']); //'directives',
    output +=  concatTemplates();
    client.source+="\n\n" + output;
}

function concatTemplates(){
    // read default templates
    var templates = {};
    var files = fs.readdirSync( path.resolve(__dirname, 'tpl') );
    files.forEach(function(filename){
         var tmp = readFile( path.resolve(__dirname, 'tpl', filename), "tpl/"+filename);
         templates[filename] = tmp;
    });
    // read user defined templates
    var userDir=angoose.config("angoose-ui-template-dir")
    if(userDir){
        try{
            var userfiles = fs.readdirSync( path.resolve(process.cwd(), userDir));
            userfiles.forEach(function(filename){
                var tmp = readFile( path.resolve(process.cwd(), userDir,filename), userDir+"/"+filename);
                templates[filename] = tmp;
            });
        }
        catch(ex){
            angoose.getLogger('angoose').error(ex);
        }
    }
    angoose.getLogger('angoose').debug("preprocessing templates: ", Object.keys(templates).length);
    return  $angooseTemplateCache.toString().replace("{/**TEMPLATES*/}", JSON.stringify(templates));
}

function concatFilesInDirectory( dirname){
    var dirs = Array.isArray(dirname)? dirname : [dirname];
    var output = '';
    dirs.forEach(function(dir){
        var absDir = path.resolve(__dirname, dir);
        var files = fs.readdirSync( absDir );
        files.forEach(function(filename){
            angoose.getLogger('angoose').trace("concating file", filename)
            output+= readFile( path.resolve(absDir, filename), dir+"/"+filename);
        });
    });
    return output;
}
function readFile(filename, relativeName){
        var output="/******* angoose-ui filename: "+ (relativeName || filename) +"   *******/\n";
        if(filename.indexOf(".tpl")>0)
            output =  "<!------------- angoose-ui filename: "+ (relativeName || filename) +" -->";
        output+=fs.readFileSync(filename, 'ascii');
        output+="\n\n";
        return output;
}

//@todo: properly implement it according to angular $templateCache
function $angooseTemplateCache(name, content){

    $angooseTemplateCache.templates = $angooseTemplateCache.templates || {/**TEMPLATES*/}; // this will be replaced
    if(!name) return;
    if(content){
        $angooseTemplateCache.templates[name]  = content;
    }
    else {
        return $angooseTemplateCache.templates[name];
    }
}


#!/usr/bin/env node

/*

C Preprocessor, CLI

© 2017 -
	Guillaume Gonnet
	Al Zohali

License GPLv2

Sources at https://github.com/ParksProjets/C-Preprocessor

*/

// Libraries
var fs = require('fs'),
	path = require('path'),
	compiler = require('../index.js'),
	argv = require('./argv-parser.js');

// Start time
var startTime = Date.now();

// Local options
var options;
var inputFile;
var outputFile;

// Show an error
function error(msg, addHint) {
	console.log('\x1b[31m');
	console.log('The compiler has stopped on an error')
	console.log(`\x1b[1;31mError: ${msg}\x1b[0m`);

	if (addHint)
		console.log(`\nPlease use -h to show the usage`);

	process.exit(1);
}

// Argv parser error
argv.error = function(msg) {
	error(msg, true);
};

// Help argument
argv.add('h', 'help', 0, function help() {
	console.log('\n  Usage: c-preprocessor [options] input-file [output-file]');
	console.log('\n  Options:');
	console.log('\n    -c, --config  configuration file');

	process.exit(1);
});

// Version argument
argv.add('v', 'version', 0, function version() {
	var json = require('../package.json');
	console.log('\n  C-Preprocessor version: ' + json.version);
	process.exit(1);
});

// Configuration file
argv.add('c', 'config', 1, function(arr) {
	try {
		options = JSON.parse(fs.readFileSync(arr[0], 'utf8'));
	} catch(e) {
		error("Can't read configuration file");
	}
});

// Start parsing argv
argv.parse();

// Input & output files
inputFile = argv.argv[0];
outputFile = argv.argv[1];

// If the input file is not specified
if (inputFile === undefined)
	error("input file are required", true);

// If the output file is not specified
if (outputFile === undefined) {
	var parsed = path.parse(inputFile),
		dir = parsed.dir ? parsed.dir+'/' : '',
		name = parsed.name,
		ext = parsed.ext;

	outputFile = dir + name + '-built' + ext;
}

// Preprocess input file
var c = new compiler.Compiler(options);

c.on('error', function(err) {
	return error(err);
});

c.on('success', function(code) {
	fs.writeFile(outputFile, code, function(err) {

		if (err)
			return error(`Unable to write the output file "${outputFile}"`);

		// Display the time spent
		console.log('\x1b[1;32m');
		console.log(`Done in ${(Date.now() - startTime) / 1000}s\x1b[0m`);
	});
});

c.compileFile(inputFile);


/** Angoose Client Core */
/**  depdnencies: http, Q */
(function BootstrapAngoose() {

    AngooseClient = function(name) {
        if ( typeof $anget != 'undefined') {
            return $anget(camelcase(name));
        }
        return AngooseClient.getClass(name)
    };

    var logger = getLogger();
    logger.setLevel(config('logging') || 'INFO');

    AngooseClient.getClass = getClass;
    AngooseClient.model = getClass;
    AngooseClient.modelNames = modelNames;
    AngooseClient.init = init;
    AngooseClient.module = getClass;
    AngooseClient.config = config;
    AngooseClient.logger = logger;

    if ( typeof (angoose) == 'undefined') {
        angoose = AngooseClient;
    }

    logger.info("Bootstraping angoose-client");

    function getLogger() {
        var levels = {
            TRACE : {
                v : 1,
                name : "TRACE"
            },
            DEBUG : {
                v : 2,
                name : "DEBUG"
            },
            INFO : {
                v : 3,
                name : "INFO"
            },
            WARN : {
                v : 4,
                name : "WARN"
            },
            ERROR : {
                v : 5,
                name : "ERROR"
            },
            LOG : {
                v : 6,
                name : "LOG"
            }
        };

        var ClientLogger = {
            levels : levels,
            level : levels.INFO
        }
        var methods = "log,info,trace,debug,warn,error".split(",");
        for(var i=0; i < methods.length; i++){
            var method = methods[i];
            if(typeof(console)!='undefined' && console[method])
                ClientLogger[method]= getLoggingFunc(method);
            else
                ClientLogger[method]=function(){}
        };
        function getLoggingFunc(method){
            return function(){
                if(ClientLogger.level.v <=  levels[method.toUpperCase()].v ){
                    var args = []; args.push( method.toUpperCase() + ":");
                    for(var i =0;i<arguments.length;i++) args.push(arguments[i]);
                    if(method == 'trace')
                        console.debug.apply(console,args);
                    else
                        console[method].apply(console,args)
                }
            }
        }
        ClientLogger.setLevel=function(level){
            if(!level) return;
            level = level.toUpperCase();
            ClientLogger.level = levels[level] || levels.INFO;
        }
        return ClientLogger;
    }

    var counterBase = Math.round(Math.random() * 10000) * 10000;
    var nextCounter = function() {
        return counterBase++;
    }
    var encode$ = function(obj) {
        if (!obj || typeof obj != 'object')
            return obj;
        if (obj && obj.length) {
            for (var i = 0; i < obj.length; i++) {
                obj[i] = encode$(obj[i]);
            };
        } else {
            //logger.debug("Type", (typeof obj), obj)
            for (var key in obj) {
                var val = encode$(obj[key]);
                if (key.indexOf('$') == 0) {
                    delete obj[key];
                    key = "_mongo_" + key;
                }
                obj[key] = val;
            }
        }
        return obj;
    }
    var autoThrower = null;
    var handleReturn = function(retval, callback, deferred, error) {
        if (error) {
            if ( typeof (error) !== 'object')
                error = {
                    message : error
                }
            error.toString = function() {
                this.consumed = true;
                return this.message || Object.toString.call(this);
            }
        }
        if (callback)
            return callback(error, retval);
        return error ? deferred.reject(error) : deferred.resolve(retval);
    }
    function staticInvoker(modelClass, methodName, methodArgs) {
        return invoker(modelClass, methodName, methodArgs, null);

    };
    function instanceInvoker(modelClass, model, methodName, methodArgs) {
        //var modelClass = getClass(model.classname$)
        return invoker(modelClass, methodName, methodArgs, model);
    }

    function invoker(modelClass, methodName, methodArgs, modelInstance) {
        // modelInstance can be null, everything else is required.
        var modelName = modelClass.modelName;
        logger.trace("invoker: ", modelName, methodName, methodArgs);
        var callback = null;
        var isStatic = modelInstance == null;
        var depends = modelClass.dependencies$;
        var data = {
            method : methodName,
            seqnumber : nextCounter(),
            args : [],
            clazz : modelName,
            static : isStatic
        }
        for (var i = 0; methodArgs && methodArgs.length > i; i++) {
            if ( typeof methodArgs[i] != 'function')
                data.args.push(methodArgs[i]);
            else {
                callback = methodArgs[i]
                data.args.push("$callback")
                logger.debug("Callback provided");
            }
        }
        if (!isStatic) {
            data.instance = modelInstance;
            // including model instance data for instance methods
        }

        // angular http ignores all key names starting with $, this will break the mongo query
        data = encode$(data);
        var callname = modelName + "." + methodName;
        logger.debug("****** BEGIN RMI #", data.seqnumber, callname, data);
        var http = depends['http'];
        var theQ = depends['promise'];
        if (!http || !theQ)
            throw "Missing http and/or Q dependencies";
        var deferred = theQ.defer();
        var ret = http.post((getConfigs().urlPrefix || '/angoose') + "/rmi/" + modelName + "/" + methodName, data);
        ret.done(function(retdata) {
            logger.debug("====== END RMI #", data.seqnumber, callname, " Result:", retdata);
            var val = undefined;
            //@todo construct object, ret value types:  1) model data, 2) list of model data, 3) String, 4) object
            if (retdata && retdata.retval && (retdata.datatype == 'model' || retdata.datatype == 'models')) {

                if (retdata.datatype == 'model') {
                    val = newInstance(modelClass, retdata.retval);
                } else {
                    var models = [];
                    for (var i = 0; i < retdata.retval.length; i++) {
                        models.push(newInstance(modelClass, retdata.retval[i]));
                    }
                    val = models;
                }
                //return handleReturn(val, callback, deferred);
                //
                //return deferred.resolve(models);
            }
            if (retdata && retdata.instance && !isStatic) {
                // state has been changed on the server side
                modelInstance.mergeData(retdata.instance);
                logger.trace("Merged server side data", modelInstance);
            }
            if (retdata.success)
                return handleReturn(val || retdata.retval, callback, deferred);
            var ex = retdata.exception ? retdata.exception : retdata;
            return handleReturn(null, callback, deferred, ex);
            //deferred.reject(ex)
        }, function(errdata) {
            logger.debug("====== END RMI #", data.seqnumber, callname, " Error:", errdata);
            if (!errdata)
                return handleReturn(errdata, callback, deferred, "Unexpected server error occurred.");
            var ex = errdata && errdata.exception ? errdata.exception : {
                message : errdata
            };
            //var ex = errdata.exception?  (errdata.exception.message || errdata.exception.value) : errdata;
            return handleReturn(null, callback, deferred, ex);
            //return deferred.reject(ex);
        })
        if (!callback)
            return deferred.promise;
    }

    function newInstance(clazz, jsonData) {
        if (jsonData && jsonData.__t && jsonData.__t !== clazz.name) {
            clazz = getClass(jsonData.__t) || clazz;
            //@todo: __t handling is not long term solution
        }
        return new clazz(jsonData);
    }

    function createProxy(module, funcName, func, funcType) {
        if ( typeof (func) == 'function')
            return func;
        if ( typeof (func) == "string" && func.indexOf("function") == 0) {
            var vname;
            return eval("vname=" + func);
        }
        if (funcType == 'static') {
            return function proxy() {
                return staticInvoker(module, funcName, arguments);
            }
        };
        if (funcType == 'instance') {
            return function proxy() {
                return instanceInvoker(module, this, funcName, arguments);
            }
        };

    }

    /** compile the model based on the server side schema */
    function compile(modelName, schema, dependencies) {
        logger.trace("Compiling schema ", modelName)
        var model = function AngooseModule(data) {
            //@todo proper clone
            for (var i in data) {
                this[i] = data[i];
            }
        };

        model.toString = function() {
            return "PROXY: function " + modelName + "()";
        }
        // static methods
        for (var name in schema.statics) {
            model[name] = createProxy(model, name, schema.statics[name], 'static');
        }
        for (var name in schema.methods) {
            model.prototype[name] = createProxy(model, name, schema.methods[name], 'instance');
        }

        //model.angoose$ = staticInvoker;
        model.dependencies$ = dependencies;
        model.schema = schema;
        //model.prototype.angoose$ = instanceInvoker;
        //model.prototype.classname$ = modelName;
        //model.prototype.schema$ = schema;
        model.prototype.get = getter;
        model.prototype.set = setter;
        model.modelName = modelName; // this is to be compatible with backend mongoose
        model.name = modelName;

        // merge data into this instance
        model.prototype.mergeData = function(source) {
            if ( typeof source != "object")
                throw "Invalid source object, must be an model instance";
            //@todo: proper implementation
            for (var i in source) {
                this[i] = source[i];
            }
        }
        AngooseClient.models = AngooseClient.models || {};
        AngooseClient.models[modelName] = model;
        return model;
    };

    function modelNames() {
        var ret = [];
        for (var key in getSchemas()) {
            ret.push(key);
        }
        return ret;
    };

    function config(path, val) {
        var options = getConfigs();
        if(!path) return options; /**@todo: probably a deep copy */

        if(typeof (path) === 'string'){
             if(val === undefined)
                return  getter.call(options, path);
             setter.call(options, path, val);
        }
    }

    function init(dependencies) {
        if (AngooseClient.initialized) {
            logger.debug("init has been called");
            return;
        }
        AngooseClient.dependencies = dependencies;
        logger.debug("Creating client side proxies for backend modules");
        for (var mName in getSchemas()) {
            compile(mName, getSchemas()[mName], dependencies);
        }
        AngooseClient.initialized = true;
    };
    function getClass(name) {
        if (!AngooseClient.initialized)
            throw "Angoose models not initialized yet";
        name = camelcase(name);
        return AngooseClient.models[name];
    };

     function getter(path) {
         if(!path) return undefined;
          var   pieces = path.split('.');
          var obj = this;
          for (var i = 0, l = pieces.length; i < l; i++) {
            obj = undefined === obj || null === obj
              ? undefined
              : obj[pieces[i]];
          }
          return obj;
    };
    function  setter(path, val){
    if(!path  || typeof(path)!='string') return;
     var   pieces = path.split('.');
      var obj = this;
      for (var i = 0, len = pieces.length; i < len; i++) {
          if(i+1  == len ) // last one
          {
              obj[ pieces[i]] = val;
              return;
          }
          obj[pieces[i]] = obj[pieces[i]] || {};
          obj = obj[pieces[i]] || {};
      }
    }

    function camelcase(name, space) {
        // converting client-user to ClientUser
        if (!name)
            return name;
        var parts = name.split("-");
        name = "";
        for (var i = 0; i < parts.length; i++) {
            if (parts[i] && parts[i].length > 0) {
                name = name && space ? name + " " : name;
                name += parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1);
            }
        }
        return name;
    }

    function getConfigs() {
        return /**CONFIG_PLACEHOLDER*/
    };
    function getSchemas() {
        return /**SCHEMA_PLACEHOLDER*/
    };
})();


import table from 'markdown-table'
import {
  getDefaultValue,
  getKey,
  getType,
  getTypeName,
  filterProps,
  isFlowType,
} from './helpers'
import { describeSubTypes } from './types'

const TABLE_HEADERS = ['Name', 'Type', 'Default', 'Required', 'Description']

/**
 * Loop through all the top level props
 * @param props
 * @param options
 * @returns {string}
 */
function addProps(props, options) {
  if (!props) return '## No props'
  const keys = Object.keys(props).filter(key =>
    filterProps(key, props[key], options),
  )
  const filteredProps = keys.reduce(
    (last, key) => ({ ...last, [key]: props[key] }),
    {},
  )

  let output = '\n## Props\n'
  let isFlow = false
  const items = [
    TABLE_HEADERS,
    ...keys.map(key => {
      const prop = filteredProps[key]
      if (isFlowType(prop)) isFlow = true

      const row = [
        isFlowType(prop) ? key : getKey(key, getType(prop)),
        getTypeName(getType(prop)),
        getDefaultValue(prop),
        prop.required,
        prop.description,
      ]
      return row.map(rowValue => {
        if (typeof rowValue === 'string') {
          return rowValue.split('\n').join('<br>')
        }
        return rowValue
      })
    }),
  ]

  output += `${table(items)}\n`

  // Add subtypes
  if (!isFlow) {
    const subTypes = describeSubTypes(filteredProps)
    if (subTypes.length) {
      output += '\n## Complex Props\n'
      output += subTypes
    }
  }

  return output
}

function normalizeOptions(options) {
  return {
    excludeKey: toRegExp(options.excludeKeys || options.excludeKey),
    excludeType: toRegExp(options.excludeTypes || options.excludeType),
    excludeDescription: toRegExp(
      options.excludeDescription || options.excludeDescription || '@internal',
    ),
  }
}

function toRegExp(input) {
  if (!input) return null
  if (Array.isArray(input)) return new RegExp(`(${input.join('|')})`, 'i')

  return new RegExp(input, 'i')
}

/**
 *
 * @param api {object} The react-docgen output
 * @param name {string} Name/title to append on the page
 * @param options {Object}
 * @param options.excludeKeys {string|Array|RegExp} Prop keys to exclude from the docs. "children" will prevent children from showing up.
 * @param options.excludeTypes {string|Array|RegExp} Prop types to exclude from the docs. "Node" will prevent Node types from showing up.
 * @param options.excludeDescription {string|Array|RegExp} If description includes matching word, the item will not be included. Default is "@internal".
 *
 * @returns {string} Markdown document
 */
function docsToMarkdown(api, name = '', options = {}) {
  let output = ''
  if (name) output += `# ${name}\n`
  if (api.description) output += `\n${api.description}\n\n`
  output += addProps(api.props, normalizeOptions(options))

  return output
}

module.exports = docsToMarkdown

export default docsToMarkdown


;(function(){

var serviceProvider = function () {
    var uiOptions ={
        '$query-error': 'throw'  // throw | alert | none, used to specify what happens when there is error loading data
    };
    var service = {
            getReference: getRef,
            getPathSchema:getPathSchema,
            debounce:debounce,
            //initQuery:initQuery,
            //defineQuery:initQuery,
            isCustomRef:isCustomRef,
            apiCamelcase:apiCamelcase,
            camelcase:camelcase,
            decamelcase:decamelcase,
            extractTemplate:extractTemplate,
            getter:getter,
            setter:setter,
            getCustomRefKeyfieldPath:getCustomRefKeyfieldPath,
            getCustomRefValue:getCustomRefValue,
            filterPath:filterPath,
            resolveAttribute: resolveAttribute,
            getFieldSchema:getFieldSchema
    }
    function getFieldSchema(scope){
        if(scope.fieldSchema) return scope.fieldSchema;
        if(!scope.path) return null;
        var modelClass = scope.instance && scope.instance.constructor;
        var modelSchema = scope.modelSchema || (modelClass && modelClass.schema);
        return modelSchema && modelSchema.paths[scope.path];
    }
    this.$get = function ($http, $templateCache, $q,$compile,  AngooseForm, AngooseQuery, angoose) {
            service.loadFieldTemplate = function(fieldTemplate){
                fieldTemplate = fieldTemplate.replace(".html", "");
                fieldTemplate = 'deform.field.' + fieldTemplate+".tpl";
                return this.loadTemplate( fieldTemplate);
            }
            service.loadTemplate = function(templateName){
                angoose.logger.trace("Loading template", templateName)
                var deferred = $q.defer();
                var html = $angooseTemplateCache(templateName);
                if(html){
                    deferred.resolve( angular.element(html));
                    return deferred.promise;
                }
                angoose.logger.trace("$http loading template", templateName);
                return $http.get(templateName, {cache:  $templateCache }).then(function(response) {
                    //console.log(response.data, "got template $http")
                  return angular.element(response.data);
                }, function(response) {
                  throw new Error('Template not found: ' + templateName);
                  //console && console.error("teamplte note found", templateName)
                });
            };
            // service.resolveTemplateUrl = function(templateName){
                // var templateUrl = deformOptions.templateDir +"/"+ templateName +".tpl.html";
                // return templateUrl;
            // }
            service.defineForm = function($scope, options){
                //@todo: use auto merge/extend
                var formSpec =  $scope.$form;
                if(!formSpec)
                    formSpec = new AngooseForm($scope);
                formSpec.update(options);
                return formSpec;
            };
            service.initQuery = service.defineQuery = function($scope, options){
                //@todo: use auto merge/extend
                var querySpec =  $scope.dmeta;
                if(!querySpec)
                    querySpec = new AngooseQuery($scope);
                querySpec.update(options);
                return querySpec;
            };

            service.resolveTemplate = function resolveTemplate($element, $attrs, config, defaultTemplateUrl){
                // templateUrl could be specified in the directive <ang-edit template-url='xxx' template='xxx'>
                 /** template resolving order
                 //todo: consolidate with field directive template

                 1)  config.template  // config is from custom controller, if available
                 2)  config.templateUrl
                 3)  $attrs.templateUrl
                 4)   inline template
                 */
                var inline = $element.html();
                var template = config && config['template'];
                var url = config && config['templateUrl'];
                url = url || $attrs.templateUrl;

                var deferred = $q.defer();

                if(url || template) inline = "";  // ignore inline template if template/templateUrl is specified

                if(!inline && !template && !url){
                    // no template specified anywhere, use the default
                    angoose.logger.trace("Using default template", defaultTemplateUrl);
                    url = defaultTemplateUrl;
                }
                var em = '';
                if(template){
                    // template is specified as string content
                    angoose.logger.trace("Compiling template content");
                    em = angular.element(template);
                }
                else if(url){
                    // second order
                    console.log("loadTemplate", url);
                    return  service.loadTemplate(url);
                }
                else{
                    // inline doesn't need special handling
                    angoose.logger.trace("Did not find configured template, using inline template" );
                    em = "";
                }
                deferred.resolve(em); // nextTick?
                return deferred.promise;
            };
            service.resolveAndCompile = function($scope, $element, $attrs, config, defaultTemplate){
                angoose.logger.trace("Resolving template[default: ", defaultTemplate+"]");
                service.resolveTemplate($element, $attrs, config, defaultTemplate).then(function(em){
                    angoose.logger.trace("resolveTemplate completed[default: ", defaultTemplate+"]. Got content: ", !!em);
                    if(em){
                        $element.html("<!-- CLEARED -->"); // first clear the inline template
                        $element.append(em);
                        $compile(em)($scope);
                    }
                }, function(err){
                    console.error("Error resolving template(list.tpl)", err);
                });
            }
            return service;
    };
    this.config = function(name, val){
        if(typeof(name) === 'object')
            uiOptions = angular.extend(uiOptions, opts)
        else if(name){
            if( val === undefined) return getter(uiOptions, name);
            setter(uiOptions, name, val)
        }
    };

};

angular.module('angoose.ui.services').provider('$ui', serviceProvider).provider('$deform', serviceProvider).run(['$ui','$rootScope', function($ui, $rootScope){
    $rootScope.defineQuery = function(meta){
        return $ui.defineQuery(this, meta);
    };
    $rootScope.defineForm = function(meta){
        return $ui.defineForm(this, meta);
    }
}]);

// a helper function to determine the attribute value based on following order:
//  $scope (from custom controller) -> $routeParam -> directive
function resolveAttribute(name, $scope, $routeParams, $attrs ){
    return getter($scope.dmeta ? $scope.dmeta: $scope.$form,  name) ||  getter($routeParams, name) ||  $attrs[name];
}
function filterPath(path, data, schema){

    if(data.options.editable === false) return true;
    if(schema && schema.options && schema.options.discriminatorKey == path) return true;
    if(path.indexOf("-")>0 || path == 'type') return true; /** cannot handle hyphen */

    if(getter(data, "schema.options.editable") === false) {
        return true
    }
    return false;
}
function setter(doc, path, val){
    if(!path || !doc ) return;
     var   pieces = path.split('.');
      var obj = doc;
      for (var i = 0, len = pieces.length; i < len; i++) {
          if(i+1  == len ) // last one
          {
              obj[ pieces[i]] = val;
              return;
          }
          obj[pieces[i]] = obj[pieces[i]] || {};
          obj = obj[pieces[i]] || {};
      }
}
function getter(obj, p){
    if(!p || !obj) return undefined;
    var p = p.split('.');
    var o = obj;
    for (var i = 0; i < p.length; i++){
        if(!o || typeof o != "object") return undefined;
        o = o[p[i]]
    }
    return o;
}
function getRef(pathSchema){
    var opts = pathSchema.options;

    /** Mongoose, array of ObjectIDs */
    if( Array.isArray(opts.type) && opts.type.length>0  &&  opts.type[0] && opts.type[0].ref )
        return opts.type[0].ref;
    /** Single ObjectID Reference*/
    if(pathSchema.instance == 'ObjectID' && opts.ref) return opts.ref;

    if(pathSchema.options.ref && pathSchema.instance == 'CustomRef'){
        /** deform custom ref*/
       return pathSchema.options.ref;
    }
    /** deform rich reference, array */
    if(Array.isArray(opts.type ) && pathSchema.caster &&
            pathSchema.caster.instance == 'CustomRef' && pathSchema.caster.options.ref ){
        return pathSchema.caster.options.ref;
    }

    return null;
}
function isCustomRef(pathSchema){
    return pathSchema && (pathSchema.instance == 'CustomRef' || (pathSchema.caster && pathSchema.caster.instance == 'CustomRef'));
}

function getCustomRefKeyfieldPath(pathSchema){
    if(!isCustomRef(pathSchema)) return undefined;
    return pathSchema.path +"." + (getter(pathSchema, 'options.keyField') || 'name');
}
function getCustomRefValue(instance, pathSchema){
    var path = getCustomRefKeyfieldPath(pathSchema);
    if(!path) return undefined;
    var name = getter(instance, path);
    if(path.indexOf("sortable")>=0)
        return formatSortable(name);
    if(name && (name.first || name.last)) name =( name.first || "")+" "+ (name.last || "");
    return name;
}

function apiCamelcase(name){
    if(!name) return name;
    var name = camelcase(name);
    return name.substring(0,1).toLowerCase() + name.substring(1);
}
function camelcase(name){
    // converting client-user to ClientUser
    if(!name) return name;
    var parts = name.split("-");
    name = "";
    for(var i=0;i< parts.length;i++){
        if(parts[i] && parts[i].length>0) name+= parts[i].substring(0,1).toUpperCase() + parts[i].substring(1);
    }
    return name;
}
function decamelcase(name){
    // convert ClientUser to client-user
    if(!name) return name;
    var ret = "";
    for(var i=0;i<name.length;i++){
        var c = name.charAt(i);
        if(c.toLowerCase() != c && ret.length>0) ret+="-"
        ret += c;
    }
    return ret.toLowerCase();
}
function trim(str){
    return str? str.replace(/^\s*(.*?)\s*$/, "$1"): str;
}
function formatSortable(sortable){
    if(!sortable || typeof(sortable)!=='string') return sortable;
    if(sortable.indexOf(",")<0) return sortable;
    sortable = trim(sortable.toLowerCase()).replace(/null/i, '');
    var parts = sortable.split(",");
    if(parts.length != 2)
        return sortable;
    if(parts.length >1){
        return  camelcase(trim(parts[1])) +" "+ camelcase( trim(parts[0]));
    }
    return sortable;
}

function getPathSchema(modelClass, path){
    var modelSchema = modelClass.schema;
    return modelSchema.paths && modelSchema.paths[path];
}

/**
 * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
 * within the last quietMillis milliseconds.
 *
 * @param quietMillis number of milliseconds to wait before invoking fn
 * @param fn function to be debounced
 * @param ctx object to be used as this reference within fn
 * @return debounced version of fn
 */
function debounce(quietMillis, fn, ctx ) {
    ctx = ctx || undefined;
    var timeout;
    return function () {
        var args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function() {
            fn.apply(ctx, args);
        }, quietMillis);
    };
}

function extractTemplate(f){
      return f.toString().
          replace(/^[^\/]+\/\*!?/, '').
          replace(/\*\/[^\/]+$/, '');
}

})();


// ## Toolbax
//
// Utility functions

var _ = require("underscore");

exports.camelcase = function cammelcase(name, space){
    // converting client-user to ClientUser
    if(!name) return name;
    var parts = name.split("-");
    name = "";
    for(var i=0;i< parts.length;i++){
        if(parts[i] && parts[i].length>0){
            name = name && space ? name+" ":name;
            name+= parts[i].substring(0,1).toUpperCase() + parts[i].substring(1);
        }
    }
    return name;
}

exports.decamelcase = function decamelcase(name){
    // convert ClientUser to client-user
    if(!name) return name;
    var ret = "";
    for(var i=0;i<name.length;i++){
        var c = name.charAt(i);
        if(c.toLowerCase() != c && ret.length>0) ret+="-"
        ret += c;
    }
    return ret.toLowerCase();
}

// **async**
//
// Wrap a function so that it is called asynchronously.
// This is necessary to ensure a call back is always asynchronous.
exports.async = function async(fn, scope){
    return function(){
        var args = arguments
        process.nextTick(function(){
            fn.apply(scope, args)
        })
    }
};

exports.getter = function (doc, path){
    if(!path || !doc) return undefined;
     var   pieces = path.split('.');
      var obj = doc;
      for (var i = 0, l = pieces.length; i < l; i++) {
        obj = undefined === obj || null === obj
          ? undefined
          : obj[pieces[i]];
      }
      return obj;
}

exports.setter = function(doc, path, val){
    if(!path || !doc ) return;
     var   pieces = path.split('.');
      var obj = doc;
      for (var i = 0, len = pieces.length; i < len; i++) {
          if(i+1  == len ) // last one
          {
              obj[ pieces[i]] = val;
              return;
          }
          obj[pieces[i]] = obj[pieces[i]] || {};
          obj = obj[pieces[i]] || {};
      }
}
exports.patchQ = function(){

    /** Workaround for Q module with CLS */
    function matroshka(fn) {
      var babushka = fn;
      Object.keys(process.namespaces).forEach(function (name) {
        babushka = process.namespaces[name].bind(babushka);
      });

      return babushka;
    }
    function patchQueue() {
      var Q = require('q');
      var proto = Q && Q.makePromise && Q.makePromise.prototype;
      function wrapperFunc(then) {
        return function nsThen(fulfilled, rejected, progressed) {
          if (typeof fulfilled === 'function') fulfilled = matroshka(fulfilled);
          if (typeof rejected === 'function') rejected = matroshka(rejected);
          if (typeof progressed === 'function') progressed = matroshka(progressed);
          return then.call(this, fulfilled, rejected, progressed);
        };
      }
      require("shimmer").wrap(proto, 'then', wrapperFunc);
      require("shimmer").wrap(proto, 'done', wrapperFunc);
    }

    patchQueue();

};

exports.patchMongoCallback = function(){
	var mongodbs = [];

	try{
        mongodbs.push( require(process.cwd() + "/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db").Db );
    }
    catch(err){
    }
	try{
		mongodbs.push( require("mongoose/node_modules/mongodb/lib/mongodb/db").Db );
	}
	catch(err){
	}
	try{
        mongodbs.push( require("mongodb/lib/mongodb/db").Db );
    }
    catch(err){
    }
    try{
        mongodbs.push( require("../mongodb/lib/mongodb/db").Db );
    }
    catch(err){
    }

	for(var i=0;i<mongodbs.length;i++){
		var mongodb = mongodbs[i];
		mongodb.prototype._executeInsertCommand = bindMongoCallback(mongodb.prototype._executeInsertCommand);
		mongodb.prototype._executeQueryCommand = bindMongoCallback(mongodb.prototype._executeQueryCommand);
		mongodb.prototype._executeUpdateCommand = mongodb.prototype._executeInsertCommand ;
		mongodb.prototype._executeRemoveCommand = mongodb.prototype._executeInsertCommand ;
	}
	var domain = require("domain");
	function bindMongoCallback(fn){
		return function(cmd, options, callback){
			if(domain.active){
				if(typeof(callback) ==='function') callback = domain.active.bind(callback);
				else if(!callback && typeof(options) === 'function') options = domain.active.bind(options);
			}
			else{
			    //console.error("DOMAIN LOST", cmd.collectionName, cmd.query);
			}
			fn.call(this, cmd, options,callback  );
		};
	};

};

exports.bindCallback = function(scope, fn){
    return function(){
        var lastArg = arguments.length? arguments[arguments.length-1]: null;
        if(typeof(lastArg) != 'function') return fn.apply(scope || this, arguments);
        var domain =require("domain");
        if(domain.active){
            arguments[arguments.length-1] = domain.active.bind(lastArg);
        }
        return fn.apply(scope || this, arguments);
    }
}
exports.addHookPoints = function(clazz, hookables){
    var hooks = require("hooks");
    hookables = hookables || clazz.hookables;
    for (var k in hooks) { clazz[k] = hooks[k];}

    _.each(hookables, function(hookable){
        clazz.hook(hookable, clazz.prototype[hookable]);
    });
    clazz.removePost = removePost;
};

exports.removeHookPoints = function(clazz, hookables){
    hookables = hookables || clazz.hookables;
    _.each(hookables, function(hookable){
        clazz.removePre(hookable).removePost(hookable);
    });
};

function removePost(name, fnToRemove) {
    var proto = this.prototype || this
      , posts = proto._posts || (proto._posts || {});
    if (!posts[name]) return this;
    if (arguments.length === 1) {
      // Remove all pre callbacks for hook `name`
      posts[name].length = 0;
    } else {
      posts[name] = posts[name].filter( function (currFn) {
        return currFn !== fnToRemove;
      });
    }
    return this;
};

/**@todo move to schema util */

exports.parseDeclaredArguments = function(funcBody){
    if(typeof funcBody === 'function') funcBody = funcBody.toString();
    if(funcBody && typeof funcBody === "string"  && funcBody.substr(0,8) == "function") {
        var startArgs = funcBody.indexOf('(') + 1;
        var endArgs = funcBody.indexOf(')');
        return  funcBody.substring(startArgs, endArgs)
    }
    return "";
}
var FunctionNamePattern = /^function\s+(remote|local|portable)/i;
exports.typeOf = function(funcBody){
    funcBody = typeof(funcBody) == 'function' ? funcBody.toString() : funcBody;
    if(funcBody.indexOf("function")!=0) return "unknown";
    var matcher = FunctionNamePattern.exec(funcBody);
    return matcher? matcher[1]: 'remote'; //default is remote
}

exports.methodType = exports.typeOf;

exports.stringifySchema = function(schemaObj ){
    //logger.trace("stringifySchema", moduleName);
    var funcs = {};
    function funcStringifier(key, value) {
        if (typeof value !== 'function') return value;
        var body =  value.toString();
        var funcType = exports.typeOf(body);
        if(!funcType) return; //'not-available';
        if(funcType.indexOf("local")>=0) return 'This method can be invoked from server side only';

        if(funcType.indexOf("portable")>=0){
            /** the function can be executed client side */
           return body;
        }
        else{
            /** stub the function */
            return '$PROXIED$'; //constructProxyFunc(key,args, funcType);
        }
    }
    return JSON.stringify( schemaObj,  funcStringifier, "\t"  );
}

function getReference(pathSchema){
    var opts = pathSchema.options;

    /** Mongoose, array of ObjectIDs */
    if( Array.isArray(opts.type) && opts.type.length>0  &&  opts.type[0] && opts.type[0].ref )
        return opts.type[0].ref;
    /** Single ObjectID Reference*/
    if(pathSchema.instance == 'ObjectID' && opts.ref) return opts.ref;

    if(pathSchema.options.ref && pathSchema.instance == 'CustomRef'){
        /** deform custom ref*/
       return pathSchema.options.ref;
    }
    /** deform rich reference, array */
    if(Array.isArray(opts.type ) && pathSchema.caster &&
            pathSchema.caster.instance == 'CustomRef' && pathSchema.caster.options.ref ){
        return pathSchema.caster.options.ref;
    }

    return null;
}

/**
 * Merge data recursively from src to dst, return dst
 * @param {Object} dst
 * @param {Object} src
 */
exports.merge = function(dst, src){
    dst = dst || {};
    src = src || {};
    var traverse = require("traverse");
    var source = traverse(src);
    var destination = traverse(dst);
    source.paths().forEach(function(path){
        var val = source.get(path);
        if(typeof(val) != 'object')
            destination.set(path, val);
    });
    return dst;
}

// Default implementation of exporting a module's methods to client
exports.exportModuleMethods = function(moduleName, mod){
    var ret = {};
    var schemaObj =  mod.schema || (mod.getSchema && mod.getSchema());
    if(!schemaObj)
        return null;
    ret.moduleName = moduleName;
    ret.methods = _.clone( schemaObj.methods ) || {};
    ret.statics = _.clone( schemaObj.statics) || {};
    return ret;
};