text: "use strict";
  
  var ValidationTypes = module.exports;
  
  var Matcher = require("./Matcher");
  
  function copy(to, from) {
      Object.keys(from).forEach(function(prop) {
          to[prop] = from[prop];
      });
  }
  copy(ValidationTypes, {
  
      isLiteral: function (part, literals) {
          var text = part.text.toString().toLowerCase(),
              args = literals.split(" | "),
              i, len, found = false;
  
          for (i=0, len=args.length; i < len && !found; i++) {
              if (args[i].charAt(0) === "<") {
                  found = this.simple[args[i]](part);
              } else if (args[i].slice(-2) === "()") {
                  found = (part.type === "function" &&
                           part.name === args[i].slice(0, -2));
              } else if (text === args[i].toLowerCase()) {
                  found = true;
              }
          }
  
          return found;
      },
  
      isSimple: function(type) {
          return Boolean(this.simple[type]);
      },
  
      isComplex: function(type) {
          return Boolean(this.complex[type]);
      },
  
      describe: function(type) {
          if (this.complex[type] instanceof Matcher) {
              return this.complex[type].toString(0);
          }
          return type;
      },
  
      /**
       * Determines if the next part(s) of the given expression
       * are any of the given types.
       */
      isAny: function (expression, types) {
          var args = types.split(" | "),
              i, len, found = false;
  
          for (i=0, len=args.length; i < len && !found && expression.hasNext(); i++) {
              found = this.isType(expression, args[i]);
          }
  
          return found;
      },
  
      /**
       * Determines if the next part(s) of the given expression
       * are one of a group.
       */
      isAnyOfGroup: function(expression, types) {
          var args = types.split(" || "),
              i, len, found = false;
  
          for (i=0, len=args.length; i < len && !found; i++) {
              found = this.isType(expression, args[i]);
          }
  
          return found ? args[i-1] : false;
      },
  
      /**
       * Determines if the next part(s) of the given expression
       * are of a given type.
       */
      isType: function (expression, type) {
          var part = expression.peek(),
              result = false;
  
          if (type.charAt(0) !== "<") {
              result = this.isLiteral(part, type);
              if (result) {
                  expression.next();
              }
          } else if (this.simple[type]) {
              result = this.simple[type](part);
              if (result) {
                  expression.next();
              }
          } else if (this.complex[type] instanceof Matcher) {
              result = this.complex[type].match(expression);
          } else {
              result = this.complex[type](expression);
          }
  
          return result;
      },
  
  
      simple: {
          __proto__: null,
  
          "<absolute-size>":
              "xx-small | x-small | small | medium | large | x-large | xx-large",
  
          "<animateable-feature>":
              "scroll-position | contents | <animateable-feature-name>",
  
          "<animateable-feature-name>": function(part) {
              return this["<ident>"](part) &&
                  !/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(part);
          },
  
          "<angle>": function(part) {
              return part.type === "angle";
          },
  
          "<attachment>": "scroll | fixed | local",
  
          "<attr>": "attr()",
  
          // inset() = inset( <shape-arg>{1,4} [round <border-radius>]? )
          // circle() = circle( [<shape-radius>]? [at <position>]? )
          // ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? )
          // polygon() = polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )
          "<basic-shape>": "inset() | circle() | ellipse() | polygon()",
  
          "<bg-image>": "<image> | <gradient> | none",
  
          "<border-style>":
              "none | hidden | dotted | dashed | solid | double | groove | " +
              "ridge | inset | outset",
  
          "<border-width>": "<length> | thin | medium | thick",
  
          "<box>": "padding-box | border-box | content-box",
  
          "<clip-source>": "<uri>",
  
          "<color>": function(part) {
              return part.type === "color" || String(part) === "transparent" || String(part) === "currentColor";
          },
  
          // The SVG <color> spec doesn't include "currentColor" or "transparent" as a color.
          "<color-svg>": function(part) {
              return part.type === "color";
          },
  
          "<content>": "content()",
  
          // https://www.w3.org/TR/css3-sizing/#width-height-keywords
          "<content-sizing>":
              "fill-available | -moz-available | -webkit-fill-available | " +
              "max-content | -moz-max-content | -webkit-max-content | " +
              "min-content | -moz-min-content | -webkit-min-content | " +
              "fit-content | -moz-fit-content | -webkit-fit-content",
  
          "<feature-tag-value>": function(part) {
              return part.type === "function" && /^[A-Z0-9]{4}$/i.test(part);
          },
  
          // custom() isn't actually in the spec
          "<filter-function>":
              "blur() | brightness() | contrast() | custom() | " +
              "drop-shadow() | grayscale() | hue-rotate() | invert() | " +
              "opacity() | saturate() | sepia()",
  
          "<flex-basis>": "<width>",
  
          "<flex-direction>": "row | row-reverse | column | column-reverse",
  
          "<flex-grow>": "<number>",
  
          "<flex-shrink>": "<number>",
  
          "<flex-wrap>": "nowrap | wrap | wrap-reverse",
  
          "<font-size>":
              "<absolute-size> | <relative-size> | <length> | <percentage>",
  
          "<font-stretch>":
              "normal | ultra-condensed | extra-condensed | condensed | " +
              "semi-condensed | semi-expanded | expanded | extra-expanded | " +
              "ultra-expanded",
  
          "<font-style>": "normal | italic | oblique",
  
          "<font-variant-caps>":
              "small-caps | all-small-caps | petite-caps | all-petite-caps | " +
              "unicase | titling-caps",
  
          "<font-variant-css21>": "normal | small-caps",
  
          "<font-weight>":
              "normal | bold | bolder | lighter | " +
              "100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900",
  
          "<generic-family>":
              "serif | sans-serif | cursive | fantasy | monospace",
  
          "<geometry-box>": "<shape-box> | fill-box | stroke-box | view-box",
  
          "<glyph-angle>": function(part) {
              return part.type === "angle" && part.units === "deg";
          },
  
          "<gradient>": function(part) {
              return part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
          },
  
          "<icccolor>":
              "cielab() | cielch() | cielchab() | " +
              "icc-color() | icc-named-color()",
  
          //any identifier
          "<ident>": function(part) {
              return part.type === "identifier" || part.wasIdent;
          },
  
          "<ident-not-generic-family>": function(part) {
              return this["<ident>"](part) && !this["<generic-family>"](part);
          },
  
          "<image>": "<uri>",
  
          "<integer>": function(part) {
              return part.type === "integer";
          },
  
          "<length>": function(part) {
              if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)) {
                  return true;
              } else {
                  return part.type === "length" || part.type === "number" || part.type === "integer" || String(part) === "0";
              }
          },
  
          "<line>": function(part) {
              return part.type === "integer";
          },
  
          "<line-height>": "<number> | <length> | <percentage> | normal",
  
          "<margin-width>": "<length> | <percentage> | auto",
  
          "<miterlimit>": function(part) {
              return this["<number>"](part) && part.value >= 1;
          },
  
          "<nonnegative-length-or-percentage>": function(part) {
              return (this["<length>"](part) || this["<percentage>"](part)) &&
                  (String(part) === "0" || part.type === "function" || (part.value) >= 0);
          },
  
          "<nonnegative-number-or-percentage>": function(part) {
              return (this["<number>"](part) || this["<percentage>"](part)) &&
                  (String(part) === "0" || part.type === "function" || (part.value) >= 0);
          },
  
          "<number>": function(part) {
              return part.type === "number" || this["<integer>"](part);
          },
  
          "<opacity-value>": function(part) {
              return this["<number>"](part) && part.value >= 0 && part.value <= 1;
          },
  
          "<padding-width>": "<nonnegative-length-or-percentage>",
  
          "<percentage>": function(part) {
              return part.type === "percentage" || String(part) === "0";
          },
  
          "<relative-size>": "smaller | larger",
  
          "<shape>": "rect() | inset-rect()",
  
          "<shape-box>": "<box> | margin-box",
  
          "<single-animation-direction>":
              "normal | reverse | alternate | alternate-reverse",
  
          "<single-animation-name>": function(part) {
              return this["<ident>"](part) &&
                  /^-?[a-z_][-a-z0-9_]+$/i.test(part) &&
                  !/^(none|unset|initial|inherit)$/i.test(part);
          },
  
          "<string>": function(part) {
              return part.type === "string";
          },
  
          "<time>": function(part) {
              return part.type === "time";
          },
  
          "<uri>": function(part) {
              return part.type === "uri";
          },
  
          "<width>": "<margin-width>"
      },
  
      complex: {
          __proto__: null,
  
          "<azimuth>":
              "<angle>" +
              " | " +
              "[ [ left-side | far-left | left | center-left | center | " +
              "center-right | right | far-right | right-side ] || behind ]" +
              " | "+
              "leftwards | rightwards",
  
          "<bg-position>": "<position>#",
  
          "<bg-size>":
              "[ <length> | <percentage> | auto ]{1,2} | cover | contain",
  
          "<border-image-slice>":
          // [<number> | <percentage>]{1,4} && fill?
          // *but* fill can appear between any of the numbers
          Matcher.many([true /* first element is required */],
                       Matcher.cast("<nonnegative-number-or-percentage>"),
                       Matcher.cast("<nonnegative-number-or-percentage>"),
                       Matcher.cast("<nonnegative-number-or-percentage>"),
                       Matcher.cast("<nonnegative-number-or-percentage>"),
                       "fill"),
  
          "<border-radius>":
              "<nonnegative-length-or-percentage>{1,4} " +
              "[ / <nonnegative-length-or-percentage>{1,4} ]?",
  
          "<box-shadow>": "none | <shadow>#",
  
          "<clip-path>": "<basic-shape> || <geometry-box>",
  
          "<dasharray>":
          // "list of comma and/or white space separated <length>s and
          // <percentage>s".  There is a non-negative constraint.
          Matcher.cast("<nonnegative-length-or-percentage>")
              .braces(1, Infinity, "#", Matcher.cast(",").question()),
  
          "<family-name>":
              // <string> | <IDENT>+
              "<string> | <ident-not-generic-family> <ident>*",
  
          "<filter-function-list>": "[ <filter-function> | <uri> ]+",
  
          // https://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property
          "<flex>":
              "none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]",
  
          "<font-family>": "[ <generic-family> | <family-name> ]#",
  
          "<font-shorthand>":
              "[ <font-style> || <font-variant-css21> || " +
              "<font-weight> || <font-stretch> ]? <font-size> " +
              "[ / <line-height> ]? <font-family>",
  
          "<font-variant-alternates>":
              // stylistic(<feature-value-name>)
              "stylistic() || " +
              "historical-forms || " +
              // styleset(<feature-value-name> #)
              "styleset() || " +
              // character-variant(<feature-value-name> #)
              "character-variant() || " +
              // swash(<feature-value-name>)
              "swash() || " +
              // ornaments(<feature-value-name>)
              "ornaments() || " +
              // annotation(<feature-value-name>)
              "annotation()",
  
          "<font-variant-ligatures>":
              // <common-lig-values>
              "[ common-ligatures | no-common-ligatures ] || " +
              // <discretionary-lig-values>
              "[ discretionary-ligatures | no-discretionary-ligatures ] || " +
              // <historical-lig-values>
              "[ historical-ligatures | no-historical-ligatures ] || " +
              // <contextual-alt-values>
              "[ contextual | no-contextual ]",
  
          "<font-variant-numeric>":
              // <numeric-figure-values>
              "[ lining-nums | oldstyle-nums ] || " +
              // <numeric-spacing-values>
              "[ proportional-nums | tabular-nums ] || " +
              // <numeric-fraction-values>
              "[ diagonal-fractions | stacked-fractions ] || " +
              "ordinal || slashed-zero",
  
          "<font-variant-east-asian>":
              // <east-asian-variant-values>
              "[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || " +
              // <east-asian-width-values>
              "[ full-width | proportional-width ] || " +
              "ruby",
  
          // Note that <color> here is "as defined in the SVG spec", which
          // is more restrictive that the <color> defined in the CSS spec.
          // none | currentColor | <color> [<icccolor>]? |
          // <funciri> [ none | currentColor | <color> [<icccolor>]? ]?
          "<paint>": "<paint-basic> | <uri> <paint-basic>?",
  
          // Helper definition for <paint> above.
          "<paint-basic>": "none | currentColor | <color-svg> <icccolor>?",
  
          "<position>":
              // Because our `alt` combinator is ordered, we need to test these
              // in order from longest possible match to shortest.
              "[ center | [ left | right ] [ <percentage> | <length> ]? ] && " +
              "[ center | [ top | bottom ] [ <percentage> | <length> ]? ]" +
              " | " +
              "[ left | center | right | <percentage> | <length> ] " +
              "[ top | center | bottom | <percentage> | <length> ]" +
              " | " +
              "[ left | center | right | top | bottom | <percentage> | <length> ]",
  
          "<repeat-style>":
              "repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}",
  
          "<shadow>":
          //inset? && [ <length>{2,4} && <color>? ]
          Matcher.many([true /* length is required */],
                       Matcher.cast("<length>").braces(2, 4), "inset", "<color>"),
  
          "<text-decoration-color>":
             "<color>",
  
          "<text-decoration-line>":
              "none | [ underline || overline || line-through || blink ]",
  
          "<text-decoration-style>":
              "solid | double | dotted | dashed | wavy",
  
          "<will-change>":
              "auto | <animateable-feature>#",
  
          "<x-one-radius>":
              //[ <length> | <percentage> ] [ <length> | <percentage> ]?
              "[ <length> | <percentage> ]{1,2}"
      }
  });
  
  Object.keys(ValidationTypes.simple).forEach(function(nt) {
      var rule = ValidationTypes.simple[nt];
      if (typeof rule === "string") {
          ValidationTypes.simple[nt] = function(part) {
              return ValidationTypes.isLiteral(part, rule);
          };
      }
  });
  
  Object.keys(ValidationTypes.complex).forEach(function(nt) {
      var rule = ValidationTypes.complex[nt];
      if (typeof rule === "string") {
          ValidationTypes.complex[nt] = Matcher.parse(rule);
      }
  });
  
  // Because this is defined relative to other complex validation types,
  // we need to define it *after* the rest of the types are initialized.
  ValidationTypes.complex["<font-variant>"] =
      Matcher.oror({ expand: "<font-variant-ligatures>" },
                   { expand: "<font-variant-alternates>" },
                   "<font-variant-caps>",
                   { expand: "<font-variant-numeric>" },
                   { expand: "<font-variant-east-asian>" });
  