/*******************************************************************************
 * Copyright (c) 2013, 2014 Google, 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:
 * 	   Sergey Prigogin (Google) - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.ui.tests.refactoring.includes;

import java.util.Collections;

import junit.framework.Test;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MultiTextEdit;

import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.PreferenceConstants;

import org.eclipse.cdt.internal.ui.refactoring.includes.HeaderSubstitutionMap;
import org.eclipse.cdt.internal.ui.refactoring.includes.IHeaderChooser;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeMap;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeOrganizer;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludePreferences.UnusedStatementDisposition;
import org.eclipse.cdt.internal.ui.refactoring.includes.SymbolExportMap;

/**
 * Tests for {@link IncludeOrganizer}.
 */
public class IncludeOrganizerTest extends IncludesTestBase {

	public IncludeOrganizerTest() {
		super();
	}

	public IncludeOrganizerTest(String name) {
		super(name);
	}

	public static Test suite() {
		return suite(IncludeOrganizerTest.class);
	}

	@Override
	protected void resetPreferences() {
		super.resetPreferences();
		IPreferenceStore preferenceStore = getPreferenceStore();
		preferenceStore.setToDefault(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION);
		preferenceStore.setToDefault(PreferenceConstants.FORWARD_DECLARE_COMPOSITE_TYPES);
		preferenceStore.setToDefault(PreferenceConstants.FORWARD_DECLARE_ENUMS);
		preferenceStore.setToDefault(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS);
		preferenceStore.setToDefault(PreferenceConstants.FORWARD_DECLARE_TEMPLATES);
		preferenceStore.setToDefault(PreferenceConstants.FORWARD_DECLARE_NAMESPACE_ELEMENTS);
		preferenceStore.setToDefault(PreferenceConstants.INCLUDES_ALLOW_REORDERING);
		preferenceStore.setToDefault(PreferenceConstants.INCLUDES_HEADER_SUBSTITUTION);
		preferenceStore.setToDefault(PreferenceConstants.INCLUDES_SYMBOL_EXPORTING_HEADERS);
	}

	private void assertExpectedResults() throws Exception {
		String actual = organizeIncludes(ast.getOriginatingTranslationUnit());
		assertEquals(selectedFile.getExpectedSource(), actual);
	}

	/**
	 * Invokes include organizer and returns the new contents of the translation unit.
	 */
	private String organizeIncludes(ITranslationUnit tu) throws Exception {
		IHeaderChooser headerChooser = new FirstHeaderChooser();
		IncludeOrganizer organizer = new IncludeOrganizer(tu, index, LINE_DELIMITER, headerChooser);
		MultiTextEdit edit = organizer.organizeIncludes(ast);
		IDocument document = new Document(new String(tu.getContents()));
		edit.apply(document);
		return document.get();
	}

	//h1.h
	//typedef int my_type;

	//A.h
	//class A {
	//  my_type m1();
	//};

	//A.cpp
	//// Comment line 1
	//// Comment line 2
	//
	//// Comment for m1
	//my_type A::m1() {
	//  return 0;
	//}
	//====================
	//// Comment line 1
	//// Comment line 2
	//
	//#include "A.h"
	//
	//#include "h1.h"
	//
	//// Comment for m1
	//my_type A::m1() {
	//  return 0;
	//}
	public void testNoExistingIncludes() throws Exception {
		assertExpectedResults();
	}

	//B.h
	//class B {};

	//C.h
	//class C {};

	//A.h
	//#if !defined(INCLUDE_GUARD)
	//#define INCLUDE_GUARD
	//// Comment line 1
	//// Comment line 2
	//
	//// Comment for A
	//class A {
	//  B f;
	//  C m();
	//};
	//#endif  // INCLUDE_GUARD
	//====================
	//#if !defined(INCLUDE_GUARD)
	//#define INCLUDE_GUARD
	//// Comment line 1
	//// Comment line 2
	//
	//#include "B.h"
	//
	//class C;
	//
	//// Comment for A
	//class A {
	//  B f;
	//  C m();
	//};
	//#endif  // INCLUDE_GUARD
	public void testIncludeGuards() throws Exception {
		assertExpectedResults();
	}

	//B.h
	//template <typename T> class B {};

	//C.h
	//class C {};

	//A.h
	//#pragma once
	//namespace ns {
	//// Comment line 1
	//// Comment line 2
	//
	//// Comment for A
	//class A : public B<C> {};
	//}  // namespace ns
	//====================
	//#pragma once
	//
	//#include "B.h"
	//#include "C.h"
	//
	//namespace ns {
	//// Comment line 1
	//// Comment line 2
	//
	//// Comment for A
	//class A : public B<C> {};
	//}  // namespace ns
	public void testPragmaOnce() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//typedef int Type1;

	//h2.h
	//class Type2 {};

	//h3.h
	//enum Type3 { ONE, TWO };

	//h4.h
	//class Unrelated {};

	//A.h
	//#include "h1.h"
	//class Type2;
	//enum class Type3;
	//extern Type1 f1();
	//extern Type2 f2();
	//extern Type3 f3();

	//A.cpp
	//// Comment
	//
	//#include "h2.h" /* Required */  // another comment
	//#include "h1.h"  // Unused
	//#include "h3.h"
	//#include "h5.h"  // Unresolved includes are preserved
	//#ifdef SOME_OTHER_TIME
	//#include "h4.h"  // Unused but unsafe to remove
	//#endif
	//
	//void test() {
	//  f1();
	//  f2();
	//  f3();
	//}
	//====================
	//// Comment
	//
	//#include "A.h"
	//
	//#include "h2.h" /* Required */  // another comment
	//#include "h3.h"
	//#include "h5.h"  // Unresolved includes are preserved
	//
	//#ifdef SOME_OTHER_TIME
	//#include "h4.h"  // Unused but unsafe to remove
	//#endif
	//
	//void test() {
	//  f1();
	//  f2();
	//  f3();
	//}
	public void testExistingIncludes() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//typedef int Type1;

	//h2.h
	//class Type2 {};

	//h3.h
	//enum class Type3 { ONE, TWO };

	//h4.h
	//class Unrelated {};

	//A.h
	//#include "h1.h"
	//class Type2;
	//enum class Type3;
	//extern Type1 f1();
	//extern Type2 f2();
	//extern Type3 f3();

	//A.cpp
	//// Comment
	//
	//#include "h2.h" /* Required */  // another comment
	//#include "h1.h"  // Unused
	//#include "h3.h"
	//#include "h5.h"  // Unresolved includes are preserved
	//#ifdef SOME_OTHER_TIME
	//#include "h4.h"  // Unused but unsafe to remove
	//#endif
	//
	//void test() {
	//  f1();
	//  f2();
	//  f3();
	//}
	//====================
	//// Comment
	//
	//#include "h2.h" /* Required */  // another comment
	//#include "h3.h"
	//#include "h5.h"  // Unresolved includes are preserved
	//#include "A.h"
	//
	//#ifdef SOME_OTHER_TIME
	//#include "h4.h"  // Unused but unsafe to remove
	//#endif
	//
	//void test() {
	//  f1();
	//  f2();
	//  f3();
	//}
	public void testExistingIncludesNoReordering() throws Exception {
		getPreferenceStore().setValue(PreferenceConstants.INCLUDES_ALLOW_REORDERING, false);
		assertExpectedResults();
	}

	//f.h
	//void f(int p);

	//f.cpp
	//#include "f.h"
	//void f(int p) {
	//}
	//====================
	//#include "f.h"
	//
	//void f(int p) {
	//}
	public void testExistingPartnerIncludeIsNotRemoved() throws Exception {
		IPreferenceStore preferenceStore = getPreferenceStore();
		preferenceStore.setValue(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION,
				UnusedStatementDisposition.REMOVE.toString());
		preferenceStore.setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true);
		assertExpectedResults();
	}

	//h1.h
	//class A {};

	//h2.h
	//class B {};

	//h3.h
	//#include "h2.h"	// IWYU pragma: export
	//class C {};

	//h4.h
	//#include "h1.h"
	///* IWYU pragma: begin_exports */
	//#include "h3.h"
	///* IWYU pragma: end_exports */
	//class D {};

	//source.cpp
	//A a;
	//B b;
	//C c;
	//D d;
	//====================
	//#include "h1.h"
	//#include "h4.h"
	//
	//A a;
	//B b;
	//C c;
	//D d;
	public void testHeaderExport() throws Exception {
		assertExpectedResults();
	}

	//dir1/private1.h
	///** @file dir1/private1.h
	// *  This is an internal header file, included by other library headers.
	// *  Do not attempt to use it directly. @headername{dir1/public1.h}
	// */
	//class A {};

	//dir1/public1.h
	//#include "private1.h"

	//dir1/private2.h
	//// IWYU pragma: private,
	//// include "dir1/public2.h"
	//class B {};

	//dir1/public2.h
	//#include "private2.h"

	//dir1/private3.h
	//// IWYU pragma: private
	//class C {};

	//dir1/public3.h
	//#include "private3.h"

	//dir2/private4.h
	//// IWYU pragma: private, include "dir2/public4.h"
	//class D {};

	//dir2/public4.h
	//#include "private4.h"

	//dir2/source.cpp
	//A a;
	//B b;
	//C c;
	//D d;
	//====================
	//#include "dir1/public1.h"
	//#include "dir1/public2.h"
	//#include "dir1/public3.h"
	//#include "dir2/private4.h"
	//
	//A a;
	//B b;
	//C c;
	//D d;
	public void testPrivateHeaders() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//class A {};

	//h2.h
	//#include "h1.h"	// IWYU pragma: export
	//class B {};

	//h3.h
	//#include "h2.h"

	//source.cpp
	//A a;
	//B b;
	//====================
	//#include "h3.h"
	//
	//A a;
	//B b;
	public void testIndirectHeaderExport() throws Exception {
		HeaderSubstitutionMap headerMap = new HeaderSubstitutionMap("Test", false,
				new IncludeMap(true, new String[] { "h2.h", "h3.h"}),
				new IncludeMap(false));
		getPreferenceStore().setValue(PreferenceConstants.INCLUDES_HEADER_SUBSTITUTION,
				HeaderSubstitutionMap.serializeMaps(Collections.singletonList(headerMap)));
		assertExpectedResults();
	}

	//h1.h
	//#define M2(t, p) t p

	//h2.h
	//#include "h1.h"
	//#define M1(x, y) M2(int, x) = y

	//h3.h
	//#include "h2.h"

	//source.cpp
	//#include "h3.h"
	//M1(a, 1);
	//====================
	//#include "h2.h"
	//
	//M1(a, 1);
	public void testMacro() throws Exception {
		IPreferenceStore preferenceStore = getPreferenceStore();
		preferenceStore.setValue(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION,
				UnusedStatementDisposition.REMOVE.toString());
		assertExpectedResults();
	}

	//string.h
	//#include "stddef.h"
	//extern char* strchr(char* s, int c);

	//stddef.h
	//#define NULL 0

	//source.cpp
	//#include "stddef.h"
	//char* test() {
	//  int* p = NULL;
	//  return strchr("aaa", '*');
	//}
	//====================
	//#include "string.h"
	//
	//char* test() {
	//  int* p = NULL;
	//  return strchr("aaa", '*');
	//}
	public void testExportedSymbol() throws Exception {
		IPreferenceStore preferenceStore = getPreferenceStore();
		preferenceStore.setValue(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION,
				UnusedStatementDisposition.REMOVE.toString());
		SymbolExportMap symbolExportMap = new SymbolExportMap(new String[] { "NULL", "string.h" });
		preferenceStore.setValue(PreferenceConstants.INCLUDES_SYMBOL_EXPORTING_HEADERS,
				SymbolExportMap.serializeMaps(Collections.singletonList(symbolExportMap)));
		assertExpectedResults();
	}

	//h1.h
	//class A {};
	//class B;

	//h2.h
	//class C {};

	//source.cpp
	//A a;
	//B* b;
	//C* c;
	//====================
	//#include "h1.h"
	//
	//class C;
	//
	//A a;
	//B* b;
	//C* c;
	public void testSymbolToDeclareIsDefinedInIncludedHeader() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//typedef int int32;

	//h2.h
	//#include "h1.h"
	//extern int32 var;

	//source.cpp
	//int a = var;
	//====================
	//#include "h2.h"
	//
	//int a = var;
	public void testVariableReference() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//struct A {
	//  template <typename T>
	//  void m(const T& p);
	//};

	//h2.h
	//#include "h1.h"
	//template<typename T>
	//void A::m(const T& p) {
	//}

	//h3.h
	//#include "h1.h"
	//typedef A B;

	//source.cpp
	//void test(B& b) {
	//  b.m(1);
	//}
	//====================
	//#include "h2.h"
	//#include "h3.h"
	//
	//void test(B& b) {
	//  b.m(1);
	//}
	public void testMethodDefinedInHeader() throws Exception {
		assertExpectedResults();
	}

	//h1.h
	//namespace ns3 {
	//class C {};
	//namespace ns2 {
	//class A {};
	//class B {};
	//namespace ns1 {
	//C* f(const A& a, B* b) { return nullptr; }
	//} // ns1
	//} // ns2
	//} // ns3

	//source.cpp
	//#include "h1.h"
	//void test(ns3::ns2::A& a) {
	//  ns3::C* c = ns3::ns2::ns1::f(a, nullptr);
	//}
	//====================
	//namespace ns3 {
	//class C;
	//namespace ns2 {
	//class A;
	//class B;
	//} /* namespace ns2 */
	//} /* namespace ns3 */
	//namespace ns3 {
	//namespace ns2 {
	//namespace ns1 {
	//C * f(const A &a, B *b);
	//} /* namespace ns1 */
	//} /* namespace ns2 */
	//} /* namespace ns3 */
	//
	//void test(ns3::ns2::A& a) {
	//  ns3::C* c = ns3::ns2::ns1::f(a, nullptr);
	//}
	public void testForwardDeclarations() throws Exception {
		// TODO(sprigogin): Move ns1 outside of other namespaces after IncludeOrganizer starts using ASTWriter.
		IPreferenceStore preferenceStore = getPreferenceStore();
		preferenceStore.setValue(PreferenceConstants.INCLUDES_UNUSED_STATEMENTS_DISPOSITION,
				UnusedStatementDisposition.REMOVE.toString());
		preferenceStore.setValue(PreferenceConstants.FORWARD_DECLARE_FUNCTIONS, true);
		assertExpectedResults();
	}
}