/**
 * 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;

import org.mvel2.ast.Proto;
import org.mvel2.compiler.AbstractParser;
import org.mvel2.integration.Interceptor;
import org.mvel2.util.MethodStub;
import org.mvel2.util.PropertyTools;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static java.lang.Thread.currentThread;

/**
 * The resusable parser configuration object.
 */
public class ParserConfiguration implements Serializable {
  protected Map<String, Object> imports;
  protected HashSet<String> packageImports;
  protected Map<String, Interceptor> interceptors;
  protected transient ClassLoader classLoader = currentThread().getContextClassLoader();

  public ParserConfiguration() {
  }

  public ParserConfiguration(Map<String, Object> imports, Map<String, Interceptor> interceptors) {
    addAllImports(imports);
    this.interceptors = interceptors;
  }

  public ParserConfiguration(Map<String, Object> imports, HashSet<String> packageImports, Map<String, Interceptor> interceptors) {
    addAllImports(imports);
    this.packageImports = packageImports;
    this.interceptors = interceptors;
  }

  public HashSet<String> getPackageImports() {
    return packageImports;
  }

  public void setPackageImports(HashSet<String> packageImports) {
    this.packageImports = packageImports;
  }

  public Class getImport(String name) {
    if (imports != null && imports.containsKey(name) && imports.get(name) instanceof Class) {
      return (Class) imports.get(name);
    }
    return (Class) (AbstractParser.LITERALS.get(name) instanceof Class ? AbstractParser.LITERALS.get(name) : null);
  }

  public MethodStub getStaticImport(String name) {
    return imports != null ? (MethodStub) imports.get(name) : null;
  }

  public Object getStaticOrClassImport(String name) {
    return (imports != null && imports.containsKey(name) ? imports.get(name) : AbstractParser.LITERALS.get(name));
  }

  public void addPackageImport(String packageName) {
    if (packageImports == null) packageImports = new LinkedHashSet<String>();
    packageImports.add(packageName);
  }

  public void addAllImports(Map<String, Object> imports) {
    if (imports == null) return;

    if (this.imports == null) this.imports = new LinkedHashMap<String, Object>();
    Object o;

    for (Map.Entry<String, Object> entry : imports.entrySet()) {
      if ((o = entry.getValue()) instanceof Method) {
        this.imports.put(entry.getKey(), new MethodStub((Method) o));
      }
      else {
        this.imports.put(entry.getKey(), o);
      }
    }
  }

  public void setAllImports(Map<String, Object> imports) {
    this.imports = imports;
  }

  private boolean checkForDynamicImport(String className) {
    if (packageImports == null) return false;

    int found = 0;
    Class cls = null;
    for (String pkg : packageImports) {
      try {
        cls = Class.forName(pkg + "." + className, true, classLoader);
        found++;
      }
      catch (ClassNotFoundException e) {
        // do nothing.
      }
      catch (NoClassDefFoundError e) {
        if (PropertyTools.contains(e.getMessage(), "wrong name")) {
          // do nothing.  this is a weirdness in the jvm.
          // see MVEL-43
        }
        else {
          throw e;
        }
      }
    }

    if (found > 1) {
      throw new RuntimeException("ambiguous class name: " + className);
    }
    else if (found == 1) {
      addImport(className, cls);
      return true;
    }
    else {
      return false;
    }
  }

  public boolean hasImport(String name) {
    return (imports != null && imports.containsKey(name)) ||
        (!"this".equals(name) && !"self".equals(name) && !"empty".equals(name) && !"null".equals(name) &&
            !"nil".equals(name) && !"true".equals(name) && !"false".equals(name)
            && AbstractParser.LITERALS.containsKey(name))
        || checkForDynamicImport(name);
  }

  public void addImport(Class cls) {
    addImport(cls.getSimpleName(), cls);
  }

  public void addImport(String name, Class cls) {
    if (this.imports == null) this.imports = new ConcurrentHashMap<String, Object>();
    this.imports.put(name, cls);
  }

  public void addImport(String name, Proto proto) {
    if (this.imports == null) this.imports = new ConcurrentHashMap<String, Object>();
    this.imports.put(name, proto);
  }

  public void addImport(String name, Method method) {
    addImport(name, new MethodStub(method));
  }

  public void addImport(String name, MethodStub method) {
    if (this.imports == null) this.imports = new ConcurrentHashMap<String, Object>();
    this.imports.put(name, method);
  }

  public Map<String, Interceptor> getInterceptors() {
    return interceptors;
  }

  public void setInterceptors(Map<String, Interceptor> interceptors) {
    this.interceptors = interceptors;
  }

  public Map<String, Object> getImports() {
    return imports;
  }

  public void setImports(Map<String, Object> imports) {
    if (imports == null) return;

    Object val;

    for (Map.Entry<String, Object> entry : imports.entrySet()) {
      if ((val = entry.getValue()) instanceof Class) {
        addImport(entry.getKey(), (Class) val);
      }
      else if (val instanceof Method) {
        addImport(entry.getKey(), (Method) val);
      }
      else if (val instanceof MethodStub) {
        addImport(entry.getKey(), (MethodStub) val);
      }
      else if (val instanceof Proto) {
        addImport(entry.getKey(), (Proto) entry.getValue());
      }
      else {
        throw new RuntimeException("invalid element in imports map: " + entry.getKey() + " (" + val + ")");
      }
    }
  }

  public boolean hasImports() {
    return (imports != null && imports.size() != 0) || (packageImports != null && packageImports.size() != 0);
  }

  public ClassLoader getClassLoader() {
    return classLoader == null ? classLoader = Thread.currentThread().getContextClassLoader() : classLoader;
  }

  public void setClassLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
  }

  public void setImports(HashMap<String, Object> imports) {
    this.imports = imports;
  }
}
