/**
 * MVEL 2.0
 * Copyright (C) 2007 The Codehaus
 * Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
 *
 * 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.
 */
package org.mvel2.optimizers;

import org.mvel2.CompileException;
import org.mvel2.MVEL;
import org.mvel2.compiler.AbstractParser;

import java.lang.reflect.Method;

import static java.lang.Thread.currentThread;
import static org.mvel2.util.ParseTools.*;

/**
 * @author Christopher Brock
 */
public class AbstractOptimizer extends AbstractParser {
  protected static final int BEAN = 0;
  protected static final int METH = 1;
  protected static final int COL = 2;
  protected static final int WITH = 3;

  protected boolean collection = false;
  protected boolean nullSafe = false;
  protected Class currType = null;
  protected boolean staticAccess = false;

  protected int tkStart;

  /**
   * Try static access of the property, and return an instance of the Field, Method of Class if successful.
   *
   * @return - Field, Method or Class instance.
   */
  protected Object tryStaticAccess() {
    int begin = cursor;
    try {
      /**
       * Try to resolve this *smartly* as a static class reference.
       *
       * This starts at the end of the token and starts to step backwards to figure out whether
       * or not this may be a static class reference.  We search for method calls simply by
       * inspecting for ()'s.  The first union area we come to where no brackets are present is our
       * test-point for a class reference.  If we find a class, we pass the reference to the
       * property accessor along  with trailing methods (if any).
       *
       */
      boolean meth = false;
      // int end = start + length;
      int last = end;
      for (int i = end - 1; i > start; i--) {
        switch (expr[i]) {
          case '.':
            if (!meth) {
              ClassLoader classLoader = pCtx != null ? pCtx.getClassLoader() : currentThread().getContextClassLoader();
              try {
                String test = new String(expr, start, (cursor = last) - start);
                if (MVEL.COMPILER_OPT_SUPPORT_JAVA_STYLE_CLASS_LITERALS && test.endsWith(".class"))
                    test = test.substring(0, test.length() - 6);

                return Class.forName(test, true, classLoader);
              }
              catch (ClassNotFoundException e) {
                Class cls = forNameWithInner(new String(expr, start, i - start), classLoader);
                String name = new String(expr, i + 1, end - i - 1);
                try {
                  return cls.getField(name);
                }
                catch (NoSuchFieldException nfe) {
                  for (Method m : cls.getMethods()) {
                    if (name.equals(m.getName())) return m;
                  }
                  return null;
                }
              }
            }

            meth = false;
            last = i;
            break;

          case '}':
            i--;
            for (int d = 1; i > start && d != 0; i--) {
              switch (expr[i]) {
                case '}':
                  d++;
                  break;
                case '{':
                  d--;
                  break;
                case '"':
                case '\'':
                  char s = expr[i];
                  while (i > start && (expr[i] != s && expr[i - 1] != '\\')) i--;
              }
            }
            break;

          case ')':
            i--;

            for (int d = 1; i > start && d != 0; i--) {
              switch (expr[i]) {
                case ')':
                  d++;
                  break;
                case '(':
                  d--;
                  break;
                case '"':
                case '\'':
                  char s = expr[i];
                  while (i > start && (expr[i] != s && expr[i - 1] != '\\')) i--;
              }
            }

            meth = true;
            last = i++;
            break;


          case '\'':
            while (--i > start) {
              if (expr[i] == '\'' && expr[i - 1] != '\\') {
                break;
              }
            }
            break;

          case '"':
            while (--i > start) {
              if (expr[i] == '"' && expr[i - 1] != '\\') {
                break;
              }
            }
            break;
        }
      }
    }
    catch (Exception cnfe) {
      cursor = begin;
    }

    return null;
  }

  private Class forNameWithInner(String className, ClassLoader classLoader) throws ClassNotFoundException {
    ClassNotFoundException cnfe = null;
    try {
      return Class.forName(className, true, classLoader);
    } catch (ClassNotFoundException e) {
      cnfe = e;
    }

    for (int lastDotPos = className.lastIndexOf('.'); lastDotPos > 0; lastDotPos = className.lastIndexOf('.')) {
      className = className.substring(0, lastDotPos) + "$" + className.substring(lastDotPos+1);
      try {
        return Class.forName(className, true, classLoader);
      } catch (ClassNotFoundException e) { }
    }

     throw cnfe;
  }

  protected int nextSubToken() {
    skipWhitespace();
    nullSafe = false;

    switch (expr[tkStart = cursor]) {
      case '[':
        return COL;
      case '{':
        if (expr[cursor - 1] == '.') {
          return WITH;
        }
        break;
      case '.':
        if ((start + 1) != end) {
          switch (expr[cursor = ++tkStart]) {
            case '?':
              skipWhitespace();
              if ((cursor = ++tkStart) == end) {
                throw new CompileException("unexpected end of statement", expr, start);
              }
              nullSafe = true;

              fields = -1;
              break;
            case '{':
              return WITH;
            default:
              if (isWhitespace(expr[tkStart])) {
                skipWhitespace();
                tkStart = cursor;
              }
          }
        }
        else {
          throw new CompileException("unexpected end of statement", expr, start);
        }
        break;
      case '?':
        if (start == cursor) {
          tkStart++;
          cursor++;
          nullSafe = true;
        }
    }

    //noinspection StatementWithEmptyBody
    while (++cursor < end && isIdentifierPart(expr[cursor])) ;

    skipWhitespace();
    if (cursor < end) {
      switch (expr[cursor]) {
        case '[':
          return COL;
        case '(':
          return METH;
        default:
          return BEAN;
      }
    }

    return 0;
  }

  protected String capture() {
    /**
     * Trim off any whitespace.
     */
    return new String(expr, tkStart = trimRight(tkStart), trimLeft(cursor) - tkStart);
  }

  /**
   * Skip to the next non-whitespace position.
   */
  protected void whiteSpaceSkip() {
    if (cursor < length)
      //noinspection StatementWithEmptyBody
      while (isWhitespace(expr[cursor]) && ++cursor != length) ;
  }

  /**
   * @param c - character to scan to.
   * @return - returns true is end of statement is hit, false if the scan scar is countered.
   */
  protected boolean scanTo(char c) {
    for (; cursor < end; cursor++) {
      switch (expr[cursor]) {
        case '\'':
        case '"':
          cursor = captureStringLiteral(expr[cursor], expr, cursor, end);
        default:
          if (expr[cursor] == c) {
            return false;
          }
      }
    }
    return true;
  }

  protected int findLastUnion() {
    int split = -1;
    int depth = 0;

    int end = start + length;
    for (int i = end - 1; i != start; i--) {
      switch (expr[i]) {
        case '}':
        case ']':
          depth++;
          break;

        case '{':
        case '[':
          if (--depth == 0) {
            split = i;
            collection = true;
          }
          break;
        case '.':
          if (depth == 0) {
            split = i;
          }
          break;
      }
      if (split != -1) break;
    }

    return split;
  }
}
