package bndtools.editor.project;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.bndtools.utils.collections.CollectionUtils;
import org.osgi.service.repository.Repository;

import aQute.bnd.build.Run;
import aQute.bnd.build.Workspace;
import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.clauses.HeaderClause;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.RepositoryPlugin;
import bndtools.central.Central;

class RepositoriesEditModel {
	private final List<Repository>		pluginOrder;
	private final List<HeaderClause>	standalone;
	private final List<HeaderClause>	ignoreStandalone;
	private final List<Repository>		actualOrder	= new ArrayList<>();
	private final Set<Repository>		included	= new HashSet<>();
	private final List<String>			runrepos;
	private final Run					run;
	private final BndEditModel			model;

	RepositoriesEditModel(BndEditModel model) {
		this.model = model;
		this.pluginOrder = model.getWorkspace()
			.getPlugins(Repository.class);
		this.standalone = model.getStandaloneLinks();
		this.runrepos = model.getRunRepos();
		this.ignoreStandalone = model.getIgnoreStandalone();
		this.run = (Run) (model.getProject() instanceof Run ? model.getProject() : null);

		if (runrepos == null) {
			actualOrder.addAll(pluginOrder);
			included.addAll(pluginOrder);
		} else {
			List<Repository> remains = new ArrayList<>(pluginOrder);

			for (String name : runrepos) {
				Repository r = find(name);
				if (r != null) {
					actualOrder.add(r);
					included.add(r);
					remains.remove(r);
				}
			}
			actualOrder.addAll(remains);
		}
	}

	private Repository find(String sought) {
		for (Repository r : pluginOrder) {
			String name = toName(r);
			if (sought.equals(name))
				return r;
		}
		return null;
	}

	boolean up(int[] indexes) {
		boolean result = CollectionUtils.moveUp(actualOrder, indexes);
		commitToModel(model);
		return result;
	}

	boolean down(int[] indexes) {
		boolean result = CollectionUtils.moveDown(actualOrder, indexes);
		commitToModel(model);
		return result;
	}

	boolean isIncluded(Repository repository) {
		return included.contains(repository);
	}

	void toggleInclusion(Repository repository) {
		if (included.contains(repository))
			included.remove(repository);
		else
			included.add(repository);
		model.setRunRepos(getRunRepos());
	}

	public void setIncluded(boolean checked, Repository repository) {
		if (isIncluded(repository) == checked)
			return;

		toggleInclusion(repository);
	}

	boolean canRemove(Repository repository) {
		HeaderClause clause = toHeaderClause(repository);
		return clause != null;
	}

	boolean canAdd(HeaderClause clause) {
		return isStandalone() && !standalone.contains(clause);
	}

	boolean remove(Repository repository) throws Exception {
		HeaderClause clause = toHeaderClause(repository);
		if (clause != null && standalone != null) {
			standalone.remove(clause);
			model.setStandaloneLinks(standalone);
			updateStandaloneWorkspace(model);
			commitToModel(model);
			return true;
		}
		return false;
	}

	List<String> getRunRepos() {
		if (included.size() == actualOrder.size() && pluginOrder.equals(actualOrder))
			return null;

		List<String> out = new ArrayList<>();
		for (Repository r : actualOrder) {
			if (isIncluded(r))
				out.add(toName(r));
		}
		return out;
	}

	private String toName(Repository repo) {
		if (repo instanceof RepositoryPlugin) {
			return ((RepositoryPlugin) repo).getName();
		}
		return repo.toString();
	}

	private HeaderClause toHeaderClause(Repository repository) {
		String name = toName(repository);
		for (HeaderClause clause : standalone) {
			if (name.equals(clause.getName())) {
				return clause;
			}
		}
		return null;
	}

	List<HeaderClause> getStandalone() {
		return this.standalone;
	}

	boolean isStandalone() {
		return standalone != null;
	}

	boolean isDirty() {
		return getRunRepos().equals(runrepos);
	}

	public List<Repository> getOrdered() {
		return Collections.unmodifiableList(actualOrder);
	}

	void commitToModel(BndEditModel model) {
		model.setRunRepos(getRunRepos());
		model.setStandaloneLinks(standalone);
	}

	void add(HeaderClause clause) throws Exception {
		if (canAdd(clause)) {
			standalone.remove(clause);
			standalone.add(clause);
			model.setStandaloneLinks(standalone);
			commitToModel(model);
			updateStandaloneWorkspace(model);
		}
	}

	RepositoriesEditModel toggleStandalone(BndEditModel model) throws Exception {
		if (run == null)
			return this;

		if (isStandalone()) {
			//
			// Set to normal workspace mode
			//
			model.setStandaloneLinks(null);
			model.setIgnoreStandalone(standalone);
			model.setWorkspace(Central.getWorkspace());
		} else {
			//
			// Create standalone
			//
			List<HeaderClause> emptyList = Collections.emptyList();
			model.setStandaloneLinks(ignoreStandalone == null ? emptyList : ignoreStandalone);
			model.setIgnoreStandalone(null);
			updateStandaloneWorkspace(model);
		}

		RepositoriesEditModel r = new RepositoriesEditModel(model);
		return r;
	}

	void updateStandaloneWorkspace(BndEditModel model) throws Exception {

		assert isStandalone();

		Processor properties = model.getProperties();
		model.setWorkspace(Workspace.createStandaloneWorkspace(properties, model.getProject()
			.getPropertiesFile()
			.toURI()));
	}

	public RepositoriesEditModel setStandalone(boolean standalone, BndEditModel model) throws Exception {

		if (isStandalone() == standalone)
			return this;

		return toggleStandalone(model);

	}

	public void setOrder(List<Repository> order) {
		this.actualOrder.clear();
		this.actualOrder.addAll(order);
		commitToModel(model);
	}

	public boolean isStandaloneRepository(Repository r) {
		if (!isStandalone())
			return false;

		String rname = toName(r);

		for (HeaderClause clause : standalone) {
			String name = clause.getAttribs()
				.get("name");
			if (name == null)
				name = clause.getName();

			if (name.equals(rname))
				return true;
		}
		return false;
	}

}
