/*******************************************************************************
 * Copyright (c) 2015, 2017 IBM Corporation.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.tool;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipException;

import javax.tools.JavaFileObject;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.tool.ModuleLocationHandler.ModuleLocationWrapper;
import org.eclipse.jdt.internal.compiler.util.JRTUtil;

public class JrtFileSystem extends Archive {

	private static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$
	
	static final String BOOT_MODULE = "jrt-fs.jar"; //$NON-NLS-1$
	
	public HashMap<String, Path> modulePathMap;
	Path modules;
	private java.nio.file.FileSystem jrtfs;
	
	public JrtFileSystem(File file) throws ZipException, IOException {
		this.file = file;
		initialize();
	}
	
	public void initialize() throws IOException {
		// initialize packages
		this.modulePathMap = new HashMap<>();
		URL jrtPath = null;

		if (this.file.exists()) {
			jrtPath = Paths.get(this.file.toPath().toString(), "lib", JRTUtil.JRT_FS_JAR).toUri().toURL(); //$NON-NLS-1$
			try (URLClassLoader loader = new URLClassLoader(new URL[] { jrtPath })) {
				HashMap<String, ?> env = new HashMap<>();
				this.jrtfs = FileSystems.newFileSystem(JRT_URI, env, loader);
				this.modules = this.jrtfs.getPath("/modules"); //$NON-NLS-1$
			}
		} else {
			return;
		}

		org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(this.file,
				new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() {

			@Override
			public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs)
					throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitFile(Path f, Path mod, BasicFileAttributes attrs)
					throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitModule(Path mod) throws IOException {
				JrtFileSystem.this.modulePathMap.put(mod.getFileName().toString(), mod);
				return FileVisitResult.CONTINUE;
			}
		}, JRTUtil.NOTIFY_MODULES);
	}

	public List<JrtFileObject> list(ModuleLocationWrapper location, String packageName,
			Set<JavaFileObject.Kind> kinds, boolean recurse, Charset charset) {
    	String module = location.modName;
    	Path mPath = this.modules.resolve(module);
    	Path resolve = mPath.resolve(packageName);
    	java.util.List<Path> files = null;
        try (Stream<Path> p = Files.list(resolve)) {
            files = p.filter((path) -> {
            	if (Files.isDirectory(path))
            		return false;
            	else 
            		return true;
            }).collect(Collectors.toList());
        } catch (IOException e) {
        	// ignore
        }
        List<JrtFileObject> result = new ArrayList<>();
        for (Path p: files) {
        	result.add(new JrtFileObject(this.file, p, module, charset));
        }
        return result;
    }
	@Override
	public ArchiveFileObject getArchiveFileObject(String fileName, String module, Charset charset) {
		return new JrtFileObject(this.file, this.modules.resolve(module).resolve(fileName), module, charset);
	}

	@Override
	public boolean contains(String entryName) {
		// FIXME
		return false;
	}

	@Override
	public String toString() {
		return "JRT: " + (this.file == null ? "UNKNOWN_ARCHIVE" : this.file.getAbsolutePath()); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	class JrtFileObject extends ArchiveFileObject {
		String module;
		Path path;
		private JrtFileObject(File file, Path path, String module, Charset charset) {
			super(file, path.toString(), charset);
			this.path = path;
		}

		@Override
		protected void finalize() throws Throwable {
			// Nothing to do here
		}

		@Override
		protected ClassFileReader getClassReader() {
			ClassFileReader reader = null;
			try {
				byte[] content = JRTUtil.getClassfileContent(this.file, this.entryName, this.module);
				if (content == null) return null;
				return new ClassFileReader(content, this.entryName.toCharArray());
			} catch (ClassFormatException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return reader;
		}
		

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#getCharContent(boolean)
		 */
		@Override
		public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
			try {
				return Util.getCharContents(this, ignoreEncodingErrors,
						org.eclipse.jdt.internal.compiler.util.JRTUtil.getClassfileContent(this.file, this.entryName, this.module),
						this.charset.name());
			} catch (ClassFormatException e) {
				e.printStackTrace();
				return null;
			}
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#getLastModified()
		 */
		@Override
		public long getLastModified() {
			return 0;
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#getName()
		 */
		@Override
		public String getName() {
			return this.path.toString();
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#openInputStream()
		 */
		@Override
		public InputStream openInputStream() throws IOException {
			return Files.newInputStream(this.path);
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#openOutputStream()
		 */
		@Override
		public OutputStream openOutputStream() throws IOException {
			throw new UnsupportedOperationException();
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#openReader(boolean)
		 */
		@Override
		public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
			throw new UnsupportedOperationException();
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#openWriter()
		 */
		@Override
		public Writer openWriter() throws IOException {
			throw new UnsupportedOperationException();
		}

		/* (non-Javadoc)
		 * @see javax.tools.FileObject#toUri()
		 */
		@Override
		public URI toUri() {
			try {
				return new URI("JRT:" + this.file.toURI().getPath() + "!" + this.entryName); //$NON-NLS-1$//$NON-NLS-2$
			} catch (URISyntaxException e) {
				return null;
			}
		}


		@Override
		public String toString() {
			return this.file.getAbsolutePath() + "[" + this.entryName + "]";//$NON-NLS-1$//$NON-NLS-2$
		}	
	}
}
