/*******************************************************************************
 * Copyright (c) 2007, 2010 Wind River Systems, Inc. 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:
 *     Anton Leherbauer (Wind River Systems) - initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.ui.tests.text;

import junit.framework.TestSuite;

import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.structuremergeviewer.DocumentRangeNode;
import org.eclipse.compare.structuremergeviewer.IStructureComparator;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;

import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.testplugin.CProjectHelper;
import org.eclipse.cdt.ui.testplugin.EditorTestHelper;
import org.eclipse.cdt.ui.testplugin.ResourceTestHelper;
import org.eclipse.cdt.ui.tests.BaseUITestCase;

import org.eclipse.cdt.internal.ui.compare.CStructureCreator;

/**
 * Tests for the CStructureCreator.
 *
 * @since 5.0
 */
public class CStructureCreatorTest extends BaseUITestCase {

	public static TestSuite suite() {
		return suite(CStructureCreatorTest.class, "_");
	}

	private ICProject fCProject;

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		fCProject= EditorTestHelper.createCProject("CStructureCreatorTest", "resources/compare", false);
	}

	@Override
	protected void tearDown() throws Exception {
		if (fCProject != null) {
			CProjectHelper.delete(fCProject);
		}
		super.tearDown();
	}

	public void testStructureCreatorNodeTypes() throws Exception {
		IFile file= ResourceTestHelper.findFile("/CStructureCreatorTest/src/CompareTest.cpp");
		assertNotNull(file);
		CStructureCreator creator= new CStructureCreator();
		IStructureComparator node= creator.getStructure(new ResourceNode(file));
		assertNotNull(node);
		Object[] children= node.getChildren();
		// one node below root == translation unit
		assertEquals(1, children.length);
		DocumentRangeNode tuNode= (DocumentRangeNode)children[0];

		// tu children
		children= tuNode.getChildren();
		assertEquals(10, children.length);
		DocumentRangeNode child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_INCLUDE, child.getTypeCode());
		child= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_MACRO, child.getTypeCode());
		DocumentRangeNode namespace= (DocumentRangeNode)children[2];
		assertEquals(ICElement.C_NAMESPACE, namespace.getTypeCode());
		child= (DocumentRangeNode)children[3];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[4];
		assertEquals(ICElement.C_FUNCTION, child.getTypeCode());
		DocumentRangeNode struct= (DocumentRangeNode)children[5];
		assertEquals(ICElement.C_STRUCT, struct.getTypeCode());
		child= (DocumentRangeNode)children[6];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[7];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[8];
		assertEquals(ICElement.C_USING, child.getTypeCode());
		child= (DocumentRangeNode)children[9];
		assertEquals(ICElement.C_FUNCTION, child.getTypeCode());

		// namespace children
		children= namespace.getChildren();
		assertEquals(22, children.length);
		DocumentRangeNode clazz= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_CLASS, clazz.getTypeCode());
		DocumentRangeNode enum1= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_ENUMERATION, enum1.getTypeCode());
		child= (DocumentRangeNode)children[2];
		assertEquals(ICElement.C_ENUMERATION, child.getTypeCode());
		child= (DocumentRangeNode)children[3];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[4];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[5];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[6];
		assertEquals(ICElement.C_VARIABLE_DECLARATION, child.getTypeCode());
		child= (DocumentRangeNode)children[7];
		assertEquals(ICElement.C_VARIABLE, child.getTypeCode());
		child= (DocumentRangeNode)children[8];
		assertEquals(ICElement.C_FUNCTION_DECLARATION, child.getTypeCode());
		child= (DocumentRangeNode)children[9];
		assertEquals(ICElement.C_FUNCTION_DECLARATION, child.getTypeCode());
		child= (DocumentRangeNode)children[10];
		assertEquals(ICElement.C_FUNCTION, child.getTypeCode());
		child= (DocumentRangeNode)children[11];
		assertEquals(ICElement.C_STRUCT, child.getTypeCode());
		child= (DocumentRangeNode)children[12];
		assertEquals(ICElement.C_TYPEDEF, child.getTypeCode());
		child= (DocumentRangeNode)children[13];
		assertEquals(ICElement.C_STRUCT, child.getTypeCode());
		child= (DocumentRangeNode)children[14];
		assertEquals(ICElement.C_TYPEDEF, child.getTypeCode());
		DocumentRangeNode union= (DocumentRangeNode)children[15];
		assertEquals(ICElement.C_UNION, union.getTypeCode());
		child= (DocumentRangeNode)children[16];
		assertEquals(ICElement.C_TEMPLATE_FUNCTION_DECLARATION, child.getTypeCode());
		child= (DocumentRangeNode)children[17];
		assertEquals(ICElement.C_TEMPLATE_FUNCTION, child.getTypeCode());
		DocumentRangeNode clazz2= (DocumentRangeNode)children[18];
		assertEquals(ICElement.C_CLASS, clazz2.getTypeCode());
		child= (DocumentRangeNode)children[19];
		assertEquals(ICElement.C_TEMPLATE_CLASS, child.getTypeCode());
		child= (DocumentRangeNode)children[20];
		assertEquals(ICElement.C_TEMPLATE_STRUCT, child.getTypeCode());
		child= (DocumentRangeNode)children[21];
		assertEquals(ICElement.C_TEMPLATE_VARIABLE, child.getTypeCode());

		// class children
		children= clazz.getChildren();
		assertEquals(3, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_FIELD, child.getTypeCode());
		child= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_METHOD, child.getTypeCode());
		namespace= (DocumentRangeNode)children[2];
		assertEquals(ICElement.C_NAMESPACE, namespace.getTypeCode());

		// enum children
		children= enum1.getChildren();
		assertEquals(3, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_ENUMERATOR, child.getTypeCode());
		child= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_ENUMERATOR, child.getTypeCode());
		child= (DocumentRangeNode)children[2];
		assertEquals(ICElement.C_ENUMERATOR, child.getTypeCode());

		// union children
		children= union.getChildren();
		assertEquals(1, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_FIELD, child.getTypeCode());

		// enclosing class children
		children= clazz2.getChildren();
		assertEquals(1, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_TEMPLATE_METHOD_DECLARATION, child.getTypeCode());

		// nested namespace children
		children= namespace.getChildren();
		assertEquals(2, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_CLASS, child.getTypeCode());
		clazz= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_CLASS, child.getTypeCode());
		// nested class children
		children= clazz.getChildren();
		assertEquals(3, children.length);
		child= (DocumentRangeNode)children[0];
		assertEquals(ICElement.C_FIELD, child.getTypeCode());
		child= (DocumentRangeNode)children[1];
		assertEquals(ICElement.C_METHOD, child.getTypeCode());
		child= (DocumentRangeNode)children[2];
		assertEquals(ICElement.C_METHOD_DECLARATION, child.getTypeCode());
	}

	public void testStructureCreatorNodeSizes() throws Exception {
		IFile file= ResourceTestHelper.findFile("/CStructureCreatorTest/src/CompareTest.cpp");
		assertNotNull(file);
		CStructureCreator creator= new CStructureCreator();
		IStructureComparator node= creator.getStructure(new ResourceNode(file));
		assertNotNull(node);
		Object[] children= node.getChildren();
		// one node below root == translation unit
		assertEquals(1, children.length);
		DocumentRangeNode tuNode= (DocumentRangeNode)children[0];
		IDocument document= tuNode.getDocument();
		Position range= tuNode.getRange();
		assertEqualPositions(new Position(0, document.getLength()), range);
		
		verifyStructure(tuNode, CoreModel.getDefault().create(file));
	}
	
	private void assertEqualPositions(Position expected, Position actual) {
		assertEquals(expected.getOffset(), actual.getOffset());
		assertEquals(expected.getLength(), actual.getLength());
	}

	private void verifyStructure(DocumentRangeNode cNode, ICElement cElement) throws CModelException, BadLocationException {
		// verify same type
		assertEquals(cElement.getElementType(), cNode.getTypeCode());
		
		if (cNode.getTypeCode() != ICElement.C_UNIT && cNode instanceof ITypedElement) {
			Position nodeRange= cNode.getRange();
			IDocument doc= cNode.getDocument();
			ISourceReference sourceRef= (ISourceReference)cElement;
			ISourceRange sourceRange= sourceRef.getSourceRange();
			// verify start and endline of elements match
			assertEquals(sourceRange.getStartLine(), doc.getLineOfOffset(nodeRange.getOffset()) + 1);
			assertEquals(sourceRange.getEndLine(), doc.getLineOfOffset(nodeRange.getOffset() + nodeRange.getLength() - 1) + 1);
			
			assertEqualPositions(new Position(sourceRange.getStartPos(), sourceRange.getLength()), nodeRange);
			
			if (cElement.getElementName().length() > 0) {
				assertEquals(cElement.getElementName(), ((ITypedElement)cNode).getName());
			}
		}
		Object[] nodeChildren= cNode.getChildren();
		// merge in extern "C" children to match cmodel hierarchy
		for (int i = 0; i < nodeChildren.length; i++) {
			DocumentRangeNode childNode= (DocumentRangeNode)nodeChildren[i];
//			if (childNode.getTypeCode() == ICElement.C_STORAGE_EXTERN) {
//				Object[] linkageSpecChildren= childNode.getChildren();
//				Object[] newArray= new Object[nodeChildren.length - 1 + linkageSpecChildren.length];
//				System.arraycopy(nodeChildren, 0, newArray, 0, i);
//				System.arraycopy(linkageSpecChildren, 0, newArray, i, linkageSpecChildren.length);
//				System.arraycopy(nodeChildren, i+1, newArray, i + linkageSpecChildren.length, nodeChildren.length - i - 1);
//				nodeChildren= newArray;
//			}
		}
		ICElement[] cElementChildren;
		if (cElement instanceof IParent) {
			cElementChildren= ((IParent)cElement).getChildren();
		} else {
			cElementChildren= new ICElement[0];
		}
		// verify same number of children
		assertEquals(cElementChildren.length, nodeChildren.length);
		for (int i = 0; i < cElementChildren.length; i++) {
			verifyStructure((DocumentRangeNode)nodeChildren[i], cElementChildren[i]);
		}
	}
}
