/*******************************************************************************
 * Copyright (c) 2008, 2018 Phil Muldoon <pkmuldoon@picobot.org>.
 *
 * 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:
 *    Phil Muldoon <pkmuldoon@picobot.org> - initial API and implementation.
 *******************************************************************************/

package org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp;

import java.util.ArrayList;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.swt.widgets.Display;

/**
 * Reconciling strategy for Systemtap editor code folding positions. The positional aspects
 * of document tag discovery should really be placed with a position builder
 */
public class STPReconcilingStrategy  implements IReconcilingStrategy,
               IReconcilingStrategyExtension {

    // Constants
    protected static final int STP_NO_TAG = 0;
    protected static final int STP_MULTILINE_COMMENT_TAG = 1;
    protected static final int STP_PROBE = 2;
    protected static final int STP_FUNCTION = 3;

    // Next Character Position
    protected int nextCharPosition = 0;

    // Current tag start
    protected int currentTagStart = 0;

    // Current tag end
    protected int currentTagEnd = 0;

    // List of positions
    protected final ArrayList<Position> documentPositionList = new ArrayList<>();

    // The end offset of the range to be scanned *//*
    protected int endOfDocumentPostion;

    private IDocument currentDocument;
    private STPEditor currentEditor;

    /**
     * Sets the current editor.
     */
    public void setEditor(STPEditor editor) {
        this.currentEditor = editor;
    }

    /**
     * Sets the current (ie working) document.
     */
    @Override
    public void setDocument(IDocument document) {
        this.currentDocument = document;
    }

    @Override
    public void reconcile(IRegion partition) {
        // Just rebuild the whole document
        initialReconcile();
    }

    @Override
    public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
        //Just rebuild the whole document
        initialReconcile();
    }

    @Override
    public void initialReconcile() {
        endOfDocumentPostion = currentDocument.getLength();
        try {
            calculatePositions();
        } catch (BadLocationException e) {
            // Cannot reconcile, return
            return;
        }
    }

    @Override
    public void setProgressMonitor(IProgressMonitor monitor) {

    }

    /**
     *
     * From currentDocument, calculate beginning of document
     * to endOfDocumentPostion to build positions for code folding.
     *
     * @throws BadLocationException
     */
    private void calculatePositions() throws BadLocationException {
        // Clear old positions and reset to beginning of document
        documentPositionList.clear();
        nextCharPosition = 0;

        // Build the actual document positions
        buildPositions();

        // Paint the folding annotations in the background.
        Display.getDefault().asyncExec(() -> currentEditor.updateFoldingStructure(documentPositionList));
    }

    /**
     *
     * Start trying to guess if given char z, what - if any - tag this
     * begins.
     *
     * @param location - location of current position
     * @return - tag type, if any
     *
     * @throws BadLocationException
     */
    private int classifyComponent(int location) throws BadLocationException {
        int deltaLocation = location;
        char ch = currentDocument.getChar(deltaLocation);
        switch (ch) {
            // The 'comment' case.
            case '/':
                deltaLocation++;
                ch = currentDocument.getChar(deltaLocation);
                if (ch == '*') {
                    currentTagStart = location;
                    deltaLocation++;
                    nextCharPosition = deltaLocation;
                    return STP_MULTILINE_COMMENT_TAG;
                }
                break;
            // The 'probe' case.
            case 'p':
                if (isProbe()) {
                    currentTagStart = location;
                    return STP_PROBE;
                }

            // The 'function' case.
            case 'f':
                if (isFunction()) {
                    currentTagStart = location;
                    return STP_FUNCTION;
                }
            // No tag, don't fold region.
            default:
                break;
        }
        return STP_NO_TAG;
    }

    /**
     *
     * Build a list of locations to mark beginning and end of folding regions.
     *
     * @throws BadLocationException
     */
    private void buildPositions() throws BadLocationException {
        while (nextCharPosition < endOfDocumentPostion) {
            switch (classifyComponent(nextCharPosition))
            {
                // All of these cases have found the beginning of a tag
                // to start folding. Each element must now be find
                // the end of the region it represents.
                case STP_MULTILINE_COMMENT_TAG:
                    currentTagEnd = findEndOfComment();
                    writePosition(currentTagStart,currentTagEnd);
                    nextCharPosition = currentTagStart + currentTagEnd;
                    break;
                case STP_PROBE:
                case STP_FUNCTION:
                    currentTagEnd = findEndOfProbeOrFunction();
                    writePosition(currentTagStart,currentTagEnd);
                    nextCharPosition = currentTagStart + currentTagEnd;
                    break;
                default:
                    nextCharPosition++;
                    break;
            }
        }
    }

    /**
     *
     * Write a Position to the position list.
     *
     * @param startOffset - start of position in the document.
     * @param length - length of position.
     *
     */
    private void writePosition(int startOffset, int length) {
        if (length > 0)
            documentPositionList.add(new Position(startOffset, length));
    }

    private boolean isProbe() throws BadLocationException {
        return matchKeyWord("probe"); //$NON-NLS-1$
    }

    private boolean isFunction() throws BadLocationException {
        return matchKeyWord("function"); //$NON-NLS-1$
    }

    private boolean matchKeyWord(String word) throws BadLocationException {
		StringBuilder keyWord = new StringBuilder();
        int location = nextCharPosition;
        while (location < endOfDocumentPostion) {
            char ch = currentDocument.getChar(location);
            if ((ch == ' ') || (!Character.isLetter(ch)))
                break;
            else
                keyWord.append(ch);
            location++;
        }
        if (keyWord.toString().compareTo(word) == 0)
            return true;
        return false;
    }

    private int findEndOfProbeOrFunction() throws BadLocationException {
        int bracketCount = 0;
        boolean firstBracket = false;
        char ch;

        while (nextCharPosition < endOfDocumentPostion) {
            ch = currentDocument.getChar(nextCharPosition);
            if (ch == '{') {
                firstBracket = true;
                bracketCount++;
            }
            if (ch == '}')
                bracketCount--;
            if ((bracketCount == 0) && (firstBracket))
                return (nextCharPosition-currentTagStart)+2;
            nextCharPosition++;
        }
        return -1;
    }

    private int findEndOfComment() throws BadLocationException {
        while (nextCharPosition < endOfDocumentPostion) {
            char ch = currentDocument.getChar(nextCharPosition);
            if (ch == '*') {
                nextCharPosition++;
                ch = currentDocument.getChar(nextCharPosition);
                if (ch == '/')
                    return (nextCharPosition-currentTagStart)+2;
            }
            nextCharPosition++;
        }
        return -1;
    }
}