/*******************************************************************************
 * Copyright (c) 2005, 2007 IBM Corporation 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:
 * 	P.Tomaszewski
 *  Anton Leherbauer (Wind River Systems)
 *  Markus Schorn (Wind River Systems)
 *******************************************************************************/

package org.eclipse.cdt.internal.ui.actions;

import java.util.ResourceBundle;

import org.eclipse.jface.text.ITextSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.TextEditorAction;

import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ICElement;
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.model.IWorkingCopy;
import org.eclipse.cdt.ui.CUIPlugin;

import org.eclipse.cdt.internal.ui.editor.CEditor;

/**
 * Gives possibility to move fast between member elements of the c/c++ source.
 *
 * @author P.Tomaszewski
 */
public class GoToNextPreviousMemberAction extends TextEditorAction {

    public static final String NEXT_MEMBER = "GotoNextMember"; //$NON-NLS-1$
	public static final String PREVIOUS_MEMBER = "GotoPrevMember"; //$NON-NLS-1$
	
	/** Determines should action take user to the next member or to the previous one. */
    private boolean fGotoNext;
    
    /**
     * Creates new action.
     * @param bundle Resource bundle.
     * @param prefix Prefix.
     * @param editor Editor.
     * @param gotoNext Is it go to next or previous action.
     */
    public GoToNextPreviousMemberAction(ResourceBundle bundle, String prefix, ITextEditor editor, boolean gotoNext) {
        super(bundle, prefix, editor);

        fGotoNext = gotoNext;
    }

    /**
     * Creates new action.
     * @param bundle Resource bundle.
     * @param prefix Prefix.
     * @param editor Editor.
     * @param style UI style.
     * @param gotoNext Is it go to next or previous action.
     */
    public GoToNextPreviousMemberAction(ResourceBundle bundle, String prefix, ITextEditor editor, int style, boolean gotoNext) {
        super(bundle, prefix, editor, style);

        fGotoNext = gotoNext;
    }

    /*
	 * @see org.eclipse.ui.texteditor.TextEditorAction#update()
	 */
	public void update() {
        final ITextEditor editor = getTextEditor();
		setEnabled(editor instanceof CEditor && ((CEditor)editor).getInputCElement() != null);
	}

	/**
     * @see org.eclipse.jface.action.Action#run()
     */
    public void run() {
        final CEditor editor = (CEditor) getTextEditor();
        final ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection();
        final IEditorInput editorInput = editor.getEditorInput();
        final IWorkingCopy workingCopy =  CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput);
        if (workingCopy == null) {
        	return;
        }
        try {
            ISourceReference next= fGotoNext ? 
            		getNextElement(workingCopy, selection.getOffset()) :
            		getPrevElement(workingCopy, selection.getOffset());
            if (next != null) {
                editor.selectAndReveal(next.getSourceRange().getIdStartPos(), 0);
            } 
        } catch (CModelException e) {
        	CUIPlugin.getDefault().log(e);
        }
    }

	private ISourceReference getNextElement(IParent parent, int offset) throws CModelException {
		ICElement[] children= parent.getChildren();
		for (int i = 0; i < children.length; i++) {
			ICElement element = children[i];
			if (element instanceof ISourceReference) {
				final ISourceReference candidate1= (ISourceReference) element;
				final ISourceRange range= candidate1.getSourceRange();
				final int idpos1= range.getIdStartPos();
				if (element instanceof IParent && range.getStartPos() + range.getLength() > offset) {
					ISourceReference candidate2= getNextElement((IParent) element, offset);
					if (candidate2 != null) {
						final int idpos2= candidate2.getSourceRange().getIdStartPos();
						if (idpos1 <= offset || idpos1 > idpos2) {
							return candidate2;
						}
					}
				}
				if (idpos1 > offset) {
					return candidate1;
				}
			}
		}
		return null;
	}

	private ISourceReference getPrevElement(IParent parent, int offset) throws CModelException {
		ICElement[] children= parent.getChildren();
		for (int i= children.length-1; i >= 0; i--) {
			ICElement element = children[i];
			if (element instanceof ISourceReference) {
				final ISourceReference candidate1= (ISourceReference) element;
				final ISourceRange range= candidate1.getSourceRange();
				final int idpos1= range.getIdStartPos();
				if (element instanceof IParent && range.getStartPos() < offset) {
					ISourceReference candidate2= getPrevElement((IParent) element, offset);
					if (candidate2 != null) {
						final int idpos2= candidate2.getSourceRange().getIdStartPos();
						if (idpos1 >= offset || idpos1 < idpos2) {
							return candidate2;
						}
					}
				}
				if (idpos1 < offset) {
					return candidate1;
				}
			}
		}
		return null;
	}
}
