/*******************************************************************************
 * Copyright (c) 2013, 2018 Red Hat Inc.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.perf.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.linuxtools.internal.perf.IPerfData;
import org.eclipse.linuxtools.internal.perf.PerfPlugin;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.FindReplaceAction;

/**
 * A ViewPart to display the output from perf's source disassembly.
 */
public class SourceDisassemblyView extends ViewPart implements IFindReplaceTarget{

    private static final Color RED = new Color(Display.getDefault(), 150, 0, 0);
    private static final Color ORANGE = new Color(Display.getDefault(), 150, 100, 0);
    private static final Color GREEN = new Color(Display.getDefault(), 0, 100, 0);
    private static String ASM = "\\s+([0-9]+\\.[0-9]+ )?:\\s+[0-9a-f]+:\\s+[0-9a-z]+\\s+.*"; //$NON-NLS-1$
    private static String CODE = "\\s+:\\s+.*"; //$NON-NLS-1$
    private static String WORD_BOUNDARY = "\\b"; //$NON-NLS-1$'
    private static int SECONDARY_ID = 0;
    private StyledText text;
    public SourceDisassemblyView() {
    }

    @Override
    public void createPartControl(Composite parent) {
        parent.setLayoutData(new GridLayout(1, true));

        text = new StyledText(parent, SWT.WRAP | SWT.V_SCROLL);
        text.setEditable(false);

        IPerfData data = PerfPlugin.getDefault().getSourceDisassemblyData();
        if (data != null) {
            setStyledText(data.getPerfData());
            setContentDescription(data.getTitle());
            setupFindDialog();
        }
    }

    @Override
    public void setFocus() {
        return;
    }

    /**
     * Set styled text field (only used for testing).
     *
     * @param txt StyledText to set.
     */
    protected void setStyledText(StyledText txt) {
        text = txt;
    }

    /**
     * Get the text content of this view.
     *
     * @return String content of this view
     */
    public String getContent() {
        return (text == null) ? "" : text.getText(); //$NON-NLS-1$
    }

    /**
     * Set styled text field based on the specified string, which is parsed in
     * order to set appropriate styles to be used for rendering the widget
     * content.
     *
     * @param input text content of widget.
     */
    private void setStyledText (String input) {
        List<StyleRange> styles = new ArrayList<> ();
        int ptr = 0;

        text.setText(input);

        StringTokenizer tok = new StringTokenizer(input, "\n"); //$NON-NLS-1$
        while (tok.hasMoreTokens()) {
            String line = tok.nextToken();
            if (Pattern.matches(ASM, line)) {
                Matcher m = Pattern.compile(ASM).matcher(line);
                if (m.matches() && m.group(1) != null) {

                    try {
                        float percent = Float.parseFloat(m.group(1).trim());
                        if (percent >= 20) {
                            styles.add(new StyleRange(ptr, line.length(), RED, null));
                        } else if  (percent >= 5) {
                            styles.add(new StyleRange(ptr, line.length(), ORANGE, null));
                        }
                    } catch (NumberFormatException e) {
                        // set no StyleRange
                    }
                }
            } else if (Pattern.matches(CODE, line)) {
                styles.add(new StyleRange(ptr, line.length(), GREEN, null));
            }

            // + 1 to skip over the '\n' at EOL that the tokenizer eats
            ptr += line.length() + 1;
        }
        text.setStyleRanges(styles.toArray(new StyleRange [0]));
    }

    public static void refreshView () {
        Display.getDefault().syncExec(() -> {
		    try {
		        // A new view is created every time
		        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
		                .showView(PerfPlugin.SOURCE_DISASSEMBLY_VIEW_ID,
		                        Integer.toString(SECONDARY_ID++),
		                        IWorkbenchPage.VIEW_CREATE);
		    } catch (PartInitException e) {
		        IStatus status = new Status(IStatus.ERROR, PerfPlugin.PLUGIN_ID, e.getMessage(), e);
		        PerfPlugin.getDefault().getLog().log(status);
		    }
		});
    }

    /**
     * Create find dialog and set is as a toolbar action.
     */
    private void setupFindDialog() {
        FindReplaceAction findAction = new FindReplaceAction(
                Platform.getResourceBundle(PerfPlugin.getDefault().getBundle()),
                null, text.getShell(), this);
        findAction.setImageDescriptor(PerfPlugin
                .getImageDescriptor("icons/search.gif"));//$NON-NLS-1$
        findAction.setToolTipText(PerfPlugin.STRINGS_SearchSourceDisassembly);
        IActionBars bars = getViewSite().getActionBars();
        bars.getToolBarManager().add(findAction);
        bars.setGlobalActionHandler(ActionFactory.FIND.getId(), findAction);
    }

    @Override
    public boolean canPerformFind() {
        return text != null && !text.getText().isEmpty();
    }

    @Override
    public int findAndSelect(int widgetOffset, String findString,
            boolean searchForward, boolean caseSensitive, boolean wholeWord) {
        int matchIndex = -1;
        String searchString = text.getText();
        String findRegex = findString;

        // offset is -1 when text boundaries are reached during a wrapped search
        if (widgetOffset < 0) {
            widgetOffset = searchForward ? 0 : searchString.length();
        }

        if (wholeWord) {
            findRegex = WORD_BOUNDARY + findRegex + WORD_BOUNDARY;
        }

        int caseFlag = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
        Pattern pattern = Pattern.compile(findRegex, caseFlag);
        Matcher matcher = pattern.matcher(searchString);

        if (searchForward) {
            matchIndex = matcher.find(widgetOffset) ? matcher.start() : -1;
        } else {
            // backward search from 0 to offset (exclusive)
            matcher.region(0, widgetOffset);

            // get start index of last match
            while (matcher.find()) {
                matchIndex = matcher.start();
            }
        }

        // only select when a match has been found
        if (matchIndex != -1) {
            text.setSelection(matchIndex, matchIndex + findString.length());
        }
        return matchIndex;
    }

    @Override
    public Point getSelection() {
        Point selection = text.getSelection();
        // selection point consists of starting point x and lenght y - x.
        return new Point(selection.x, selection.y - selection.x);
    }

    @Override
    public String getSelectionText() {
        return text.getSelectionText();
    }

    @Override
    public boolean isEditable() {
        return false;
    }

    @Override
    public void replaceSelection(String text) {
    }

}
