/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIESOR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.aries.util.filesystem.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import org.apache.aries.util.IORuntimeException;
import org.apache.aries.util.filesystem.ICloseableDirectory;
import org.apache.aries.util.filesystem.IDirectory;
import org.apache.aries.util.filesystem.IFile;
import org.apache.aries.util.io.IOUtils;

public class NestedZipDirectory extends NestedZipFile implements IDirectory {
	public NestedZipDirectory(IFile archive, ZipEntry entry, NestedZipDirectory parent, NestedCloseableDirectory cache) {
		super(archive, entry, parent, cache);
	}

	public NestedZipDirectory(IFile archive, String pathInZip, NestedZipDirectory parent, NestedCloseableDirectory cache) {
		super(archive, pathInZip, parent, cache);
	}

	public NestedZipDirectory(IFile archive) {
		super(archive);
	}

	public NestedZipDirectory(NestedZipDirectory other, NestedCloseableDirectory cache) {
		super(other, cache);
	}

	public IDirectory convert() {
		return this;
	}

	public Iterator<IFile> iterator() {
		return listFiles().iterator();
	}

	public List<IFile> listFiles() {
		return listFiles(false);
	}

	public List<IFile> listAllFiles() {
		return listFiles(true);
	}

	private List<IFile> listFiles(boolean includeFilesInNestedSubdirs) {
			Map<String, ZipEntry> entriesByName = new LinkedHashMap<String, ZipEntry>();
			for (ZipEntry entry : getAllEntries()) {
				if (ZipDirectory.isInDir(getNameInZip(), entry, includeFilesInNestedSubdirs)) {
					entriesByName.put(entry.getName(), entry);
				}
			}

			List<IFile> files = new ArrayList<IFile>();
			for (ZipEntry ze : entriesByName.values()) {
				NestedZipDirectory parent = includeFilesInNestedSubdirs ? buildParent(ze, entriesByName) : this;
				if (ze.isDirectory()) files.add(new NestedZipDirectory(archive, ze, parent, cache));
				else files.add(new NestedZipFile(archive, ze, parent, cache));
			}

			return files;
	}

	private List<? extends ZipEntry> getAllEntries() {
		if (cache != null && !cache.isClosed()) {
			return Collections.list(cache.getZipFile().entries());
		} else {
			ZipInputStream zis = null;
			try {
				zis = new ZipInputStream(archive.open());

				List<ZipEntry> result = new ArrayList<ZipEntry>();
				ZipEntry entry = zis.getNextEntry();
				while (entry != null) {
					result.add(entry);
					entry = zis.getNextEntry();
				}

				return result;
			} catch (IOException e) {
				throw new IORuntimeException("IOException reading nested ZipFile", e);
			} finally {
				IOUtils.close(zis);
			}
		}
	}

	private NestedZipDirectory buildParent(ZipEntry entry, Map<String,ZipEntry> entries) {
		NestedZipDirectory result = this;

		String path = entry.getName().substring(getNameInZip().length());
		String[] segments = path.split("/");

		if (segments != null && segments.length > 1) {
			StringBuilder entryPath = new StringBuilder(getNameInZip());
			for (int i=0; i<segments.length-1; i++) {
				String p = segments[i];
				entryPath.append(p).append("/");
				ZipEntry ze = entries.get(entryPath.toString());

				if (ze != null) {
					result = new NestedZipDirectory(archive, ze, result, cache);
				} else {
					result = new NestedZipDirectory(archive, entryPath.toString(), result, cache);
				}
			}
		}

		return result;
	}

	public IFile getFile(String name) {
		Map<String,ZipEntry> entries = new HashMap<String, ZipEntry>();
		ZipEntry ze;

		if (cache != null && !cache.isClosed()) {
			ZipFile zip = cache.getZipFile();

			String[] segments = name.split("/");
			StringBuilder path = new StringBuilder();
			for (String s : segments) {
				path.append(s).append('/');
				ZipEntry p = zip.getEntry(path.toString());
				if (p != null) entries.put(path.toString(), p);
			}

			ze = zip.getEntry(name);

		} else {
			ZipInputStream zis = null;

			try {
				zis = new ZipInputStream(archive.open());

				ze = zis.getNextEntry();

				while (ze != null && !ze.getName().equals(name)) {
					if (name.startsWith(ze.getName())) entries.put(ze.getName(), ze);

					ze = zis.getNextEntry();
				}
			} catch (IOException e) {
				throw new IORuntimeException("IOException reading nested ZipFile", e);
			} finally {
				IOUtils.close(zis);
			}
		}

		if (ze != null) {
			NestedZipDirectory parent = buildParent(ze, entries);
			if (ze.isDirectory()) return new NestedZipDirectory(archive, ze, parent, cache);
			else return new NestedZipFile(archive, ze, parent, cache);
		} else {
			return null;
		}
	}


	public boolean isDirectory() {
		return true;
	}

	public InputStream open() throws IOException, UnsupportedOperationException {
		throw new UnsupportedOperationException();
	}

	public boolean isFile() {
		return false;
	}

	public boolean isRoot() {
		return false;
	}

	public ICloseableDirectory toCloseable() {
		try {
			return new NestedCloseableDirectory(archive, this);
		} catch (IOException e) {
			throw new IORuntimeException("Exception while creating extracted version of nested zip file", e);
		}
	}
}
