package org.osgi.service.indexer.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.osgi.service.indexer.Resource;

public class JarResource implements Resource {

	private final File							file;
	private final JarFile						jarFile;
	private final String						location;

	private final Dictionary<String,Object>		properties	= new Hashtable<String,Object>();

	private final Map<String,List<JarEntry>>	prefixMap	= new HashMap<String,List<JarEntry>>();
	private final Map<String,JarEntry>			paths		= new HashMap<String,JarEntry>();

	private Manifest							manifest;

	public JarResource(File file) throws IOException {
		this.file = file;

		this.location = file.getPath().replace(File.separatorChar, '/');
		this.jarFile = new JarFile(file);

		properties.put(NAME, file.getName());
		properties.put(LOCATION, location);
		properties.put(SIZE, file.length());
		properties.put(LAST_MODIFIED, file.lastModified());

		Enumeration<JarEntry> entries = jarFile.entries();
		while (entries.hasMoreElements()) {
			JarEntry entry = entries.nextElement();

			String path = entry.getName();
			paths.put(path, entry);

			String parentPath = getParentPath(path);

			List<JarEntry> list = getOrCreatePrefix(parentPath);
			list.add(entry);
		}
	}

	private static String getParentPath(String path) {
		int index;
		if (path.endsWith("/")) {
			index = path.substring(0, path.length() - 1).lastIndexOf("/");
		} else {
			index = path.lastIndexOf("/");
		}

		String parentPath;
		if (index == -1)
			parentPath = "";
		else
			parentPath = path.substring(0, index + 1);
		return parentPath;
	}

	private synchronized List<JarEntry> getOrCreatePrefix(String prefix) {
		List<JarEntry> list = prefixMap.get(prefix);
		if (list == null) {
			list = new LinkedList<JarEntry>();
			prefixMap.put(prefix, list);
		}
		return list;
	}

	public String getLocation() {
		return location;
	}

	public Dictionary<String,Object> getProperties() {
		return properties;
	}

	public long getSize() {
		return file.length();
	}

	public InputStream getStream() throws IOException {
		return Files.newInputStream(file.toPath());
	}

	public Manifest getManifest() throws IOException {
		synchronized (this) {
			if (manifest == null) {
				Resource manifestResource = getChild("META-INF/MANIFEST.MF");
				if (manifestResource != null) {
					try (InputStream in = manifestResource.getStream()) {
						manifest = new Manifest(in);
					} finally {
						manifestResource.close();
					}
				}
			}
			return manifest;
		}
	}

	public List<String> listChildren(String prefix) throws IOException {
		List<JarEntry> entries = prefixMap.get(prefix);
		if (entries == null)
			return null;

		List<String> result = new ArrayList<String>(entries.size());
		for (JarEntry entry : entries) {
			String unprefixedPath = entry.getName().substring(prefix.length());
			result.add(unprefixedPath);
		}
		return result;
	}

	public Resource getChild(String path) throws IOException {
		String childLocation = getLocation() + "#" + path;

		JarEntry entry = paths.get(path);
		if (entry == null)
			return null;
		return new FlatStreamResource(path, childLocation, jarFile.getInputStream(entry));
	}

	public void close() {
		try {
			jarFile.close();
		} catch (IOException e) {
			// Don't care
		}
	}

}
