/*******************************************************************************
 * Copyright (c) 2000, 2013 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;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.TextUtilities;


/**
 * A test case for text utilities.
 */
public class TextUtilitiesTest {

	/**
	 * A document which is a copy of another document.
	 * Implementation uses old document state.
	 */
	private static class LazilyMirroredDocument extends Document {

		private final class DocumentListener implements IDocumentListener {
			@Override
			public void documentAboutToBeChanged(DocumentEvent event) {}
			@Override
			public void documentChanged(DocumentEvent event) {
				fEvents.add(event);
			}
		}

		/** The document listener. */
		private final DocumentListener fDocumentListener= new DocumentListener();

		/** The buffered events. */
		private final List<DocumentEvent> fEvents= new ArrayList<>();

		public LazilyMirroredDocument(IDocument document) {
			document.addDocumentListener(fDocumentListener);
		}

		private void flush() throws BadLocationException {
			DocumentEvent event= TextUtilities.mergeUnprocessedDocumentEvents(this, fEvents);
			if (event == null)
				return;

			replace(event.getOffset(), event.getLength(), event.getText());
			fEvents.clear();
		}

		/*
		 * Should override all other getXXX() methods as well, but it's sufficient for the test.
		 *
		 * @see org.eclipse.jface.text.IDocument#get()
		 */
		@Override
		public String get() {
			try {
				flush();
			} catch (BadLocationException e) {
				assertFalse(true);
			}
			return super.get();
		}
	}

	/**
	 * A document which is a copy of another document.
	 * Implementation uses new document state.
	 */
	private static class LazilyMirroredDocument2 extends Document {

		private final class DocumentListener implements IDocumentListener {
			@Override
			public void documentAboutToBeChanged(DocumentEvent event) {}
			@Override
			public void documentChanged(DocumentEvent event) {
				event= new DocumentEvent(event.getDocument(), event.getOffset(), event.getLength(), event.getText());
				fEvents.add(event);
			}
		}

		/** The document listener. */
		private final DocumentListener fDocumentListener= new DocumentListener();

		/** The buffered events. */
		private final List<DocumentEvent> fEvents= new ArrayList<>();

		public LazilyMirroredDocument2(IDocument document) {
			document.addDocumentListener(fDocumentListener);
		}

		private void flush() throws BadLocationException {
			DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fEvents);
			if (event == null)
				return;

			replace(event.getOffset(), event.getLength(), event.getText());
			fEvents.clear();
		}

		/*
		 * Should override all other getXXX() methods as well, but it's sufficient for the test.
		 *
		 * @see org.eclipse.jface.text.IDocument#get()
		 */
		@Override
		public String get() {
			try {
				flush();
			} catch (BadLocationException e) {
				Assert.fail("bad implementation");
			}
			return super.get();
		}
	}


	/**
	 * Constructor for UtilitiesTest.
	 * 
	 * @param name the name
	 */
	private static DocumentEvent createRandomEvent(IDocument document, int maxLength, char character) {

		int index0= (int) (Math.random() * (maxLength + 1));
		int index1= (int) (Math.random() * (maxLength + 1));

		int offset= Math.min(index0, index1);
		int length= Math.max(index0, index1) - offset;

		int stringLength=  (int) (Math.random() * 10);
		StringBuffer buffer= new StringBuffer(stringLength);
		for (int i= 0; i < stringLength; ++i)
			buffer.append(character);

		return new DocumentEvent(document, offset, length, buffer.toString());
	}
	
	@Test
	public void testMergeEvents1() {
		IDocument reference= new Document();
		LazilyMirroredDocument testee= new LazilyMirroredDocument(reference);

		try {
			reference.replace(0, 0, "01234567890123");
			check(reference, testee);

			reference.replace(4, 3, "moo ");
			reference.replace(9, 2, "asd");
			check(reference, testee);

		} catch (BadLocationException e) {
			Assert.fail("bad location exception");
		}
	}
	
	@Test
	public void testMergeEvents() {
		IDocument reference= new Document();
		LazilyMirroredDocument testee= new LazilyMirroredDocument(reference);

		try {

			List<DocumentEvent> events= new ArrayList<>();
			int currentLength= 0;

			events.add(new DocumentEvent(reference, 0, 0, "foo bar goo haa"));
			events.add(new DocumentEvent(reference, 0, "foo bar goo haa".length(), "foo bar goo haa"));
			events.add(new DocumentEvent(reference, 4, 4, "xxxx"));
			events.add(new DocumentEvent(reference, 4, 4, "yyy"));
			events.add(new DocumentEvent(reference, 4, 3, "moo "));
			events.add(new DocumentEvent(reference, 9, 2, "asd"));
			events.add(new DocumentEvent(reference, 0, 2, "asd"));

			for (Iterator<DocumentEvent> iterator= events.iterator(); iterator.hasNext();) {
				DocumentEvent event= iterator.next();
				currentLength += event.getText().length() - event.getLength();
			}

			for (int i= 0; i < 500; i++) {
				char character= (char) (32 + i % 95);
				DocumentEvent event= createRandomEvent(reference, currentLength, character);
				currentLength += event.getText().length() - event.getLength();
				events.add(event);
			}

			for (Iterator<DocumentEvent> iterator= events.iterator(); iterator.hasNext();) {
				DocumentEvent event= iterator.next();

//				System.err.println(event.getOffset() + ", " + event.getLength() + ", [" + event.getText() + "]") ;

				reference.replace(event.getOffset(), event.getLength(), event.getText());
				if (Math.random() < 0.3) {
//					System.err.println("check");
					check(reference, testee);
//					System.err.println("length= " + reference.getLength());
				}
			}

			check(reference, testee);

//			System.out.println("[" + reference.get() + "]");
//			System.out.println("[" + testee.get() + "]");

		} catch (BadLocationException e) {
			Assert.fail("bad location exception");
		}
	}
	
	@Test
	public void testMergeEvents2() {
		IDocument reference= new Document();
		LazilyMirroredDocument2 testee= new LazilyMirroredDocument2(reference);

		try {

			List<DocumentEvent> events= new ArrayList<>();
			int currentLength= 0;

			events.add(new DocumentEvent(reference, 0, 0, "foo bar goo haa"));
			events.add(new DocumentEvent(reference, 0, "foo bar goo haa".length(), "foo bar goo haa"));
			events.add(new DocumentEvent(reference, 4, 4, "xxxx"));
			events.add(new DocumentEvent(reference, 4, 4, "yyy"));
			events.add(new DocumentEvent(reference, 4, 3, "moo "));
			events.add(new DocumentEvent(reference, 9, 2, "asd"));
			events.add(new DocumentEvent(reference, 0, 2, "asd"));

			for (Iterator<DocumentEvent> iterator= events.iterator(); iterator.hasNext();) {
				DocumentEvent event= iterator.next();
				currentLength += event.getText().length() - event.getLength();
			}

			for (int i= 0; i < 500; i++) {
				char character= (char) (32 + i % 95);
				DocumentEvent event= createRandomEvent(reference, currentLength, character);
				currentLength += event.getText().length() - event.getLength();
				events.add(event);
			}

			for (Iterator<DocumentEvent> iterator= events.iterator(); iterator.hasNext();) {
				DocumentEvent event= iterator.next();

				reference.replace(event.getOffset(), event.getLength(), event.getText());
				if (Math.random() < 0.3) {
					check(reference, testee);
				}
			}

			check(reference, testee);

		} catch (BadLocationException e) {
			Assert.fail("bad location exception");
		}
	}

	private static void check(IDocument reference, IDocument testee) {
		Assert.assertEquals(reference.get(), testee.get());
	}
	
	@Test
	public void testIndexOf() {
		int[] result;
		result= TextUtilities.indexOf(new String[] {"a", "ab", "abc"}, "xxxxxxxxxx", 0);
		assertEquals(-1, result[0]);
		assertEquals(-1, result[1]);

		result= TextUtilities.indexOf(new String[] {"a", "ab", "abc"}, "foobarabcd", 0);
		assertEquals(4, result[0]);
		assertEquals(0, result[1]);
	}

}
