package aQute.bnd.osgi.repository;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.repository.Repository;

import aQute.bnd.osgi.resource.ResourceUtils;
import aQute.bnd.osgi.resource.TypedAttribute;
import aQute.lib.io.IO;
import aQute.lib.tag.Tag;

/**
 * Can turn an OSGi repository into an
 * {@code http://www.osgi.org/xmlns/repository/v1.0.0} XML file. See the
 * Repository spec in OSGi.
 */
public class XMLResourceGenerator {

	private Tag				repository	= new Tag("repository");
	private Set<Resource>	visited		= new HashSet<>();
	private int				indent		= 2;
	private boolean			compress	= false;

	public XMLResourceGenerator() {
		repository.addAttribute("xmlns", "http://www.osgi.org/xmlns/repository/v1.0.0");
	}

	public void save(File location) throws IOException {
		if (location.getName().endsWith(".gz"))
			compress = true;

		IO.mkdirs(location.getParentFile());
		File tmp = IO.createTempFile(location.getParentFile(), "index", ".xml");

		try (OutputStream out = IO.outputStream(tmp)) {
			save(out);
		}
		IO.rename(tmp, location);
	}

	public void save(OutputStream out) throws IOException {
		try {
			if (compress) {
				out = new GZIPOutputStream(out);
			}

			try (Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
					PrintWriter pw = new PrintWriter(writer)) {
					pw.printf("<?xml version='1.0' encoding='UTF-8'?>\n");
					repository.print(indent, pw);
			}
		} finally {
			out.close();
		}
	}

	public XMLResourceGenerator name(String name) {
		repository.addAttribute("name", name);
		repository.addAttribute("increment", System.currentTimeMillis());
		return this;
	}

	public XMLResourceGenerator referral(URI reference, int depth) {
		Tag referall = new Tag(repository, "referral");
		referall.addAttribute("url", reference);
		if (depth > 0)
			referall.addAttribute("depth", depth);
		return this;
	}

	public XMLResourceGenerator repository(Repository repository) throws Exception {
		Requirement wildcard = ResourceUtils.createWildcardRequirement();
		Map<Requirement,Collection<Capability>> findProviders = repository
				.findProviders(Collections.singleton(wildcard));
		for (Capability capability : findProviders.get(wildcard)) {
			resource(capability.getResource());
		}
		return this;
	}

	public XMLResourceGenerator resources(Collection< ? extends Resource> resources) throws Exception {
		for (Resource resource : resources) {
			resource(resource);
		}
		return this;
	}

	public XMLResourceGenerator resource(Resource resource) throws Exception {
		if (!visited.contains(resource)) {
			visited.add(resource);

			Tag r = new Tag(repository, "resource");
			for (Capability cap : resource.getCapabilities(null)) {
				Tag cr = new Tag(r, "capability");
				cr.addAttribute("namespace", cap.getNamespace());
				directives(cr, cap.getDirectives());
				attributes(cr, cap.getAttributes());
			}

			for (Requirement req : resource.getRequirements(null)) {
				Tag cr = new Tag(r, "requirement");
				cr.addAttribute("namespace", req.getNamespace());
				directives(cr, req.getDirectives());
				attributes(cr, req.getAttributes());
			}
		}
		return this;
	}

	private void directives(Tag cr, Map<String,String> directives) {
		for (Entry<String,String> e : directives.entrySet()) {
			Tag d = new Tag(cr, "directive");
			d.addAttribute("name", e.getKey());
			d.addAttribute("value", e.getValue());
		}
	}

	private void attributes(Tag cr, Map<String,Object> atrributes) throws Exception {
		for (Entry<String,Object> e : atrributes.entrySet()) {
			Object value = e.getValue();
			if (value == null)
				continue;

			TypedAttribute ta = TypedAttribute.getTypedAttribute(value);
			if (ta == null)
				continue;

			Tag d = new Tag(cr, "attribute");
			d.addAttribute("name", e.getKey());
			d.addAttribute("value", ta.value);
			if (ta.type != null)
				d.addAttribute("type", ta.type);

		}
	}

	public XMLResourceGenerator indent(int n) {
		this.indent = n;
		return this;
	}

	public XMLResourceGenerator compress() {
		this.compress = true;
		return this;
	}
}
