package aQute.bnd.deployer.repository.wrapper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.osgi.framework.FrameworkUtil;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.indexer.ResourceIndexer.IndexResult;
import org.osgi.service.indexer.impl.KnownBundleAnalyzer;
import org.osgi.service.indexer.impl.RepoIndex;

import aQute.bnd.build.DownloadBlocker;
import aQute.bnd.osgi.repository.BaseRepository;
import aQute.bnd.osgi.resource.CapReqBuilder;
import aQute.bnd.osgi.resource.PersistentResource;
import aQute.bnd.osgi.resource.ResourceBuilder;
import aQute.bnd.service.repository.InfoRepository;
import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor;
import aQute.bnd.version.Version;
import aQute.lib.collections.MultiMap;
import aQute.lib.filter.Filter;
import aQute.lib.hex.Hex;
import aQute.lib.persistentmap.PersistentMap;

public class InfoRepositoryWrapper extends BaseRepository {
	final RepoIndex								repoIndexer;
	final PersistentMap<PersistentResource>		persistent;
	final Collection< ? extends InfoRepository>	repos;;
	long										lastTime	= 0;
	private Properties							augments	= new Properties();

	// private boolean inited;

	public InfoRepositoryWrapper(File dir, Collection< ? extends InfoRepository> repos) throws Exception {
		this.repoIndexer = new RepoIndex();

		KnownBundleAnalyzer knownBundleAnalyzer = new KnownBundleAnalyzer();
		this.augments = new Properties();
		knownBundleAnalyzer.setKnownBundlesExtra(this.augments);

		this.repoIndexer.addAnalyzer(knownBundleAnalyzer, FrameworkUtil.createFilter("(name=*)"));
		this.repos = repos;
		this.persistent = new PersistentMap<PersistentResource>(dir, PersistentResource.class);
	}

	boolean init() {
		try {
			if (System.currentTimeMillis() < lastTime + 10000)
				return true;
		} finally {
			lastTime = System.currentTimeMillis();
		}

		Set<String> errors = new LinkedHashSet<String>();

		try {
			//
			// Get the current repo contents
			//

			Set<String> toBeDeleted = new HashSet<String>(persistent.keySet());
			Map<String,DownloadBlocker> blockers = new HashMap<String,DownloadBlocker>();

			for (InfoRepository repo : repos) {
				Map<String,ResourceDescriptor> map = collectKeys(repo);

				for (final Map.Entry<String,ResourceDescriptor> entry : map.entrySet()) {
					final String id = entry.getKey();

					toBeDeleted.remove(id);

					if (persistent.containsKey(id))
						continue;

					final ResourceDescriptor rd = entry.getValue();

					DownloadBlocker blocker = new DownloadBlocker(null) {

						//
						// We steal the thread of the downloader to index
						//

						@Override
						public void success(File file) throws Exception {
							IndexResult index = null;
							try {
								index = repoIndexer.indexFile(file);

								ResourceBuilder rb = new ResourceBuilder();

								//
								// Unfortunately, we need to convert the
								// caps/reqs
								// since they are not real caps/reqs
								//

								for (org.osgi.service.indexer.Capability capability : index.capabilities) {
									CapReqBuilder cb = new CapReqBuilder(capability.getNamespace());
									cb.addAttributes(capability.getAttributes());
									cb.addDirectives(capability.getDirectives());
									rb.addCapability(cb.buildSyntheticCapability());
								}
								for (org.osgi.service.indexer.Requirement requirement : index.requirements) {
									CapReqBuilder cb = new CapReqBuilder(requirement.getNamespace());
									cb.addAttributes(requirement.getAttributes());
									cb.addDirectives(requirement.getDirectives());
									rb.addRequirement(cb.buildSyntheticRequirement());
								}

								Resource resource = rb.build();

								PersistentResource pr = new PersistentResource(resource);
								persistent.put(id, pr);
							} finally {
								super.success(file);

								if (index != null) {
									index.resource.close();
								}
							}
						}
					};
					blockers.put(entry.getKey(), blocker);
					repo.get(rd.bsn, rd.version, null, blocker);
				}

			}

			for (Entry<String,DownloadBlocker> entry : blockers.entrySet()) {
				String key = entry.getKey();
				DownloadBlocker blocker = entry.getValue();

				String reason = blocker.getReason();
				if (reason != null) {
					errors.add(key + ": " + reason);
				}
			}
			persistent.keySet().removeAll(toBeDeleted);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		if (!errors.isEmpty())
			throw new IllegalStateException("Cannot index " + repos + " due to " + errors);

		return true;
	}

	/**
	 * The repository method
	 * 
	 * @throws Exception
	 */

	public void findProviders(Map<Requirement,List<Capability>> result, Collection< ? extends Requirement> requirements)
			throws Exception {
		init();

		nextReq: for (Requirement req : requirements) {
			String f = req.getDirectives().get("filter");
			if (f == null)
				continue nextReq;

			Filter filter = new Filter(f);

			for (PersistentResource presource : persistent.values()) {
				Resource resource = presource.getResource();
				for (Capability cap : resource.getCapabilities(req.getNamespace())) {
					if (filter.matchMap(cap.getAttributes())) {
						List<Capability> l = result.get(req);
						if (l == null)
							result.put(req, l = new ArrayList<Capability>());
						l.add(cap);
					}
				}
			}
		}
	}

	@SuppressWarnings({
			"unchecked", "rawtypes"
	})
	public Map<Requirement,Collection<Capability>> findProviders(Collection< ? extends Requirement> requirements) {
		MultiMap<Requirement,Capability> result = new MultiMap<Requirement,Capability>();
		try {
			findProviders(result, requirements);
			return (Map) result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/*
	 * Get all the shas from the repo
	 */
	private Map<String,ResourceDescriptor> collectKeys(InfoRepository repo) throws Exception {
		Map<String,ResourceDescriptor> map = new HashMap<String,ResourceDescriptor>();

		for (String bsn : repo.list(null)) {
			for (Version version : repo.versions(bsn)) {
				ResourceDescriptor rd = repo.getDescriptor(bsn, version);
				if (rd != null)
					map.put(Hex.toHexString(rd.id), rd);
			}
		}
		return map;
	}

	public String toString() {
		return "InfoRepositoryWrapper[" + repos.size() + "]";
	}

	public void close() throws IOException {
		persistent.close();
	}

	public void addAugment(Properties properties) {
		augments.putAll(properties);
	}

	public void clear() {
		this.persistent.clear();
	}

	/**
	 * Clear all files that were indexed before this date
	 * 
	 * @param whenOlder
	 */
	public void clear(long whenOlder) {
		persistent.clear(whenOlder);
	}

}
