import { astRoot } from './codegen';
import { parse as parseDSL } from './parser';

const isStickySupported = typeof /./g.sticky === 'boolean';

const execLambda = (pattern) => {
  if (pattern.length) return pattern;
  return (state) => pattern()(state);
};

const execString = (pattern) => {
  return (state) => {
    if (state.x < state.quasis.length) {
      const input = state.quasis[state.x];
      for (let i = 0, l = pattern.length; i < l; i++)
        if (input.charCodeAt(state.y + i) !== pattern.charCodeAt(i))
          return null;
      state.y += pattern.length;
      return pattern;
    }
  };
};

const execRegex = (pattern) => {
  pattern = isStickySupported
    ? new RegExp(pattern.source, 'y')
    : new RegExp(pattern.source + '|()', 'g');
  return (state) => {
    if (state.x < state.quasis.length) {
      const input = state.quasis[state.x];
      pattern.lastIndex = state.y;
      let match;
      if (isStickySupported) {
        if (pattern.test(input))
          match = input.slice(state.y, pattern.lastIndex);
      } else {
        const x = pattern.exec(input);
        if (x[1] == null) match = x[0];
      }

      state.y = pattern.lastIndex;
      return match;
    }
  };
};

export const __pattern = (input) => {
  if (typeof input === 'function') {
    return execLambda(input);
  } else if (typeof input === 'string') {
    return execString(input);
  } else {
    return execRegex(input);
  }
};

export const interpolation = (predicate) => (state) => {
  let match;

  if (
    state.x < state.expressions.length &&
    state.y >= state.quasis[state.x].length
  ) {
    state.y = 0;
    match = state.expressions[state.x++];
    if (predicate && match) match = predicate(match);
  }

  return match;
};

export const parse = (matcher) => (quasis, ...expressions) => {
  if (typeof quasis === 'string') quasis = [quasis];
  const state = { quasis, expressions, x: 0, y: 0 };
  return matcher(state);
};

export const match = (name, transform) => (quasis, ...expressions) => {
  const ast = parseDSL(
    quasis,
    expressions.map((_, i) => ({ id: `_${i}` }))
  );
  return new Function(
    '_n,_t,' + expressions.map((_expression, i) => `_${i}`).join(','),
    'return ' + astRoot(ast, '_n', transform ? '_t' : null)
  )(name, transform, ...expressions.map(__pattern));
};
