/*******************************************************************************
 * Copyright (c) 2005, 2008 Wind River Systems, Inc.
 * 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: 
 *     Markus Schorn - initial API and implementation 
 ******************************************************************************/ 
package org.eclipse.cdt.ui.tests.refactoring.rename;

import java.io.StringWriter;

import org.eclipse.core.resources.IFile;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.search.internal.core.text.FileCharSequenceProvider;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMManager;
import org.eclipse.cdt.core.tests.BaseTestFramework;

import org.eclipse.cdt.internal.core.dom.SavedCodeReaderFactory;

/**
 * @author markus.schorn@windriver.com
 */
public class RefactoringTests extends BaseTestFramework {
    private int fBufferSize;

    public RefactoringTests() {
    }

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

    @Override
	protected void setUp() throws Exception {
        super.setUp();
        CCorePlugin.getIndexManager().setIndexerId(cproject, IPDOMManager.ID_FAST_INDEXER);
        fBufferSize= FileCharSequenceProvider.BUFFER_SIZE;
        FileCharSequenceProvider.BUFFER_SIZE= 1024 * 4;
    }

    @Override
	protected void tearDown() throws Exception {
        super.tearDown();
        SavedCodeReaderFactory.getInstance().getCodeReaderCache().flush();
        FileCharSequenceProvider.BUFFER_SIZE= fBufferSize;
    }

    protected void assertTotalChanges(int numChanges, Change changes) throws Exception {
        assertTotalChanges(numChanges, 0, 0, changes);
    }

    protected void assertTotalChanges(int numChanges, int potChanges, int commentCh, 
            Change changes) throws Exception {
        int count[]= { 0, 0, 0 };
        if (changes != null) {
            countChanges(changes, count);
        }
        assertEquals(numChanges, count[0]);
        assertEquals("potential changes: ", potChanges, count[1]); //$NON-NLS-1$
        assertEquals("comment changes: ", commentCh, count[2]); //$NON-NLS-1$
    }

    private void countChanges(Change change, int[] count) {
        if (change instanceof CompositeChange) {
            Change[] children = ((CompositeChange) change).getChildren();
            for (int i = 0; i < children.length; i++) {
                countChanges(children[i], count);
            }
        } else if (change instanceof TextFileChange) {
            TextFileChange tfc= (TextFileChange) change;
            TextEditChangeGroup[] tecgs= tfc.getTextEditChangeGroups();
            for (int i = 0; i < tecgs.length; i++) {
                TextEditChangeGroup group = tecgs[i];
                countChanges(group, count);
            }
        }
    }

    private void countChanges(TextEditChangeGroup edit, int[] count) {
        String name= edit.getName();
        if (name.indexOf("potential") != -1) { //$NON-NLS-1$
            count[1]++;
        } else if (name.indexOf("comment") != -1) { //$NON-NLS-1$
            count[2]++;
        } else {
            count[0]++;
        }
    }

    protected void assertChange(Change changes, IFile file, int startOffset, int numChars, String newText) throws Exception {
        assertChange(changes, file, startOffset, numChars, newText, false);
    }

    protected void assertChange(Change changes, IFile file, int startOffset, int numChars, String newText, boolean potential) throws Exception {
        boolean found = false;
        if (changes != null && changes instanceof CompositeChange) {
            found = checkCompositeChange((CompositeChange) changes, file, startOffset, numChars, newText, potential);
        }
        
        if (!found) {
            fail ("Rename at offset " + startOffset + " in \"" + file.getLocation() + "\" not found.");  //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
            assertFalse(true);
        }
    }

    private boolean checkCompositeChange(CompositeChange composite, IFile file, int startOffset, int numChars, String newText, boolean potential) {
        boolean found = false;
        Change[] children = composite.getChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof CompositeChange) {
                found = checkCompositeChange((CompositeChange) children[i], file, startOffset, numChars, newText, potential);
            } else if (children[i] instanceof TextFileChange) {
                TextFileChange tuChange = (TextFileChange) children[i];
                if (tuChange.getFile().toString().equals(file.toString())) {
                    found = checkTranslationUnitChange(tuChange, startOffset, numChars, newText, potential);
                }
            }
            if (found)
                return found;
        }
        return found;
    }

    private boolean checkTranslationUnitChange(TextFileChange change, int startOffset, int numChars, String newText, boolean potential) {
        TextEditChangeGroup[] groups= change.getTextEditChangeGroups();
        for (int i = 0; i < groups.length; i++) {
            TextEditGroup group = groups[i].getTextEditGroup();
            if ((group.getName().indexOf("potential") != -1) == potential) {  //$NON-NLS-1$
                TextEdit[] edits= group.getTextEdits();
                if (checkTextEdits(edits, startOffset, numChars, newText)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean checkTextEdit(TextEdit edit, int startOffset, int numChars, String newText) {
        if (edit instanceof MultiTextEdit) {
            if (checkTextEdits(((MultiTextEdit) edit).getChildren(), 
                    startOffset, numChars, newText)) {
                return true;
            }
        } else if (edit instanceof ReplaceEdit) {
            if (checkReplaceEdit((ReplaceEdit) edit, startOffset, numChars, newText)) {
                return true;
            }
        }
        return false;
    }

    private boolean checkTextEdits(TextEdit[] edits, int startOffset, int numChars, String newText) {
        for (int i = 0; i < edits.length; i++) {
            TextEdit edit = edits[i];
            if (checkTextEdit(edit, startOffset, numChars, newText)) {
                return true;
            }
        }
        return false;
    }

    private boolean checkReplaceEdit(ReplaceEdit edit, int startOffset, int numChars, String newText) {
        return (edit.getOffset() == startOffset && edit.getLength() == numChars && edit.getText().equals(newText ) );
    }
    
    protected IFile createCppFwdDecls(String fileName) throws Exception {
        StringWriter writer = new StringWriter();
        writer.write("class class_fwd;         \n"); //$NON-NLS-1$
        writer.write("struct struct_fwd;       \n"); //$NON-NLS-1$
        writer.write("union union_fwd;         \n"); //$NON-NLS-1$
        writer.write("int func_proto();        \n"); //$NON-NLS-1$
        writer.write("int func_proto_ov();     \n"); //$NON-NLS-1$
        writer.write("int func_proto_ov(int);  \n"); //$NON-NLS-1$
        writer.write("int func_proto_ov(int*); \n"); //$NON-NLS-1$
        writer.write("extern int extern_var;   \n"); //$NON-NLS-1$
        String contents = writer.toString();
        return importFile(fileName, contents ); 
    }

    protected IFile createCFwdDecls(String fileName) throws Exception {
        StringWriter writer = new StringWriter();
        writer.write("struct struct_fwd;       \n"); //$NON-NLS-1$
        writer.write("union union_fwd;         \n"); //$NON-NLS-1$
        writer.write("int func_proto();        \n"); //$NON-NLS-1$
        writer.write("extern int extern_var;   \n"); //$NON-NLS-1$
        String contents = writer.toString();
        return importFile(fileName, contents ); 
    }

    protected IFile createCppDefs(String fileName) throws Exception {
        StringWriter writer = new StringWriter();
        writer.write("class class_def {               \n"); //$NON-NLS-1$
        writer.write("public:                         \n"); //$NON-NLS-1$
        writer.write("   int member;                  \n"); //$NON-NLS-1$
        writer.write("   static int static_member;    \n"); //$NON-NLS-1$
        writer.write("   void method(int par);        \n"); //$NON-NLS-1$
        writer.write("   void static_method(int par); \n"); //$NON-NLS-1$
        writer.write("   int method_ov();             \n"); //$NON-NLS-1$
        writer.write("   int method_ov(int);          \n"); //$NON-NLS-1$
        writer.write("   int method_ov(int*);         \n"); //$NON-NLS-1$
        writer.write("};                              \n"); //$NON-NLS-1$
        writer.write("struct struct_def {        \n"); //$NON-NLS-1$
        writer.write("   int st_member;          \n"); //$NON-NLS-1$
        writer.write("};                         \n"); //$NON-NLS-1$
        writer.write("union union_def {          \n"); //$NON-NLS-1$
        writer.write("   int un_member;          \n"); //$NON-NLS-1$
        writer.write("};                         \n"); //$NON-NLS-1$
        writer.write("typedef int typedef_def;   \n"); //$NON-NLS-1$
        writer.write("namespace namespace_def{}; \n"); //$NON-NLS-1$
        writer.write("enum enum_def {            \n"); //$NON-NLS-1$
        writer.write("   enum_item };            \n"); //$NON-NLS-1$
        writer.write("int func_def() {}          \n"); //$NON-NLS-1$
        writer.write("int func_def_ov() {}       \n"); //$NON-NLS-1$
        writer.write("int func_def_ov(int){}     \n"); //$NON-NLS-1$
        writer.write("int func_def_ov(int*){}    \n"); //$NON-NLS-1$
        writer.write("int var_def;               \n"); //$NON-NLS-1$
        String contents = writer.toString();
        return importFile(fileName, contents ); 
    }

    protected IFile createCDefs(String fileName) throws Exception {
        StringWriter writer = new StringWriter();
        writer.write("struct struct_def {        \n"); //$NON-NLS-1$
        writer.write("   int st_member;          \n"); //$NON-NLS-1$
        writer.write("};                         \n"); //$NON-NLS-1$
        writer.write("union union_def {          \n"); //$NON-NLS-1$
        writer.write("   int un_member;          \n"); //$NON-NLS-1$
        writer.write("};                         \n"); //$NON-NLS-1$
        writer.write("typedef int typedef_def;   \n"); //$NON-NLS-1$
        writer.write("enum enum_def {            \n"); //$NON-NLS-1$
        writer.write("   enum_item };            \n"); //$NON-NLS-1$
        writer.write("int func_def() {}          \n"); //$NON-NLS-1$
        writer.write("int var_def;               \n"); //$NON-NLS-1$
        String contents = writer.toString();
        return importFile(fileName, contents ); 
    }

    protected void assertRefactoringError(RefactoringStatus status, String msg) {
        RefactoringStatusEntry e= status.getEntryMatchingSeverity(RefactoringStatus.ERROR);
        assertNotNull("Expected refactoring error!", e); //$NON-NLS-1$
        assertEquals(msg, e.getMessage());
    }

    protected void assertRefactoringWarning(RefactoringStatus status, String msg) {
        RefactoringStatusEntry e= status.getEntryMatchingSeverity(RefactoringStatus.WARNING);
        assertNotNull("Expected refactoring warning!", e); //$NON-NLS-1$
        assertEquals(msg, e.getMessage());
    }

    protected void assertRefactoringOk(RefactoringStatus status) {
        assertTrue("Expected refactoring status ok: " + //$NON-NLS-1$
                status.getMessageMatchingSeverity(status.getSeverity()), 
                status.getSeverity()==RefactoringStatus.OK); 
    }
}
