/*******************************************************************************
 * Copyright (c) 2004, 2010 Tasktop Technologies and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.tasks.tests;

import java.io.File;
import java.util.Date;

import junit.framework.TestCase;

import org.apache.commons.lang.RandomStringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.commons.sdk.util.CommonTestUtil;
import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager;
import org.eclipse.mylyn.internal.tasks.core.data.TaskDataState;
import org.eclipse.mylyn.internal.tasks.core.data.TaskDataStore;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.core.data.TaskCommentMapper;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.eclipse.mylyn.tasks.core.data.TaskOperation;
import org.eclipse.mylyn.tasks.tests.connector.MockRepositoryConnector;

/**
 * @author Robert Elves
 * @author Steffen Pingel
 */
public class TaskDataStoreTest extends TestCase {

	private static final String MOCK_ID = "1";

	private TaskDataStore storage;

	private TaskRepository taskRepository;

	private File file;

	private TaskData data;

	private TaskDataState state;

	@Override
	protected void setUp() throws Exception {
		TaskRepositoryManager taskRepositoryManager = new TaskRepositoryManager();
		storage = new TaskDataStore(taskRepositoryManager);
		taskRepository = new TaskRepository(MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL);
		file = File.createTempFile("mylyn", null);
		file.deleteOnExit();

		taskRepositoryManager.addRepositoryConnector(new MockRepositoryConnector());
		taskRepositoryManager.addRepository(taskRepository);
	}

	@Override
	protected void tearDown() throws Exception {
		file.delete();
	}

	public void testPutAndGet() throws Exception {
		TaskDataState state = new TaskDataState(MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);

		TaskData data = new TaskData(new TaskAttributeMapper(taskRepository), MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);
		TaskMapper mapper = new TaskMapper(data, true);
		mapper.getTaskData().getRoot().createAttribute("attributeKey1").setValue("attValue!");
		mapper.setDescription("description");
		mapper.setSummary("summary");
		mapper.setTaskKind("task kind");

		TaskData oldData = new TaskData(new TaskAttributeMapper(taskRepository),
				MockRepositoryConnector.CONNECTOR_KIND, MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);

		state.setRepositoryData(data);
		state.setLastReadData(oldData);
		state.setEditsData(null);

		storage.putTaskData(file, state);

		TaskDataState retrieved = storage.getTaskDataState(file);
		assertNotNull(retrieved);
		TaskData newTaskData = retrieved.getRepositoryData();
		assertNotNull(newTaskData);
		assertEquals(MockRepositoryConnector.CONNECTOR_KIND, newTaskData.getConnectorKind());
		mapper = new TaskMapper(newTaskData);
		assertEquals("description", mapper.getDescription());
		assertEquals("summary", mapper.getSummary());
		assertEquals("task kind", mapper.getTaskKind());
	}

	public void testDelete() throws Exception {
		TaskData data = new TaskData(new TaskAttributeMapper(taskRepository), MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);
		TaskDataState state = new TaskDataState(MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);
		state.setRepositoryData(data);
		storage.putTaskData(file, state);
		storage.deleteTaskData(file);
		assertFalse(file.exists());
		assertNull(storage.getTaskDataState(file));
	}

	private void setupData() {
		data = new TaskData(new TaskAttributeMapper(taskRepository), MockRepositoryConnector.CONNECTOR_KIND,
				MockRepositoryConnector.REPOSITORY_URL, MOCK_ID);
		state = new TaskDataState(MockRepositoryConnector.CONNECTOR_KIND, MockRepositoryConnector.REPOSITORY_URL,
				MOCK_ID);
		state.setRepositoryData(data);
	}

	public void testAttributes() throws Exception {
		setupData();

		TaskAttribute attribute = data.getRoot().createAttribute("testId");
		attribute.getMetaData().setLabel("testName").setReadOnly(true);
		attribute.putOption("Option Name 1", "Option Value 1");
		attribute.putOption("Option Name 2", "Option Value 2");
		attribute.addValue("Value 1");
		attribute.addValue("Value 2");
		attribute.addValue("Value 3");
		attribute.getMetaData().putValue("MetaKey1", "MetaValue1");
		attribute.getMetaData().putValue("MetaKey2", "MetaValue2");
		attribute.getMetaData().putValue("MetaKey3", "MetaValue3");
		attribute.getMetaData().putValue("MetaKey4", "MetaValue4");

		assertData(1);
	}

	private void assertData(int attributeCount) throws CoreException {
		storage.putTaskData(file, state);
		state = storage.getTaskDataState(file);

		assertNotNull(state);
		assertNotNull(state.getRepositoryData());
		assertEquals(data.getRoot().toString(), state.getRepositoryData().getRoot().toString());
		assertEquals(attributeCount, state.getRepositoryData().getRoot().getAttributes().size());
	}

	public void testOperations() throws Exception {
		setupData();

		TaskOperation.applyTo(data.getRoot().createAttribute("op1"), "op1 id", "op1 label");
		TaskOperation.applyTo(data.getRoot().createAttribute("op2"), "op2 id", "op2 label");

		assertData(2);
	}

	public void testComments() throws Exception {
		setupData();

		TaskCommentMapper comment1 = new TaskCommentMapper();
		comment1.setCommentId("attachmentId1");
		TaskAttribute attr = data.getRoot().createAttribute("1");
		comment1.applyTo(attr);
		attr.createAttribute("attr1").setValue("attr1Name");

		TaskCommentMapper comment2 = new TaskCommentMapper();
		comment2.setCommentId("attachmentId2");
		comment2.setCreationDate(new Date());
		comment2.setNumber(100);
		comment2.applyTo(data.getRoot().createAttribute("2"));

		assertData(2);
	}

	public void testAttachments() throws Exception {
		setupData();

		TaskAttachmentMapper attachment1 = new TaskAttachmentMapper();
		attachment1.setAuthor(taskRepository.createPerson("thecreator"));
		attachment1.setDeprecated(false);
		attachment1.setPatch(true);
		TaskAttribute attr = data.getRoot().createAttribute("1");
		attachment1.applyTo(attr);
		attr.createAttribute("attr1").setValue("attr1Name");
		attr.createAttribute("attr2").setValue("attr1Name");

		TaskAttachmentMapper attachment2 = new TaskAttachmentMapper();
		attachment2.setAuthor(taskRepository.createPerson("thecreator2"));
		attachment2.setDeprecated(true);
		attachment2.setPatch(false);
		attachment2.applyTo(data.getRoot().createAttribute("2"));

		assertData(2);
	}

	public void testReadWriteInvalidCharacters() throws Exception {
		setupData();
		data.getRoot().createAttribute("attribute").setValue("\u0000");

		storage.putTaskData(file, state);

		try {
			TaskDataState state2 = storage.getTaskDataState(file);
			fail("Expected CoreException, got " + state2);
		} catch (CoreException expected) {
		}
	}

	public void testReadWriteC0Characters() throws Exception {
		setupData();
		data.getRoot().createAttribute("attribute").setValue("\u0001\u001F");

		storage.putTaskData(file, state);

		TaskDataState state2 = storage.getTaskDataState(file);
		assertEquals(state.getRepositoryData().getRoot().toString(), state2.getRepositoryData().getRoot().toString());
	}

	public void testReadWriteC1Characters() throws Exception {
		setupData();
		data.getRoot().createAttribute("attribute").setValue("\u007F\u0080");

		storage.putTaskData(file, state);

		TaskDataState state2 = storage.getTaskDataState(file);
		assertEquals(state.getRepositoryData().getRoot().toString(), state2.getRepositoryData().getRoot().toString());
	}

	public void testReadWriteC0C1Characters() throws Exception {
		setupData();
		data.getRoot().createAttribute("attribute").setValue("\u0001\u001F\u007F\u0080");

		storage.putTaskData(file, state);

		if (System.getProperty("java.version").compareTo("1.6") < 0) {
			// Java 1.5 fails to parse C1 characters with XML 1.1
			try {
				TaskDataState state2 = storage.getTaskDataState(file);
				fail("Expected CoreException, got '" + state2.getRepositoryData().getRoot().toString() + "'");
			} catch (CoreException expected) {
			}
		} else {
			// Java 1.6 is apparently able to parse C1 characters with XML 1.1
			TaskDataState state2 = storage.getTaskDataState(file);
			assertEquals(state.getRepositoryData().getRoot().toString(), state2.getRepositoryData()
					.getRoot()
					.toString());
		}
	}

	public void testReadWriteSetValue() throws Exception {
		setupData();
		TaskAttribute attribute = data.getRoot().createAttribute("attribute");
		assertFalse(attribute.hasValue());
		attribute.setValue("foo");
		assertTrue(attribute.hasValue());

		File file = File.createTempFile("mylyn", null);
		file.deleteOnExit();
		storage.putTaskData(file, state);

		TaskDataState state2 = storage.getTaskDataState(file);
		assertTrue(state2.getRepositoryData().getRoot().getAttribute("attribute").hasValue());
	}

	public void testReadWriteUnsetValue() throws Exception {
		setupData();
		TaskAttribute attribute = data.getRoot().createAttribute("attribute");
		assertFalse(attribute.hasValue());
		attribute.setValue("foo");
		assertTrue(attribute.hasValue());
		attribute.clearValues();
		assertFalse(attribute.hasValue());

		File file = File.createTempFile("mylyn", null);
		file.deleteOnExit();
		storage.putTaskData(file, state);

		TaskDataState state2 = storage.getTaskDataState(file);
		assertFalse(state2.getRepositoryData().getRoot().getAttribute("attribute").hasValue());
	}

	public void testCorruptedData() throws Exception {
		if (!hasXerces()) {
			System.err.println("Skipping testCorruptedData() due to Xerces missing");
			return;
		}
		state = storage.getTaskDataState(CommonTestUtil.getFile(this, "testdata/taskdata-bug406647.zip"));
		assertFalse(state.getRepositoryData().getRoot().toString().contains("<ke"));
		assertFalse(state.getRepositoryData().getRoot().toString().contains("<va"));
		assertFalse(state.getRepositoryData().getRoot().toString().contains("ey>"));
		assertFalse(state.getRepositoryData().getRoot().toString().contains("al>"));
	}

	private boolean hasXerces() {
		try {
			Class.forName("org.apache.xerces.parsers.SAXParser");
			return true;
		} catch (ClassNotFoundException e) {
			return false;
		}
	}

	public void testRandomDataXml_1_0() throws Exception {
		randomData(32, Integer.MAX_VALUE);
	}

	public void testRandomDataXml_1_1() throws Exception {
		if (!hasXerces()) {
			System.err.println("Skipping testRandomDataXml_1_1 due to Xerces missing");
			return;
		}
		randomData(0, Integer.MAX_VALUE);
	}

	private void randomData(int start, int end) throws Exception {
		setupData();

		for (int i = 0; i < 1000; i++) {
			TaskAttribute attribute = data.getRoot().createAttribute("testId");
			attribute.getMetaData().setLabel(generateString(start, end));
			attribute.putOption(generateString(start, end), generateString(start, end));
			attribute.putOption(generateString(start, end), generateString(start, end));
			attribute.addValue(generateString(start, end));
			attribute.addValue(generateString(start, end));
			if (start == 0) {
				// ensure that XML is read as version 1.1
				attribute.addValue(generateString(start, end) + "\u0001");
			}
		}

		String expectedValue = data.getRoot().toString();
		storage.putTaskData(file, state);

		state = storage.getTaskDataState(file);
		String actualValue = state.getRepositoryData().getRoot().toString();
		assertEquals(expectedValue, actualValue);
	}

	/**
	 * Returns a random string that doesn't contain "key" or "val".
	 */
	private String generateString(int start, int end) {
		return RandomStringUtils.random(1000, start, end, true, true);
	}

}
