
package org.python.modules;

import org.python.core.Py;
import org.python.core.PyFile;
import org.python.core.PyList;
import org.python.core.PyModule;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PySystemState;
import org.python.core.PyTuple;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/*
 * A bogus implementation of the CPython builtin module "imp".
 * Only the functions required by IDLE and PMW are implemented.
 * Luckily these function are also the only function that IMO can
 * be implemented under Jython.
 */

public class imp {
    public static PyString __doc__ = new PyString(
        "This module provides the components needed to build your own\n"+
        "__import__ function.  Undocumented functions are obsolete.\n"
    );

    public static final int PY_SOURCE = 1;
    public static final int PY_COMPILED = 2;
    public static final int C_EXTENSION = 3;
    public static final int PKG_DIRECTORY = 5;
    public static final int C_BUILTIN = 6;
    public static final int PY_FROZEN = 7;
    public static final int IMP_HOOK = 9;

    private static class ModuleInfo {
        PyObject file;
        String filename;
        String suffix;
        String mode;
        int type;
        ModuleInfo(PyObject file, String filename, String suffix, String mode, int type) {
            this.file = file;
            this.filename = filename;
            this.suffix = suffix;
            this.mode = mode;
            this.type = type;
        }
    }

    private static PyObject newFile(File file) {
        try {
            return new PyFile(new FileInputStream(file));
        } catch (IOException ioe) {
            throw Py.IOError(ioe);
        }
    }

    private static boolean caseok(File file, String filename) {
        return org.python.core.imp.caseok(file, filename);
    }

    /**
     * This needs to be consolidated with the code in (@see org.python.core.imp).
     *
     * @param name module name
     * @param entry a path String
     * @param findingPackage if looking for a package only try to locate __init__
     * @return null if no module found otherwise module information
     */
    static ModuleInfo findFromSource(String name, String entry, boolean findingPackage,
                                     boolean preferSource) {
        String sourceName = "__init__.py";
        String compiledName = "__init__$py.class";
        String directoryName = PySystemState.getPathLazy(entry);
        // displayDirName is for identification purposes: when null it
        // forces java.io.File to be a relative path (e.g. foo/bar.py
        // instead of /tmp/foo/bar.py)
        String displayDirName = entry.equals("") ? null : entry;

        // First check for packages
        File dir = findingPackage ? new File(directoryName) : new File(directoryName, name);
        File sourceFile = new File(dir, sourceName);
        File compiledFile = new File(dir, compiledName);

        boolean pkg = dir.isDirectory() && caseok(dir, name) && (sourceFile.isFile()
                                                                 || compiledFile.isFile());

        if(!findingPackage) {
            if(pkg) {
                return new ModuleInfo(Py.None, new File(displayDirName, name).getPath(),
                                      "", "", PKG_DIRECTORY);
            } else {
                Py.writeDebug("import", "trying source " + dir.getPath());
                sourceName = name + ".py";
                compiledName = name + "$py.class";
                sourceFile = new File(directoryName, sourceName);
                compiledFile = new File(directoryName, compiledName);
            }
        }

        if (sourceFile.isFile() && caseok(sourceFile, sourceName)) {
            if (!preferSource && compiledFile.isFile() && caseok(compiledFile, compiledName)) {
                Py.writeDebug("import", "trying precompiled " + compiledFile.getPath());
                long pyTime = sourceFile.lastModified();
                long classTime = compiledFile.lastModified();
                if (classTime >= pyTime) {
                    return new ModuleInfo(newFile(compiledFile),
                                          new File(displayDirName, compiledName).getPath(),
                                          ".class", "rb", PY_COMPILED);
                }
            }
            return new ModuleInfo(newFile(sourceFile),
                                  new File(displayDirName, sourceName).getPath(),
                                  ".py", "r", PY_SOURCE);
        }

        // If no source, try loading precompiled
        Py.writeDebug("import", "trying " + compiledFile.getPath());
        if (compiledFile.isFile() && caseok(compiledFile, compiledName)) {
            return new ModuleInfo(newFile(compiledFile),
                    new File(displayDirName, compiledName).getPath(),
                                  ".class", "rb", PY_COMPILED);
        }
        return null;
    }

    public static PyObject load_dynamic(String name, String pathname) {
        return load_dynamic(name, pathname, null);
    }

    public static PyObject load_dynamic(String name, String pathname, PyObject file) {
        throw Py.ImportError("No module named " + name);
    }

    public static PyObject load_source(String modname, String filename) {
        return load_source(modname, filename, null);
    }

    public static PyObject load_source(String modname, String filename, PyObject file) {
        PyObject mod = Py.None;
        if (file == null) {
            // XXX: This should load the accompanying byte code file instead, if it exists
            file = new PyFile(filename, "r", 1024);
        }
        Object o = file.__tojava__(InputStream.class);
        if (o == Py.NoConversion) {
            throw Py.TypeError("must be a file-like object");
        }
        PySystemState sys = Py.getSystemState();
        String compiledFilename =
                org.python.core.imp.makeCompiledFilename(sys.getPath(filename));
        mod = org.python.core.imp.createFromSource(modname.intern(), (InputStream)o,
                                                   filename, compiledFilename);
        PyObject modules = sys.modules;
        modules.__setitem__(modname.intern(), mod);
        return mod;
    }

    public static PyObject load_compiled(String name, String pathname) {
        return load_compiled(name, pathname, new PyFile(pathname, "rb", -1));
    }

    public static PyObject load_compiled(String name, String pathname, PyObject file) {
        InputStream stream = (InputStream) file.__tojava__(InputStream.class);
        if (stream == Py.NoConversion) {
            throw Py.TypeError("must be a file-like object");
        }

        // XXX: Ideally we wouldn't care about sourceName here (see
        // http://bugs.jython.org/issue1605847 msg3805)
        String sourceName = pathname;
        if (sourceName.endsWith("$py.class")) {
            sourceName = sourceName.substring(0, sourceName.length() - 9) + ".py";
        }
        return org.python.core.imp.loadFromCompiled(name.intern(), stream, sourceName, pathname);
    }

    public static PyObject find_module(String name) {
        return find_module(name, Py.None);
    }

    public static PyObject find_module(String name, PyObject path) {
        if (path == Py.None && PySystemState.getBuiltin(name) != null) {
            return new PyTuple(Py.None, Py.newString(name),
                               new PyTuple(Py.EmptyString, Py.EmptyString,
                                           Py.newInteger(C_BUILTIN)));
        }

        if (path == Py.None) {
            path = Py.getSystemState().path;
        }
        for (PyObject p : path.asIterable()) {
            ModuleInfo mi = findFromSource(name, p.toString(), false, true);
            if(mi == null) {
                continue;
            }
            return new PyTuple(mi.file,
                               new PyString(mi.filename),
                               new PyTuple(new PyString(mi.suffix),
                                           new PyString(mi.mode),
                                           Py.newInteger(mi.type)));
        }
        throw Py.ImportError("No module named " + name);
    }

    public static PyObject load_module(String name, PyObject file, PyObject filename, PyTuple data) {
        PyObject mod = Py.None;
        PySystemState sys = Py.getSystemState();
        int type = data.__getitem__(2).asInt();
        while(mod == Py.None) {
            String compiledName;
            switch (type) {
                case PY_SOURCE:
                    Object o = file.__tojava__(InputStream.class);
                    if (o == Py.NoConversion) {
                        throw Py.TypeError("must be a file-like object");
                    }

                    // XXX: This should load the accompanying byte code file instead, if it exists
                    String resolvedFilename = sys.getPath(filename.toString());
                    compiledName = org.python.core.imp.makeCompiledFilename(resolvedFilename);
                    if (name.endsWith(".__init__")) {
                        name = name.substring(0, name.length() - ".__init__".length());
                    } else if (name.equals("__init__")) {
                        name = new File(sys.getCurrentWorkingDir()).getName();
                    }

                    File fp = new File(resolvedFilename);
                    long mtime = -1;
                    if (fp.isFile()) {
                        mtime = fp.lastModified();
                    }

                    mod = org.python.core.imp.createFromSource(name.intern(),
                                                               (InputStream)o,
                                                               filename.toString(),
                                                               compiledName,
                                                               mtime);
                    break;
                case PY_COMPILED:
                    mod = load_compiled(name, filename.toString(), file);
                    break;
                case PKG_DIRECTORY:
                    PyModule m = org.python.core.imp.addModule(name);
                    m.__dict__.__setitem__("__path__", new PyList(new PyObject[] {filename}));
                    m.__dict__.__setitem__("__file__", filename);
                    ModuleInfo mi = findFromSource(name, filename.toString(), true, true);
                    type = mi.type;
                    file = mi.file;
                    filename = new PyString(mi.filename);
                    break;
                default:
                    throw Py.ImportError("No module named " + name);
            }
        }
        PyObject modules = sys.modules;
        modules.__setitem__(name.intern(), mod);
        return mod;
    }

    public static PyObject get_suffixes() {
        return new PyList(new PyObject[] {new PyTuple(new PyString(".py"),
                                                      new PyString("r"),
                                                      Py.newInteger(PY_SOURCE)),
                                          new PyTuple(new PyString("$py.class"),
                                                      new PyString("rb"),
                                                      Py.newInteger(PY_COMPILED)),});
    }

    public static PyModule new_module(String name) {
        return new PyModule(name, null);
    }

    public static boolean is_builtin(String name) {
        return PySystemState.getBuiltin(name) != null;
    }

    public static boolean is_frozen(String name) {
        return false;
    }

    /**
     * Acquires the interpreter's import lock for the current thread.
     *
     * This lock should be used by import hooks to ensure
     * thread-safety when importing modules.
     *
     */
    public static void acquire_lock() {
        org.python.core.imp.importLock.lock();
    }

    /**
     * Release the interpreter's import lock.
     *
     */
    public static void release_lock() {
        try{
            org.python.core.imp.importLock.unlock();
        }catch(IllegalMonitorStateException e){
            throw Py.RuntimeError("not holding the import lock");
        }
    }

    /**
     * Return true if the import lock is currently held, else false.
     *
     * @return true if the import lock is currently held, else false.
     */
    public static boolean lock_held() {
        return org.python.core.imp.importLock.isHeldByCurrentThread();
    }
}
