'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.parseMediaFeature = parseMediaFeature;
exports.parseMediaQuery = parseMediaQuery;
exports.parseMediaList = parseMediaList;

var _Node = require('./nodes/Node');

var _Node2 = _interopRequireDefault(_Node);

var _Container = require('./nodes/Container');

var _Container2 = _interopRequireDefault(_Container);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Parses a media feature expression, e.g. `max-width: 10px`, `(color)`
 *
 * @param {string} string - the source expression string, can be inside parens
 * @param {Number} index - the index of `string` in the overall input
 *
 * @return {Array} an array of Nodes, the first element being a media feature,
 *    the secont - its value (may be missing)
 */

function parseMediaFeature(string) {
  var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];

  var modesEntered = [{
    mode: 'normal',
    character: null
  }];
  var result = [];
  var lastModeIndex = 0;
  var mediaFeature = '';
  var colon = null;
  var mediaFeatureValue = null;
  var indexLocal = index;

  var stringNormalized = string;
  // Strip trailing parens (if any), and correct the starting index
  if (string[0] === '(' && string[string.length - 1] === ')') {
    stringNormalized = string.substring(1, string.length - 1);
    indexLocal++;
  }

  for (var i = 0; i < stringNormalized.length; i++) {
    var character = stringNormalized[i];

    // If entering/exiting a string
    if (character === '\'' || character === '"') {
      if (modesEntered[lastModeIndex].isCalculationEnabled === true) {
        modesEntered.push({
          mode: 'string',
          isCalculationEnabled: false,
          character: character
        });
        lastModeIndex++;
      } else if (modesEntered[lastModeIndex].mode === 'string' && modesEntered[lastModeIndex].character === character && stringNormalized[i - 1] !== '\\') {
        modesEntered.pop();
        lastModeIndex--;
      }
    }

    // If entering/exiting interpolation
    if (character === '{') {
      modesEntered.push({
        mode: 'interpolation',
        isCalculationEnabled: true
      });
      lastModeIndex++;
    } else if (character === '}') {
      modesEntered.pop();
      lastModeIndex--;
    }

    // If a : is met outside of a string, function call or interpolation, than
    // this : separates a media feature and a value
    if (modesEntered[lastModeIndex].mode === 'normal' && character === ':') {
      var mediaFeatureValueStr = stringNormalized.substring(i + 1);
      mediaFeatureValue = {
        type: 'value',
        before: /^(\s*)/.exec(mediaFeatureValueStr)[1],
        after: /(\s*)$/.exec(mediaFeatureValueStr)[1],
        value: mediaFeatureValueStr.trim()
      };
      // +1 for the colon
      mediaFeatureValue.sourceIndex = mediaFeatureValue.before.length + i + 1 + indexLocal;
      colon = {
        type: 'colon',
        sourceIndex: i + indexLocal,
        after: mediaFeatureValue.before,
        value: ':' };
      break;
    }

    mediaFeature += character;
  }

  // Forming a media feature node
  mediaFeature = {
    type: 'media-feature',
    before: /^(\s*)/.exec(mediaFeature)[1],
    after: /(\s*)$/.exec(mediaFeature)[1],
    value: mediaFeature.trim()
  };
  mediaFeature.sourceIndex = mediaFeature.before.length + indexLocal;
  result.push(mediaFeature);

  if (colon !== null) {
    colon.before = mediaFeature.after;
    result.push(colon);
  }

  if (mediaFeatureValue !== null) {
    result.push(mediaFeatureValue);
  }

  return result;
}

/**
 * Parses a media query, e.g. `screen and (color)`, `only tv`
 *
 * @param {string} string - the source media query string
 * @param {Number} index - the index of `string` in the overall input
 *
 * @return {Array} an array of Nodes and Containers
 */

function parseMediaQuery(string) {
  var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];

  var result = [];

  // How many timies the parser entered parens/curly braces
  var localLevel = 0;
  // Has any keyword, media type, media feature expression or interpolation
  // ('element' hereafter) started
  var insideSomeValue = false;
  var node = void 0;

  function resetNode() {
    return {
      before: '',
      after: '',
      value: ''
    };
  }

  node = resetNode();

  for (var i = 0; i < string.length; i++) {
    var character = string[i];
    // If not yet entered any element
    if (!insideSomeValue) {
      if (character.search(/\s/) !== -1) {
        // A whitespace
        // Don't form 'after' yet; will do it later
        node.before += character;
      } else {
        // Not a whitespace - entering an element
        // Expression start
        if (character === '(') {
          node.type = 'media-feature-expression';
          localLevel++;
        }
        node.value = character;
        node.sourceIndex = index + i;
        insideSomeValue = true;
      }
    } else {
      // Already in the middle of some alement
      node.value += character;

      // Here parens just increase localLevel and don't trigger a start of
      // a media feature expression (since they can't be nested)
      // Interpolation start
      if (character === '{' || character === '(') {
        localLevel++;
      }
      // Interpolation/function call/media feature expression end
      if (character === ')' || character === '}') {
        localLevel--;
      }
    }

    // If exited all parens/curlies and the next symbol
    if (insideSomeValue && localLevel === 0 && (character === ')' || i === string.length - 1 || string[i + 1].search(/\s/) !== -1)) {
      if (['not', 'only', 'and'].indexOf(node.value) !== -1) {
        node.type = 'keyword';
      }
      // if it's an expression, parse its contents
      if (node.type === 'media-feature-expression') {
        node.nodes = parseMediaFeature(node.value, node.sourceIndex);
      }
      result.push(Array.isArray(node.nodes) ? new _Container2.default(node) : new _Node2.default(node));
      node = resetNode();
      insideSomeValue = false;
    }
  }

  // Now process the result array - to specify undefined types of the nodes
  // and specify the `after` prop
  for (var _i = 0; _i < result.length; _i++) {
    node = result[_i];
    if (_i > 0) {
      result[_i - 1].after = node.before;
    }

    // Node types. Might not be set because contains interpolation/function
    // calls or fully consists of them
    if (node.type === undefined) {
      if (_i > 0) {
        // only `and` can follow an expression
        if (result[_i - 1].type === 'media-feature-expression') {
          node.type = 'keyword';
          continue;
        }
        // Anything after 'only|not' is a media type
        if (result[_i - 1].value === 'not' || result[_i - 1].value === 'only') {
          node.type = 'media-type';
          continue;
        }
        // Anything after 'and' is an expression
        if (result[_i - 1].value === 'and') {
          node.type = 'media-feature-expression';
          continue;
        }

        if (result[_i - 1].type === 'media-type') {
          // if it is the last element - it might be an expression
          // or 'and' depending on what is after it
          if (!result[_i + 1]) {
            node.type = 'media-feature-expression';
          } else {
            node.type = result[_i + 1].type === 'media-feature-expression' ? 'keyword' : 'media-feature-expression';
          }
        }
      }

      if (_i === 0) {
        // `screen`, `fn( ... )`, `#{ ... }`. Not an expression, since then
        // its type would have been set by now
        if (!result[_i + 1]) {
          node.type = 'media-type';
          continue;
        }

        // `screen and` or `#{...} (max-width: 10px)`
        if (result[_i + 1] && (result[_i + 1].type === 'media-feature-expression' || result[_i + 1].type === 'keyword')) {
          node.type = 'media-type';
          continue;
        }
        if (result[_i + 2]) {
          // `screen and (color) ...`
          if (result[_i + 2].type === 'media-feature-expression') {
            node.type = 'media-type';
            result[_i + 1].type = 'keyword';
            continue;
          }
          // `only screen and ...`
          if (result[_i + 2].type === 'keyword') {
            node.type = 'keyword';
            result[_i + 1].type = 'media-type';
            continue;
          }
        }
        if (result[_i + 3]) {
          // `screen and (color) ...`
          if (result[_i + 3].type === 'media-feature-expression') {
            node.type = 'keyword';
            result[_i + 1].type = 'media-type';
            result[_i + 2].type = 'keyword';
            continue;
          }
        }
      }
    }
  }
  return result;
}

/**
 * Parses a media query list. Takes a possible `url()` at the start into
 * account, and divides the list into media queries that are parsed separately
 *
 * @param {string} string - the source media query list string
 *
 * @return {Array} an array of Nodes/Containers
 */

function parseMediaList(string) {
  var result = [];
  var interimIndex = 0;
  var levelLocal = 0;

  // Check for a `url(...)` part (if it is contents of an @import rule)
  var doesHaveUrl = /^(\s*)url\s*\(/.exec(string);
  if (doesHaveUrl !== null) {
    var i = doesHaveUrl[0].length;
    var parenthesesLv = 1;
    while (parenthesesLv > 0) {
      var character = string[i];
      if (character === '(') {
        parenthesesLv++;
      }
      if (character === ')') {
        parenthesesLv--;
      }
      i++;
    }
    result.unshift(new _Node2.default({
      type: 'url',
      value: string.substring(0, i).trim(),
      sourceIndex: doesHaveUrl[1].length,
      before: doesHaveUrl[1],
      after: /^(\s*)/.exec(string.substring(i))[1]
    }));
    interimIndex = i;
  }

  // Start processing the media query list
  for (var _i2 = interimIndex; _i2 < string.length; _i2++) {
    var _character = string[_i2];

    // Dividing the media query list into comma-separated media queries
    // Only count commas that are outside of any parens
    // (i.e., not part of function call params list, etc.)
    if (_character === '(') {
      levelLocal++;
    }
    if (_character === ')') {
      levelLocal--;
    }
    if (levelLocal === 0 && _character === ',') {
      var _mediaQueryString = string.substring(interimIndex, _i2);
      var _spaceBefore = /^(\s*)/.exec(_mediaQueryString)[1];
      result.push(new _Container2.default({
        type: 'media-query',
        value: _mediaQueryString.trim(),
        sourceIndex: interimIndex + _spaceBefore.length,
        nodes: parseMediaQuery(_mediaQueryString, interimIndex),
        before: _spaceBefore,
        after: /(\s*)$/.exec(_mediaQueryString)[1]
      }));
      interimIndex = _i2 + 1;
    }
  }

  var mediaQueryString = string.substring(interimIndex);
  var spaceBefore = /^(\s*)/.exec(mediaQueryString)[1];
  result.push(new _Container2.default({
    type: 'media-query',
    value: mediaQueryString.trim(),
    sourceIndex: interimIndex + spaceBefore.length,
    nodes: parseMediaQuery(mediaQueryString, interimIndex),
    before: spaceBefore,
    after: /(\s*)$/.exec(mediaQueryString)[1]
  }));

  return result;
}