// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
// limitations under the License.
(function (scope, testing) {
  // Evaluates a calc expression.
  // https://drafts.csswg.org/css-values-3/#calc-notation
  function calculate(expression) {
    // In calc expressions, white space is required on both sides of the
    // + and - operators. https://drafts.csswg.org/css-values-3/#calc-notation
    // Thus any + or - immediately adjacent to . or 0..9 is part of the number,
    // e.g. -1.23e+45
    // This regular expression matches ( ) * / + - and numbers.
    var tokenRegularExpression = /([\+\-\w\.]+|[\(\)\*\/])/g;
    var currentToken;

    function consume() {
      var matchResult = tokenRegularExpression.exec(expression);
      if (matchResult) currentToken = matchResult[0];else currentToken = undefined;
    }

    consume(); // Read the initial token.

    function calcNumber() {
      // https://drafts.csswg.org/css-values-3/#number-value
      var result = Number(currentToken);
      consume();
      return result;
    }

    function calcValue() {
      // <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )
      if (currentToken !== '(') return calcNumber();
      consume();
      var result = calcSum();
      if (currentToken !== ')') return NaN;
      consume();
      return result;
    }

    function calcProduct() {
      // <calc-product> = <calc-value> [ '*' <calc-value> | '/' <calc-number-value> ]*
      var left = calcValue();

      while (currentToken === '*' || currentToken === '/') {
        var operator = currentToken;
        consume();
        var right = calcValue();
        if (operator === '*') left *= right;else left /= right;
      }

      return left;
    }

    function calcSum() {
      // <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
      var left = calcProduct();

      while (currentToken === '+' || currentToken === '-') {
        var operator = currentToken;
        consume();
        var right = calcProduct();
        if (operator === '+') left += right;else left -= right;
      }

      return left;
    } // <calc()> = calc( <calc-sum> )


    return calcSum();
  }

  function parseDimension(unitRegExp, string) {
    string = string.trim().toLowerCase();
    if (string == '0' && 'px'.search(unitRegExp) >= 0) return {
      px: 0
    }; // If we have parenthesis, we're a calc and need to start with 'calc'.

    if (!/^[^(]*$|^calc/.test(string)) return;
    string = string.replace(/calc\(/g, '('); // We tag units by prefixing them with 'U' (note that we are already
    // lowercase) to prevent problems with types which are substrings of
    // each other (although prefixes may be problematic!)

    var matchedUnits = {};
    string = string.replace(unitRegExp, function (match) {
      matchedUnits[match] = null;
      return 'U' + match;
    });
    var taggedUnitRegExp = 'U(' + unitRegExp.source + ')'; // Validating input is simply applying as many reductions as we can.

    var typeCheck = string.replace(/[-+]?(\d*\.)?\d+([Ee][-+]?\d+)?/g, 'N').replace(new RegExp('N' + taggedUnitRegExp, 'g'), 'D').replace(/\s[+-]\s/g, 'O').replace(/\s/g, '');
    var reductions = [/N\*(D)/g, /(N|D)[*/]N/g, /(N|D)O\1/g, /\((N|D)\)/g];
    var i = 0;

    while (i < reductions.length) {
      if (reductions[i].test(typeCheck)) {
        typeCheck = typeCheck.replace(reductions[i], '$1');
        i = 0;
      } else {
        i++;
      }
    }

    if (typeCheck != 'D') return;

    for (var unit in matchedUnits) {
      var result = calculate(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
      if (!isFinite(result)) return;
      matchedUnits[unit] = result;
    }

    return matchedUnits;
  }

  function mergeDimensionsNonNegative(left, right) {
    return mergeDimensions(left, right, true);
  }

  function mergeDimensions(left, right, nonNegative) {
    var units = [],
        unit;

    for (unit in left) units.push(unit);

    for (unit in right) {
      if (units.indexOf(unit) < 0) units.push(unit);
    }

    left = units.map(function (unit) {
      return left[unit] || 0;
    });
    right = units.map(function (unit) {
      return right[unit] || 0;
    });
    return [left, right, function (values) {
      var result = values.map(function (value, i) {
        if (values.length == 1 && nonNegative) {
          value = Math.max(value, 0);
        } // Scientific notation (e.g. 1e2) is not yet widely supported by browser vendors.


        return scope.numberToString(value) + units[i];
      }).join(' + ');
      return values.length > 1 ? 'calc(' + result + ')' : result;
    }];
  }

  var lengthUnits = 'px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc';
  var parseLength = parseDimension.bind(null, new RegExp(lengthUnits, 'g'));
  var parseLengthOrPercent = parseDimension.bind(null, new RegExp(lengthUnits + '|%', 'g'));
  var parseAngle = parseDimension.bind(null, /deg|rad|grad|turn/g);
  scope.parseLength = parseLength;
  scope.parseLengthOrPercent = parseLengthOrPercent;
  scope.consumeLengthOrPercent = scope.consumeParenthesised.bind(null, parseLengthOrPercent);
  scope.parseAngle = parseAngle;
  scope.mergeDimensions = mergeDimensions;
  var consumeLength = scope.consumeParenthesised.bind(null, parseLength);
  var consumeSizePair = scope.consumeRepeated.bind(undefined, consumeLength, /^/);
  var consumeSizePairList = scope.consumeRepeated.bind(undefined, consumeSizePair, /^,/);
  scope.consumeSizePairList = consumeSizePairList;

  var parseSizePairList = function (input) {
    var result = consumeSizePairList(input);

    if (result && result[1] == '') {
      return result[0];
    }
  };

  var mergeNonNegativeSizePair = scope.mergeNestedRepeated.bind(undefined, mergeDimensionsNonNegative, ' ');
  var mergeNonNegativeSizePairList = scope.mergeNestedRepeated.bind(undefined, mergeNonNegativeSizePair, ',');
  scope.mergeNonNegativeSizePair = mergeNonNegativeSizePair;
  scope.addPropertiesHandler(parseSizePairList, mergeNonNegativeSizePairList, ['background-size']);
  scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensionsNonNegative, ['border-bottom-width', 'border-image-width', 'border-left-width', 'border-right-width', 'border-top-width', 'flex-basis', 'font-size', 'height', 'line-height', 'max-height', 'max-width', 'outline-width', 'width']);
  scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensions, ['border-bottom-left-radius', 'border-bottom-right-radius', 'border-top-left-radius', 'border-top-right-radius', 'bottom', 'left', 'letter-spacing', 'margin-bottom', 'margin-left', 'margin-right', 'margin-top', 'min-height', 'min-width', 'outline-offset', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top', 'perspective', 'right', 'shape-margin', 'stroke-dashoffset', 'text-indent', 'top', 'vertical-align', 'word-spacing']);
})(webAnimations1, webAnimationsTesting);