'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _typeof2 = require('babel-runtime/helpers/typeof');

var _typeof3 = _interopRequireDefault(_typeof2);

exports.fromExpression = fromExpression;
exports.safeElementFromExpression = safeElementFromExpression;
exports.toExpression = toExpression;

var _get_type = require('./get_type');

var _grammar = require('./grammar');

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

/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you 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 getArgumentString(arg, argKey) {
  var level = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;

  var type = (0, _get_type.getType)(arg);

  function maybeArgKey(argKey, argString) {
    return argKey == null || argKey === '_' ? argString : argKey + '=' + argString;
  }

  if (type === 'string') {
    // correctly (re)escape double quotes
    var escapedArg = arg.replace(/[\\"]/g, '\\$&'); // $& means the whole matched string
    return maybeArgKey(argKey, '"' + escapedArg + '"');
  }

  if (type === 'boolean' || type === 'null' || type === 'number') {
    // use values directly
    return maybeArgKey(argKey, '' + arg);
  }

  if (type === 'expression') {
    // build subexpressions
    return maybeArgKey(argKey, '{' + getExpression(arg.chain, level + 1) + '}');
  }

  // unknown type, throw with type value
  throw new Error('Invalid argument type in AST: ' + type);
}

function getExpressionArgs(block) {
  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

  var args = block.arguments;
  var hasValidArgs = (typeof args === 'undefined' ? 'undefined' : (0, _typeof3.default)(args)) === 'object' && args != null && !Array.isArray(args);

  if (!hasValidArgs) throw new Error('Arguments can only be an object');

  var argKeys = Object.keys(args);
  var MAX_LINE_LENGTH = 80; // length before wrapping arguments
  return argKeys.map(function (argKey) {
    return args[argKey].reduce(function (acc, arg) {
      var argString = getArgumentString(arg, argKey, level);
      var lineLength = acc.split('\n').pop().length;

      // if arg values are too long, move it to the next line
      if (level === 0 && lineLength + argString.length > MAX_LINE_LENGTH) {
        return acc + '\n  ' + argString;
      }

      // append arg values to existing arg values
      if (lineLength > 0) return acc + ' ' + argString;

      // start the accumulator with the first arg value
      return argString;
    }, '');
  });
}

function fnWithArgs(fnName, args) {
  if (!args || args.length === 0) return fnName;
  return fnName + ' ' + args.join(' ');
}

function getExpression(chain) {
  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

  if (!chain) throw new Error('Expressions must contain a chain');

  // break new functions onto new lines if we're not in a nested/sub-expression
  var separator = level > 0 ? ' | ' : '\n| ';

  return chain.map(function (chainObj) {
    var type = (0, _get_type.getType)(chainObj);

    if (type === 'function') {
      var fn = chainObj.function;
      if (!fn || fn.length === 0) throw new Error('Functions must have a function name');

      var expArgs = getExpressionArgs(chainObj, level);

      return fnWithArgs(fn, expArgs);
    }
  }, []).join(separator);
}

function fromExpression(expression) {
  var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'expression';

  try {
    return (0, _grammar.parse)(String(expression), { startRule: type });
  } catch (e) {
    throw new Error('Unable to parse expression: ' + e.message);
  }
}

// TODO: OMG This is so bad, we need to talk about the right way to handle bad expressions since some are element based and others not
function safeElementFromExpression(expression) {
  try {
    return fromExpression(expression);
  } catch (e) {
    return fromExpression('markdown\n"## Crud.\nCanvas could not parse this element\'s expression. I am so sorry this error isn\'t more useful. I promise it will be soon.\n\nThanks for understanding,\n#### Management\n"');
  }
}

// TODO: Respect the user's existing formatting
function toExpression(astObj) {
  var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'expression';

  if (type === 'argument') return getArgumentString(astObj);

  var validType = ['expression', 'function'].includes((0, _get_type.getType)(astObj));
  if (!validType) throw new Error('Expression must be an expression or argument function');

  if ((0, _get_type.getType)(astObj) === 'expression') {
    if (!Array.isArray(astObj.chain)) throw new Error('Expressions must contain a chain');

    return getExpression(astObj.chain);
  }

  var expArgs = getExpressionArgs(astObj);
  return fnWithArgs(astObj.function, expArgs);
}