/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.text.tests.link;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.link.ILinkedModeListener;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;


public class LinkedModeModelTest {

	private List<LinkedPosition> fPositions= new LinkedList<>();

	private List<IDocument[]> fDocumentMap= new ArrayList<>();
	
	@Test
	public void testUpdate() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.forceInstall();

		// edit the document
		doc1.replace(1, 9, "GRETCHEN");

		assertEquals(group1, "GRETCHEN");
		assertUnchanged(group1);
	}
	
	@Test
	public void testUpdateUnequalContent() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);
		
		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "Allumfasser");
		createLinkedPositions(group1, doc1, "Gott");
		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.forceInstall();
		
		// edit the document
		doc1.replace(GARTEN1.indexOf("Gott"), 4, "SUPERMAN");
		
		assertEquals(group1, "SUPERMAN");
		assertUnchanged(group1);
	}
	
	@Test
	public void testUpdateTwoGroups() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);

		env.forceInstall();


		// edit the document
		doc1.replace(7, 3, "INE");

		assertEquals(group1, "MARGARINE");
		assertEquals(group2, "FAUST");
		assertUnchanged(group1, group2);
	}
	
	@Test
	public void testUpdateMultipleGroups() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);

		env.forceInstall();


		// edit the document
		doc1.replace(7, 3, "INE");
		doc1.replace(42, 1, "");
		doc1.replace(44, 2, "GE");

		assertEquals(group1, "MARGARINE");
		assertEquals(group2, "AUGE");
		assertUnchanged(group1, group2);
	}
	
	@Test
	public void testUpdateMultiDocument() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);
		IDocument doc2= new Document(GARTEN2);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		createLinkedPositions(group1, doc2, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");
		createLinkedPositions(group2, doc2, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);

		env.forceInstall();


		// edit the document
		doc1.replace(7, 3, "INE");
		doc1.replace(42, 1, "");
		doc1.replace(44, 2, "GE");

		assertEquals(group1, "MARGARINE");
		assertEquals(group2, "AUGE");
		assertUnchanged(group1, group2);

	}
	
	@Test
	public void testAddCompatibleGroups() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		try {
			env.addGroup(group1);
			env.addGroup(group2);
		} catch (BadLocationException e) {
			assertFalse(true);
		}
		assertUnchanged(group1, group2);

	}
	
	@Test
	public void testAddIncompatibleGroups() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "MARGA");

		LinkedModeModel env= new LinkedModeModel();
		try {
			env.addGroup(group1);
			env.addGroup(group2);
		} catch (BadLocationException e) {
			return;
		}
		assertFalse(true);
	}
	
	@Test
	public void testAddNullGroup() throws BadLocationException {
		LinkedModeModel env= new LinkedModeModel();
		try {
			env.addGroup(null);
		} catch (IllegalArgumentException e) {
			return;
		}

		assertFalse(true);
	}
	
	@Test
	public void testAddGroupWhenSealed() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.forceInstall();

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");
		try {
			env.addGroup(group2);
		} catch (IllegalStateException e) {
			return;
		}

		assertFalse(true);
	}
	
	@Test
	public void testDoubleInstall() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);

		env.forceInstall();

		try {
			env.forceInstall();
		} catch (IllegalStateException e) {
			return;
		}

		assertFalse(true);
	}
	
	@Test
	public void testEmptyInstall() throws BadLocationException {
		LinkedModeModel env= new LinkedModeModel();

		try {
			env.forceInstall();
		} catch (IllegalStateException e) {
			return;
		}

		assertFalse(true);
	}
	
	@Test
	public void testNestedUpdate() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);

		env.forceInstall();

		// second level

		LinkedPositionGroup group1_2= new LinkedPositionGroup();
		group1_2.addPosition(new LinkedPosition(doc1, 7, 3, LinkedPositionGroup.NO_STOP));


		LinkedModeModel childEnv= new LinkedModeModel();
		childEnv.addGroup(group1_2);
		childEnv.forceInstall();

		assertTrue(childEnv.isNested());
		assertFalse(env.isNested());


		// edit the document
		doc1.replace(7, 3, "INE");

		assertEquals(group1_2, "INE");
		assertEquals(group1, "MARGARINE");
		assertEquals(group2, "FAUST");
		assertUnchanged(group1, group2);
	}
	
	@Test
	public void testNestedForceInstall() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);

		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});

		env.forceInstall();


		// second level

		LinkedPositionGroup group1_2= new LinkedPositionGroup();

		group1_2.addPosition(new LinkedPosition(doc1, 12, 3, LinkedPositionGroup.NO_STOP));

		LinkedModeModel childEnv= new LinkedModeModel();
		childEnv.addGroup(group1_2);
		childEnv.forceInstall();

		assertFalse(childEnv.isNested());
		assertTrue(isExit[0]);


		// edit the document
		doc1.replace(12, 3, "INE");

		assertEquals(group1_2, "INE");
	}
	
	@Test
	public void testNestedTryInstall() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		env.addGroup(group1);
		env.addGroup(group2);
		env.forceInstall();


		// second level

		LinkedPositionGroup group1_2= new LinkedPositionGroup();
		group1_2.addPosition(new LinkedPosition(doc1, 12, 3, LinkedPositionGroup.NO_STOP));

		LinkedModeModel childEnv= new LinkedModeModel();
		childEnv.addGroup(group1_2);

		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});

		assertFalse(childEnv.tryInstall());
		assertFalse(childEnv.isNested());


		// edit the document
		doc1.replace(7, 3, "INE");

		assertEquals(group1, "MARGARINE");
		assertUnchanged(group1, group2);
	}
	
	@Test
	public void testOutsideUpdate() throws BadLocationException {
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});
		env.addGroup(group1);
		env.forceInstall();

		// edit the document
		doc1.replace(16, 2, "b");

		assertEquals(group1, "MARGARETE");
		assertFalse(isExit[0]);
		Assert.assertEquals("	MARGARETE:\n" +
				"	Verbrich mir, Heinrich!", doc1.get(0, 36));
//		assertUnchanged(group1); // would fail, since it was changed outside
	}
	
	@Test
	public void testOverlappingUpdate() throws BadLocationException {
		// a change partially touches a linked position, but also "in-between" text
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});
		env.addGroup(group1);
		env.forceInstall();

		// edit the document
		doc1.replace(7, 6, "INE-PLANTA");

		assertEquals(group1, "MARGARINE-PLANTA");
		assertFalse(isExit[0]);
		Assert.assertEquals("	MARGARINE-PLANTA" +
				"Versprich mir, Heinrich!", doc1.get(0, 41));
//		assertUnchanged(group1); // would fail, since it was changed outside
	}
	
	@Test
	public void testOverlappingDelete() throws BadLocationException {
		// a change partially touches a linked position, but also "in-between" text
		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");
		LinkedModeModel env= new LinkedModeModel();
		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});
		env.addGroup(group1);
		env.forceInstall();

		// edit the document
		doc1.replace(7, 6, "");

		assertEquals(group1, "MARGAR");
		assertFalse(isExit[0]);
		Assert.assertEquals("	MARGAR" +
				"Versprich mir, Heinrich!", doc1.get(0, 31));
//		assertUnchanged(group1); // would fail, since it was changed outside
	}
	
	@Test
	public void testIllegalChange1() throws BadLocationException {
		// linked mode does not exit if the documents change outside the linked
		// positions, but it does exit if a change invalidates the constraints
		// on the positions (complete disjointness, no touching positions)

		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedModeModel env= new LinkedModeModel();
		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});
		env.addGroup(group1);
		env.forceInstall();

		// edit the document
		doc1.replace(1, 73, "");

		assertTrue(isExit[0]);
	}
	
	@Test
	public void testIllegalChange2() throws BadLocationException {
		// linked mode does not exit if the documents change outside the linked
		// positions, but it does exit if a change invalidates the constraints
		// on the positions (complete disjointness, no touching positions)

		IDocument doc1= new Document(GARTEN1);

		// set up linked mode
		LinkedPositionGroup group1= new LinkedPositionGroup();
		createLinkedPositions(group1, doc1, "MARGARETE");

		LinkedPositionGroup group2= new LinkedPositionGroup();
		createLinkedPositions(group2, doc1, "FAUST");

		LinkedModeModel env= new LinkedModeModel();
		final boolean[] isExit= { false } ;
		env.addLinkingListener(new LinkedAdapter() {
			@Override
			public void left(LinkedModeModel environment, int flags) {
				isExit[0]= true;
			}
		});
		env.addGroup(group1);
		env.addGroup(group2);
		env.forceInstall();

		// edit the document
		doc1.replace(9, 35, "");

		assertTrue(isExit[0]);
	}

	private void assertEquals(LinkedPositionGroup group, String expected) throws BadLocationException {
		LinkedPosition[] positions= group.getPositions();
		for (int i= 0; i < positions.length; i++) {
			LinkedPosition pos= positions[i];
			if (!pos.isDeleted())
				Assert.assertEquals(expected, pos.getContent());
		}
	}

	private void assertUnchanged(LinkedPositionGroup actual1) throws BadLocationException {
		assertUnchanged(actual1, new LinkedPositionGroup());
	}

	private void assertUnchanged(LinkedPositionGroup actual1, LinkedPositionGroup actual2) throws BadLocationException {
		LinkedPosition[] exp= fPositions.toArray(new LinkedPosition[0]);
		LinkedPosition[] act1= actual1.getPositions();
		LinkedPosition[] act2= actual2.getPositions();
		LinkedPosition[] act= new LinkedPosition[act1.length + act2.length];
		System.arraycopy(act1, 0, act, 0, act1.length);
		System.arraycopy(act2, 0, act, act1.length, act2.length);
		Arrays.sort(act, new PositionComparator());
		Arrays.sort(exp, new PositionComparator());

		Assert.assertEquals(exp.length, act.length);

		LinkedPosition e_prev= null, a_prev= null;
		for (int i= 0; i <= exp.length; i++) {
			LinkedPosition e_next= i == exp.length ? null : exp[i];
			LinkedPosition a_next= i == exp.length ? null : act[i];

			IDocument e_doc= e_prev != null ? e_prev.getDocument() : e_next.getDocument();
			if (e_next != null && e_next.getDocument() != e_doc) {
				// split at document boundaries
				Assert.assertEquals(getContentBetweenPositions(e_prev, null), getContentBetweenPositions(a_prev, null));
				Assert.assertEquals(getContentBetweenPositions(null, e_next), getContentBetweenPositions(null, a_next));
			} else {
				Assert.assertEquals(getContentBetweenPositions(e_prev, e_next), getContentBetweenPositions(a_prev, a_next));
			}

			e_prev= e_next;
			a_prev= a_next;
		}
	}

	private String getContentBetweenPositions(LinkedPosition p1, LinkedPosition p2) throws BadLocationException {
		if (p1 == null && p2 == null)
			return null;
		if (p1 == null)
			p1= new LinkedPosition(p2.getDocument(), 0, 0);

		if (p2 == null)
			p2= new LinkedPosition(p1.getDocument(), p1.getDocument().getLength(), 0);

		IDocument document= p1.getDocument();

		int offset= p1.getOffset() + p1.getLength();
		int length= p2.getOffset() - offset;

		return document.get(offset, length);
	}


	@Before
	public void setUp() {
		fPositions.clear();
		fDocumentMap.clear();
	}

	/*
	 * Returns a test group on a copy of the document
	 */
	private void createLinkedPositions(LinkedPositionGroup group, IDocument doc, String substring) throws BadLocationException {
		String text= doc.get();

		IDocument original= getOriginal(doc);
		if (original == null) {
			original= new Document(text);
			putOriginal(doc, original);
		}


		for (int offset= text.indexOf(substring); offset != -1; offset= text.indexOf(substring, offset + 1)) {
			group.addPosition(new LinkedPosition(doc, offset, substring.length(), LinkedPositionGroup.NO_STOP));
			fPositions.add(new LinkedPosition(original, offset, substring.length()));
		}

	}

	private void putOriginal(IDocument doc, IDocument original) {
		fDocumentMap.add(new IDocument[] { doc, original });
	}

	private IDocument getOriginal(IDocument doc) {
		for (Iterator<IDocument[]> it = fDocumentMap.iterator(); it.hasNext(); ) {
			IDocument[] docs = it.next();
			if (docs[0] == doc)
				return docs[1];
		}
		return null;
	}

	private static final String GARTEN1=
		"	MARGARETE:\n" +
		"	Versprich mir, Heinrich!\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Was ich kann!\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Nun sag, wie hast du\'s mit der Religion?\n" +
		"	Du bist ein herzlich guter Mann,\n" +
		"	Allein ich glaub, du haltst nicht viel davon.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Las das, mein Kind! Du fuhlst, ich bin dir gut;\n" +
		"	Fur meine Lieben lies\' ich Leib und Blut,\n" +
		"	Will niemand sein Gefuhl und seine Kirche rauben.\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Das ist nicht recht, man mus dran glauben.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Mus man?\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Ach! wenn ich etwas auf dich konnte! Du ehrst auch nicht die heil\'gen Sakramente.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Ich ehre sie.\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Doch ohne Verlangen. Zur Messe, zur Beichte bist du lange nicht gegangen.\n" +
		"	Glaubst du an Gott?\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Mein Liebchen, wer darf sagen: Ich glaub an Gott?\n" +
		"	Magst Priester oder Weise fragen,\n" +
		"	Und ihre Antwort scheint nur Spott\n" +
		"	uber den Frager zu sein.\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	So glaubst du nicht?\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Mishor mich nicht, du holdes Angesicht!\n" +
		"	Wer darf ihn nennen?\n" +
		"	Und wer bekennen:\n" +
		"	\"Ich glaub ihn!\"?\n" +
		"	Wer empfinden,\n" +
		"	Und sich unterwinden\n" +
		"	Zu sagen: \"Ich glaub ihn nicht!\"?\n" +
		"	Der Allumfasser,\n" +
		"	Der Allerhalter,\n" +
		"	Fast und erhalt er nicht\n" +
		"	Dich, mich, sich selbst?\n" +
		"	Wolbt sich der Himmel nicht da droben?\n" +
		"	Liegt die Erde nicht hier unten fest?\n" +
		"	Und steigen freundlich blickend\n" +
		"	Ewige Sterne nicht herauf?\n" +
		"	Schau ich nicht Aug in Auge dir,\n" +
		"	Und drangt nicht alles\n" +
		"	Nach Haupt und Herzen dir,\n" +
		"	Und webt in ewigem Geheimnis\n" +
		"	Unsichtbar sichtbar neben dir?\n" +
		"	Erfull davon dein Herz, so gros es ist,\n" +
		"	Und wenn du ganz in dem Gefuhle selig bist,\n" +
		"	Nenn es dann, wie du willst,\n" +
		"	Nenn\'s Gluck! Herz! Liebe! Gott\n" +
		"	Ich habe keinen Namen\n" +
		"	Dafur! Gefuhl ist alles;\n" +
		"	Name ist Schall und Rauch,\n" +
		"	Umnebelnd Himmelsglut.\n";

	private static final String GARTEN2=
		"	MARGARETE:\n" +
		"	Das ist alles recht schon und gut;\n" +
		"	Ungefahr sagt das der Pfarrer auch,\n" +
		"	Nur mit ein bischen andern Worten.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Es sagen\'s allerorten\n" +
		"	Alle Herzen unter dem himmlischen Tage,\n" +
		"	Jedes in seiner Sprache;\n" +
		"	Warum nicht ich in der meinen?\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Wenn man\'s so hort, mocht\'s leidlich scheinen,\n" +
		"	Steht aber doch immer schief darum;\n" +
		"	Denn du hast kein Christentum.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Liebs Kind!\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Es tut mir lange schon weh, Das ich dich in der Gesellschaft seh.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Wieso?\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Der Mensch, den du da bei dir hast, Ist mir in tiefer innrer Seele verhast;\n" +
		"	Es hat mir in meinem Leben\n" +
		"	So nichts einen Stich ins Herz gegeben\n" +
		"	Als des Menschen widrig Gesicht.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Liebe Puppe, furcht ihn nicht!\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Seine Gegenwart bewegt mir das Blut.\n" +
		"	Ich bin sonst allen Menschen gut;\n" +
		"	Aber wie ich mich sehne, dich zu schauen,\n" +
		"	Hab ich vor dem Menschen ein heimlich Grauen,\n" +
		"	Und halt ihn fur einen Schelm dazu!\n" +
		"	Gott verzeih mir\'s, wenn ich ihm unrecht tu!\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Es mus auch solche Kauze geben.\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Wollte nicht mit seinesgleichen leben!\n" +
		"	Kommt er einmal zur Tur herein,\n" +
		"	Sieht er immer so spottisch drein\n" +
		"	Und halb ergrimmt;\n" +
		"	Man sieht, das er an nichts keinen Anteil nimmt;\n" +
		"	Es steht ihm an der Stirn geschrieben,\n" +
		"	Das er nicht mag eine Seele lieben.\n" +
		"	Mir wird\'s so wohl in deinem Arm,\n" +
		"	So frei, so hingegeben warm,\n" +
		"	Und seine Gegenwart schnurt mir das Innre zu.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Du ahnungsvoller Engel du!\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Das ubermannt mich so sehr,\n" +
		"	Das, wo er nur mag zu uns treten,\n" +
		"	Mein ich sogar, ich liebte dich nicht mehr.\n" +
		"	Auch, wenn er da ist, konnt ich nimmer beten,\n" +
		"	Und das frist mir ins Herz hinein;\n" +
		"	Dir, Heinrich, mus es auch so sein.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Du hast nun die Antipathie!\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Ich mus nun fort.\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Ach kann ich nie Ein Stundchen ruhig dir am Busen hangen\n" +
		"	Und Brust an Brust und Seel in Seele drangen?\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Ach wenn ich nur alleine schlief!\n" +
		"	Ich lies dir gern heut nacht den Riegel offen;\n" +
		"	Doch meine Mutter schlaft nicht tief,\n" +
		"	Und wurden wir von ihr betroffen,\n" +
		"	Ich war gleich auf der Stelle tot!\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Du Engel, das hat keine Not.\n" +
		"	Hier ist ein Flaschchen!\n" +
		"	Drei Tropfen nur In ihren Trank umhullen\n" +
		"	Mit tiefem Schlaf gefallig die Natur.\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Was tu ich nicht um deinetwillen?\n" +
		"	Es wird ihr hoffentlich nicht schaden!\n" +
		"	 \n" +
		"	FAUST:\n" +
		"	Wurd ich sonst, Liebchen, dir es raten?\n" +
		"	 \n" +
		"	MARGARETE:\n" +
		"	Seh ich dich, bester Mann, nur an,\n" +
		"	Weis nicht, was mich nach deinem Willen treibt,\n" +
		"	Ich habe schon so viel fur dich getan,\n" +
		"	Das mir zu tun fast nichts mehr ubrigbleibt.";

	private class LinkedAdapter implements ILinkedModeListener {
		@Override
		public void left(LinkedModeModel environment, int flags) {}
		@Override
		public void suspend(LinkedModeModel environment) {}
		@Override
		public void resume(LinkedModeModel environment, int flags) {}
	}

	public class PositionComparator implements Comparator<LinkedPosition> {

		@Override
		public int compare(LinkedPosition p1, LinkedPosition p2) {
			IDocument d1= p1.getDocument();
			IDocument d2= p2.getDocument();

			if (d1 == d2)
				// sort by offset inside the same document
				return p1.getOffset() - p2.getOffset();
			return getIndex(d1) - getIndex(d2);
		}

		private int getIndex(IDocument doc) {
			int i= 0;
			for (Iterator<IDocument[]> it= fDocumentMap.iterator(); it.hasNext(); i++) {
				IDocument[] docs= it.next();
				if (docs[0] == doc || docs[1] == doc)
					return i;
			}
			return -1;
		}
	}

}
