// Copyright (c) Corporation for National Research Initiatives
// Copyright 2000 Samuele Pedroni

package org.python.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.StringTokenizer;
import java.util.zip.ZipEntry;

import org.python.core.util.RelativeFile;

public class SyspathJavaLoader extends ClassLoader {

    private static final char SLASH_CHAR = '/';

    public SyspathJavaLoader(ClassLoader parent) {
    	super(parent);
    }
    

    /** 
     * Returns a byte[] with the contents read from an InputStream.
     * 
     * The stream is closed after reading the bytes.
     *  
     * @param input The input stream 
     * @param size The number of bytes to read
     *   
     * @return an array of byte[size] with the contents read
     * */
    private byte[] getBytesFromInputStream(InputStream input, int size) {
    	try { 
	    	byte[] buffer = new byte[size];
	        int nread = 0;
	        while(nread < size) {
	            nread += input.read(buffer, nread, size - nread);
	        }
	        return buffer;
    	} catch (IOException exc) {
            return null;
    	} finally {
            try {
                input.close();
            } catch (IOException e) {
            	// Nothing to do
            }
    	}
    }
     
    private byte[] getBytesFromDir(String dir, String name) {
    	try { 
    		File file = getFile(dir, name);
	        if (file == null) {
	            return null;
	        }
	        return getBytesFromInputStream(new FileInputStream(file), (int)file.length());
        } catch (FileNotFoundException e) {
            return null;
        } catch(SecurityException e) {
            return null;
        }

    }
    
    private byte[] getBytesFromArchive(SyspathArchive archive, String name) {
        String entryname = name.replace('.', SLASH_CHAR) + ".class";
        ZipEntry ze = archive.getEntry(entryname);
        if (ze == null) {
            return null;
        }
        try {
			return getBytesFromInputStream(archive.getInputStream(ze), 
					                       (int)ze.getSize());
		} catch (IOException e) {
			return null;
		}
    }

    protected Package definePackageForClass(String name) {
	    int lastDotIndex = name.lastIndexOf('.');
	    if (lastDotIndex < 0) {
	    	return null;
	    }
        String pkgname = name.substring(0, lastDotIndex);
        Package pkg = getPackage(pkgname);
        if (pkg == null) {
            pkg = definePackage(pkgname, null, null, null, null, null, null, null);
        }
        return pkg;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    	PySystemState sys = Py.getSystemState();
    	ClassLoader sysClassLoader = sys.getClassLoader(); 
    	if (sysClassLoader != null) {
    		// sys.classLoader overrides this class loader!
    		return sysClassLoader.loadClass(name);
    	}
        // Search the sys.path for a .class file matching the named class.
    	PyList path = sys.path;
        for (int i = 0; i < path.__len__(); i++) {
        	byte[] buffer;
            PyObject entry = replacePathItem(sys, i, path);
            if (entry instanceof SyspathArchive) {
                SyspathArchive archive = (SyspathArchive)entry;
                buffer = getBytesFromArchive(archive, name);                
            } else {
                if (!(entry instanceof PyUnicode)) {
                    entry = entry.__str__();
                }
                String dir = entry.toString();
            	buffer = getBytesFromDir(dir, name);
            }
            if (buffer != null) {
            	definePackageForClass(name);
            	return defineClass(name, buffer, 0, buffer.length);
            }
        }
        // couldn't find the .class file on sys.path
        throw new ClassNotFoundException(name);
    }
       
    @Override
    protected URL findResource(String res) {
    	PySystemState sys = Py.getSystemState();
    	
    	if (res.charAt(0) == SLASH_CHAR) {
            res = res.substring(1);
        }
    	String entryRes = res;
        if (File.separatorChar != SLASH_CHAR) {
            res = res.replace(SLASH_CHAR, File.separatorChar);
            entryRes = entryRes.replace(File.separatorChar, SLASH_CHAR);
        }

        PyList path = sys.path;
        for (int i = 0; i < path.__len__(); i++) {
            PyObject entry = replacePathItem(sys, i, path);
            if (entry instanceof SyspathArchive) {
                SyspathArchive archive = (SyspathArchive) entry;
                ZipEntry ze = archive.getEntry(entryRes);
                if (ze != null) {
                	try {
						return new URL("jar:file:" + entry.__str__().toString() + "!/" + entryRes);
					} catch (MalformedURLException e) {
						throw new RuntimeException(e);
					}
                }
                continue;
            }
            if (!(entry instanceof PyUnicode)) {
                entry = entry.__str__();
            }
            String dir = sys.getPath(entry.toString());
            try {
				File resource = new File(dir, res);
				if (!resource.exists()) {
					continue;
				}
				return resource.toURI().toURL();
			} catch (MalformedURLException e) {
				throw new RuntimeException(e);
			}
        }
        return null;
    }


    static PyObject replacePathItem(PySystemState sys, int idx, PyList paths) {
        PyObject path = paths.__getitem__(idx);
        if (path instanceof SyspathArchive) {
            // already an archive
            return path;
        }

        try {
            // this has the side affect of adding the jar to the PackageManager during the
            // initialization of the SyspathArchive
            path = new SyspathArchive(sys.getPath(path.toString()));
        } catch (Exception e) {
            return path;
        }
        paths.__setitem__(idx, path);
        return path;
    }

    private File getFile(String dir, String name) {
        String accum = "";
        boolean first = true;
        for (StringTokenizer t = new StringTokenizer(name, "."); t
                .hasMoreTokens();) {
            String token = t.nextToken();
            if (!first) {
                accum += File.separator;
            }
            accum += token;
            first = false;
        }
        return new RelativeFile(dir, accum + ".class");
    }


}
