/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */
package org.apache.lucene.expressions.js;

import java.util.ArrayList;
import java.util.List;

/**
 * A helper to parse the context of a variable name, which is the base variable, followed by the
 * sequence of array (integer or string indexed) and member accesses.
 */
public class VariableContext {

  /** Represents what a piece of a variable does. */
  public enum Type {
    /** A member of the previous context (ie "dot" access). */
    MEMBER,

    /** Brackets containing a string as the "index". */
    STR_INDEX,

    /** Brackets containing an integer index (ie an array). */
    INT_INDEX,

    /** Parenthesis represent a member method to be called. */
    METHOD
  }

  /** The type of this piece of a variable. */
  public final Type type;

  /**
   * The text of this piece of the variable. Used for {@link Type#MEMBER} and {@link Type#STR_INDEX}
   * types.
   */
  public final String text;

  /** The integer value for this piece of the variable. Used for {@link Type#INT_INDEX}. */
  public final int integer;

  private VariableContext(Type c, String s, int i) {
    type = c;
    text = s;
    integer = i;
  }

  /**
   * Parses a normalized javascript variable. All strings in the variable should be single quoted,
   * and no spaces (except possibly within strings).
   */
  public static final VariableContext[] parse(String variable) {
    char[] text = variable.toCharArray();
    List<VariableContext> contexts = new ArrayList<>();
    int i = addMember(text, 0, contexts); // base variable is a "member" of the global namespace
    while (i < text.length) {
      if (text[i] == '[') {
        if (text[++i] == '\'') {
          i = addStringIndex(text, i, contexts);
        } else {
          i = addIntIndex(text, i, contexts);
        }
        ++i; // move past end bracket
      } else { // text[i] == '.', ie object member
        i = addMember(text, i + 1, contexts);
      }
    }
    return contexts.toArray(VariableContext[]::new);
  }

  // i points to start of member name
  private static int addMember(final char[] text, int i, List<VariableContext> contexts) {
    int j = i + 1;
    while (j < text.length && text[j] != '[' && text[j] != '.' && text[j] != '(') {
      ++j; // find first array, member access, or method call
    }
    if (j + 1 < text.length && text[j] == '(' && text[j + 1] == ')') {
      contexts.add(new VariableContext(Type.METHOD, new String(text, i, j - i), -1));
      j += 2; // move past the parenthesis
    } else {
      contexts.add(new VariableContext(Type.MEMBER, new String(text, i, j - i), -1));
    }
    return j;
  }

  // i points to start of single quoted index
  private static int addStringIndex(final char[] text, int i, List<VariableContext> contexts) {
    ++i; // move past quote
    int j = i;
    while (text[j] != '\'') { // find end of single quoted string
      if (text[j] == '\\') ++j; // skip over escapes
      ++j;
    }
    StringBuffer buf = new StringBuffer(j - i); // space for string, without end quote
    while (i < j) { // copy string to buffer (without begin/end quotes)
      if (text[i] == '\\') ++i; // unescape escapes
      buf.append(text[i]);
      ++i;
    }
    contexts.add(new VariableContext(Type.STR_INDEX, buf.toString(), -1));
    return j + 1; // move past quote, return end bracket location
  }

  // i points to start of integer index
  private static int addIntIndex(final char[] text, int i, List<VariableContext> contexts) {
    int j = i + 1;
    while (text[j] != ']') ++j; // find end of array access
    int index = Integer.parseInt(new String(text, i, j - i));
    contexts.add(new VariableContext(Type.INT_INDEX, null, index));
    return j;
  }
}
