/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package java.lang;

import gnu.classpath.Configuration;
import gnu.classpath.SystemProperties;
import gnu.java.lang.InstrumentationImpl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.instrument.Instrumentation;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.ZipFile;

import org.jikesrvm.classloader.BootstrapClassLoader;
import org.jikesrvm.classloader.RVMClassLoader;
import org.jikesrvm.classloader.RVMType;

import org.jikesrvm.util.ImmutableEntryHashMapRVM;

/**
 * Jikes RVM impl of VMClassLoader.
 * See GNU classpath reference impl for javadoc.
 */
final class VMClassLoader {
  /**
   * A map of maps. The first map is indexed by the classloader. The
   * map this finds then maps String class names to classes
   */
  private static final ImmutableEntryHashMapRVM<ClassLoader,ImmutableEntryHashMapRVM<String,Class<?>>> loadedClasses =
    new ImmutableEntryHashMapRVM<ClassLoader,ImmutableEntryHashMapRVM<String,Class<?>>>();

  /**
   * The map for the boot strap class loader that is often represented by null
   */
  private static final ImmutableEntryHashMapRVM<String,Class<?>> bootStrapLoadedClasses =
    new ImmutableEntryHashMapRVM<String,Class<?>>();

  /** packages loaded by the bootstrap class loader */
  private static final ImmutableEntryHashMapRVM<String,Package> definedPackages =
    new ImmutableEntryHashMapRVM<String,Package>();

  private static final ImmutableEntryHashMapRVM<String,ZipFile> bootjars =
    new ImmutableEntryHashMapRVM<String,ZipFile>();

  static {
    String[] packages = getBootPackages();

    if (packages != null) {
      String specName =
        SystemProperties.getProperty("java.specification.name");
      String vendor =
        SystemProperties.getProperty("java.specification.vendor");
      String version =
        SystemProperties.getProperty("java.specification.version");

      for (String packageName : packages) {
        final Package p = new Package(packageName,
            specName,
            vendor,
            version,
            "GNU Classpath",
            "GNU",
            Configuration.CLASSPATH_VERSION,
            null,
            null);

        definedPackages.put(packageName, p);
      }
    }
  }

  static Class<?> defineClass(ClassLoader cl, String name,
                              byte[] data, int offset, int len,
                              ProtectionDomain pd)
                              throws ClassFormatError {
    RVMType vmType = RVMClassLoader.defineClassInternal(name, data, offset, len, cl);
    Class<?> ans = vmType.getClassForType();
    JikesRVMSupport.setClassProtectionDomain(ans, pd);
    ImmutableEntryHashMapRVM<String,Class<?>> mapForCL;
    if (cl == null || cl == BootstrapClassLoader.getBootstrapClassLoader()) {
      mapForCL = bootStrapLoadedClasses;
    } else {
      mapForCL = loadedClasses.get(cl);
      if (mapForCL == null) {
        mapForCL = new ImmutableEntryHashMapRVM<String,Class<?>>();
        loadedClasses.put(cl, mapForCL);
      }
    }
    mapForCL.put(ans.getName(), ans);
    return ans;
  }

  static void resolveClass(Class<?> c) {
    RVMType cls = JikesRVMSupport.getTypeForClass(c);
    cls.resolve();
    cls.instantiate();
    cls.initialize();
  }

  static Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException {
    return BootstrapClassLoader.getBootstrapClassLoader().loadClass(name, resolve);
  }

  static URL getResource(String name) {
    Enumeration<URL> e = getResources(name);
    if (e.hasMoreElements())
      return e.nextElement();
    return null;
  }

  static Enumeration<URL> getResources(String name) {
    StringTokenizer st = new StringTokenizer(
        SystemProperties.getProperty("java.boot.class.path", "."),
        File.pathSeparator);
    Vector<URL> v = new Vector<URL>();
    while (st.hasMoreTokens()) {
      File file = new File(st.nextToken());
      if (file.isDirectory()) {
        try {
          File f = new File(file, name);
          if (!f.exists()) continue;
          v.add(new URL("file://" + f.getAbsolutePath()));
        } catch (MalformedURLException e) {
          throw new Error(e);
        }
      } else if (file.isFile()) {
        ZipFile zip;
        synchronized(bootjars) {
          zip = bootjars.get(file.getName());
        }
        if(zip == null) {
          try {
            zip = new ZipFile(file);
            synchronized(bootjars) {
              bootjars.put(file.getName(), zip);
            }
          } catch (IOException e) {
            continue;
          }
        }
        String zname = name.startsWith("/") ? name.substring(1) : name;
        if (zip.getEntry(zname) == null)
          continue;
        try {
          v.add(new URL("jar:file://" +
                        file.getAbsolutePath() + "!/" + zname));
        } catch (MalformedURLException e) {
          throw new Error(e);
        }
      }
    }
    return v.elements();
  }

  private static String[] getBootPackages() {
    URL indexList = getResource("META-INF/INDEX.LIST");
    if (indexList != null) {
      try {
        Set<String> packageSet = new HashSet<String>();
        String line;
        int lineToSkip = 3;
        BufferedReader reader = new BufferedReader(new InputStreamReader(indexList.openStream()));
        while ((line = reader.readLine()) != null) {
          if (lineToSkip == 0) {
            if (line.length() == 0) {
              lineToSkip = 1;
            } else {
              packageSet.add(line.replace('/', '.'));
            }
          } else {
            lineToSkip--;
          }
        }
        reader.close();
        return packageSet.toArray(new String[packageSet.size()]);
      } catch (IOException e) {
        return new String[0];
      }
    } else {
      return new String[0];
    }
  }


  static Package getPackage(String name) {
    return definedPackages.get(name);
  }

  static Package[] getPackages() {
    Package[] packages = new Package[definedPackages.size()];

    Iterator<Package> it = definedPackages.valueIterator();
    int idx = 0;
    while (it.hasNext()) {
      packages[idx++] = it.next();
    }
    return packages;
  }

  static Class<?> getPrimitiveClass(char type) {
    RVMType t;
    switch (type) {
    case 'Z':
      t = RVMType.BooleanType;
      break;
    case 'B':
      t = RVMType.ByteType;
      break;
    case 'C':
      t = RVMType.CharType;
      break;
    case 'D':
      t = RVMType.DoubleType;
      break;
    case 'F':
      t = RVMType.FloatType;
      break;
    case 'I':
      t = RVMType.IntType;
      break;
    case 'J':
      t = RVMType.LongType;
      break;
    case 'S':
      t = RVMType.ShortType;
      break;
    case 'V':
      t = RVMType.VoidType;
      break;
    default:
      throw new NoClassDefFoundError("Invalid type specifier: " + type);
    }
    return t.getClassForType();
  }

  static boolean defaultAssertionStatus() {
    return true;
  }

  @SuppressWarnings({"unchecked","unused"}) // TODO should this method be deleted ?
  private static Map packageAssertionStatus() {
    return new HashMap();
  }

  @SuppressWarnings({"unchecked","unused"}) // TODO should this method be deleted ?
  private static Map classAssertionStatus() {
    return new HashMap();
  }

  static ClassLoader getSystemClassLoader() {
    return RVMClassLoader.getApplicationClassLoader();
  }

  static Class<?>[] getAllLoadedClasses() {
    Vector<Class<?>> classList = new Vector<Class<?>>();
    for (ImmutableEntryHashMapRVM<String,Class<?>> classes : loadedClasses.values()) {
      for (Class<?> cl : classes.values()) {
        classList.add(cl);
      }
    }
    Class<?>[] result = new Class[classList.size()];
    return classList.toArray(result);
  }

  static Class<?>[] getInitiatedClasses(ClassLoader classLoader) {
    ImmutableEntryHashMapRVM<String,Class<?>> mapForCL = loadedClasses.get(classLoader);
    if (mapForCL == null) return new Class[]{};
    Vector<Class<?>> classList = new Vector<Class<?>>();
    for (Class<?> cl : mapForCL.values())
      classList.add(cl);
    Class<?>[] result = new Class[classList.size()];
    return classList.toArray(result);
  }

  static Class<?> findLoadedClass(ClassLoader cl, String name) {
    ImmutableEntryHashMapRVM<String,Class<?>> mapForCL = loadedClasses.get(cl);
    if (mapForCL == null) return null;
    return mapForCL.get(name);
  }

  private static Instrumentation instrumenter = null;

  static void setInstrumenter(Instrumentation theInstrumenter) {
    instrumenter = theInstrumenter;
  }

  static Class<?> defineClassWithTransformers(ClassLoader loader, String name, byte[] data,
      int offset, int len, ProtectionDomain pd) {

    if (instrumenter != null) {
      byte[] modifiedData = new byte[len];
      System.arraycopy(data, offset, modifiedData, 0, len);
      String jvmName = name.replace('.', '/');
      modifiedData =
        ((InstrumentationImpl)instrumenter).callTransformers(loader, jvmName, null, pd, modifiedData);

      return defineClass(loader, name, modifiedData, 0, modifiedData.length, pd);
    } else {
      return defineClass(loader, name, data, offset, len, pd);
    }
  }
}
