/*******************************************************************************
 * Copyright (c) 2007, 2018 Alphonse Van Assche and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Alphonse Van Assche - initial API and implementation
 *******************************************************************************/

package org.eclipse.linuxtools.internal.rpm.ui.editor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.linuxtools.rpm.ui.editor.parser.Specfile;
import org.eclipse.linuxtools.rpm.ui.editor.parser.SpecfileElement;

public class SpecfileFoldingStructureProvider {

	private static class ElementByLineNbrComparator implements Comparator<SpecfileElement> {
		@Override
		public int compare(SpecfileElement element1, SpecfileElement element2) {
			Integer lineNbr1 = element1.getLineNumber();
			Integer lineNbr2 = element2.getLineNumber();
			return lineNbr1.compareTo(lineNbr2);
		}
	}

	private static final Annotation[] EMPTY = new Annotation[] {};
	private SpecfileEditor sEditor;
	private IDocument sDocument;
	private IProgressMonitor sProgressMonitor;

	public SpecfileFoldingStructureProvider(SpecfileEditor editor) {
		sEditor = editor;
	}

	public void setProgressMonitor(IProgressMonitor progressMonitor) {
		sProgressMonitor = progressMonitor;
	}

	public void setDocument(IDocument document) {
		sDocument = document;
	}

	public void updateFoldingRegions() {
		ProjectionAnnotationModel model = sEditor.getAdapter(ProjectionAnnotationModel.class);
		if (model != null) {
			updateFoldingRegions(model);
		}
	}

	void updateFoldingRegions(ProjectionAnnotationModel model) {
		Set<Position> structure = createFoldingStructure(sEditor.getSpecfile());
		Annotation[] deletions = computeDifferences(model, structure);
		Map<Annotation, Position> additions = computeAdditions(structure);
		if ((deletions.length != 0 || !additions.isEmpty())
				&& (sProgressMonitor == null || !sProgressMonitor.isCanceled())) {
			model.modifyAnnotations(deletions, additions, EMPTY);
		}
	}

	private Map<Annotation, Position> computeAdditions(Set<Position> currentRegions) {
		Map<Annotation, Position> additionsMap = new HashMap<>();
		for (Position position : currentRegions) {
			additionsMap.put(new ProjectionAnnotation(), position);
		}
		return additionsMap;
	}

	private Annotation[] computeDifferences(ProjectionAnnotationModel model, Set<Position> current) {
		List<Annotation> deletions = new ArrayList<>();
		for (Iterator<Annotation> iter = model.getAnnotationIterator(); iter.hasNext();) {
			Annotation annotation = iter.next();
			if (annotation instanceof ProjectionAnnotation) {
				Position position = model.getPosition(annotation);
				if (current.contains(position)) {
					current.remove(position);
				} else {
					deletions.add(annotation);
				}
			}
		}
		return deletions.toArray(new Annotation[deletions.size()]);
	}

	private Set<Position> createFoldingStructure(Specfile specfile) {
		List<SpecfileElement> elements = new ArrayList<>();
		elements.addAll(specfile.getSections());
		elements.addAll(specfile.getComplexSections());
		Collections.sort(elements, new ElementByLineNbrComparator());
		return addFoldingRegions(elements);
	}

	private Set<Position> addFoldingRegions(List<SpecfileElement> elements) {
		Set<Position> regions = new HashSet<>();
		// add folding on the preamble section
		Position position;
		if (elements.size() > 0) {
			SpecfileElement element = elements.get(0);
			position = new Position(0, element.getLineStartPosition() - 1);
			regions.add(position);
		}

		for (int i = 0; i < elements.size(); i++) {
			SpecfileElement startElement = elements.get(i);
			int offsetPos = startElement.getLineStartPosition();
			int lenghtPos;
			if (i < elements.size() - 1) {
				SpecfileElement endElement = elements.get(i + 1);
				lenghtPos = endElement.getLineStartPosition() - startElement.getLineStartPosition() - 1;
			} else {
				lenghtPos = sDocument.getLength() - startElement.getLineStartPosition();
			}
			position = new Position(offsetPos, lenghtPos);
			regions.add(position);
		}
		return regions;
	}
}
