/*******************************************************************************
 * Copyright (c) 2012, 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 - initial API and implementation
 *******************************************************************************/

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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp.STPCompletionProcessor;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp.STPDocumentProvider;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp.STPEditor;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.TapsetLibrary;
import org.junit.BeforeClass;
import org.junit.Test;
import org.osgi.framework.FrameworkUtil;

public class STPCompletionProcessorTest {

    private static String TEST_STP_SCRIPT = ""+
            "\n"+
            "\n//marker1"+
            "probe syscall.write{\n"+
            "  // Some comment inside a probe\n"+
            "   printf(\"%s fd %d\taddr%d\tcount%dargstr%s\n\", name, fd, buf_uaddr, count, argstr)\n"+
            "}\n"+
            "\n";

    private static class MockSTPDocumentProvider extends STPDocumentProvider {
        private IDocument document;

        MockSTPDocumentProvider(IDocument document){
            this.document = document;
            this.setupDocument(document);
        }

        protected IDocument createDocument() {
            return document;
        }
    }

    private static class MockSTPEditor extends STPEditor {
        public MockSTPEditor(IDocument document) {
            super();
            setDocumentProvider(new MockSTPDocumentProvider(document));
        }
    }

    /**
     * Use pre-written contents to populate the Function & Probe views.
     */
    @BeforeClass
	public static void prepareTrees() {
		TapsetLibrary.stop();
		IPath path = new Path(System.getenv("HOME")).append(".systemtapgui"); //$NON-NLS-1$ //$NON-NLS-2$
		path.toFile().mkdirs(); // ensure folder will exist
		path = path.append("TreeSettings").addFileExtension("xml"); //$NON-NLS-1$ //$NON-NLS-2$

		try (InputStream is = FileLocator.openStream(FrameworkUtil.getBundle(STPCompletionProcessorTest.class),
				new Path("helpers/TreeSettings.xml"), false);
				OutputStream outStream = new FileOutputStream(path.toFile())) {
			Files.copy(is, path.toFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
			TapsetLibrary.readTreeFile();
		} catch (IOException e) {
			fail("Unable to read dummy function/probe tree file for testing");
		}
	}

    @Test
    public void testCompletionRequest() {
        Document testDocument = new Document("");
        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();
        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument, 0);
        assertNotNull(proposals);
    }

    @Test
    public void testCompletionRequestAtEOF() {
        Document testDocument = new Document(TEST_STP_SCRIPT);
        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();
        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        TEST_STP_SCRIPT.length());
        assertNotNull(proposals);
    }

    @Test
    public void testGlobalCompletion() {
        MockSTPDocumentProvider provider = new MockSTPDocumentProvider(new Document(TEST_STP_SCRIPT));
        IDocument testDocument = provider.createDocument();
        int offset = TEST_STP_SCRIPT.indexOf("//marker1");

        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();
        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        offset);

        assertTrue(proposalsContain(proposals, "probe "));
        assertTrue(proposalsContain(proposals, "global "));
        assertTrue(proposalsContain(proposals, "function "));
    }

    @Test
    public void testGlobalPartialCompletion() throws BadLocationException {
        String prefix = "prob";
        ICompletionProposal[] proposals = getCompletionsForPrefix(prefix);
        assertTrue(proposalsContain(proposals, "probe "));
        assertTrue(!proposalsContain(proposals, "global "));
        assertTrue(!proposalsContain(proposals, "function "));
    }

    @Test
    public void testProbeCompletion() throws BadLocationException {
        String prefix = "probe ";
        ICompletionProposal[] proposals = getCompletionsForPrefix(prefix);
        assertTrue(proposalsContain(proposals, "syscall"));
        assertTrue(!proposalsContain(proposals, "syscall.write"));
    }

    @Test
    public void testMultiProbeCompletion() throws BadLocationException {
        String prefix = "probe begin,e";
        ICompletionProposal[] proposals = getCompletionsForPrefix(prefix);
        assertTrue(proposalsContain(proposals, "end"));
        assertTrue(proposalsContain(proposals, "error"));

        prefix = "probe myBegin = b";
        proposals = getCompletionsForPrefix(prefix);
        assertTrue(proposalsContain(proposals, "begin"));
    }

    @Test
    public void testGlobalInvalidCompletion() throws BadLocationException {
        ICompletionProposal[] proposals = getCompletionsForPrefix("probe fake.fake");
        assertEquals(proposals.length, 0);
    }

    @Test
    public void testStaticProbeCompletion() throws BadLocationException {
        ICompletionProposal[] proposals = getCompletionsForPrefix("probe kernel.");
        assertTrue(proposalsContain(proposals, "kernel.function(string)"));
        assertTrue(proposalsContain(proposals, "kernel.mark(string)"));
    }

    @Test
    public void testEndProbeCompletion() throws BadLocationException {
        Document testDocument = new Document(TEST_STP_SCRIPT);
        @SuppressWarnings("unused")
        MockSTPEditor editor = new MockSTPEditor(testDocument);

        int offset = TEST_STP_SCRIPT.indexOf("//marker1");
        String prefix = "probe end{}";
        testDocument.replace(offset, 0, prefix);
        offset += prefix.length() - 1;

        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();
        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        offset);

        assertTrue(proposalsContain(proposals, "addr"));
        assertTrue(proposalsContain(proposals, "backtrace"));
        assertTrue(proposalsContain(proposals, "cmdline_args"));
    }

    @Test
    public void testProbeVariableCompletion() throws BadLocationException {
        Document testDocument = new Document(TEST_STP_SCRIPT);
        @SuppressWarnings("unused")
        MockSTPEditor editor = new MockSTPEditor(testDocument);

        int offset = TEST_STP_SCRIPT.indexOf("//marker1");
        String prefix = "probe syscall.write{}";
        testDocument.replace(offset, 0, prefix);
        offset += prefix.length() - 1;

        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();
        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        offset);

        assertTrue(proposalsContain(proposals, "fd:long"));
        assertTrue(proposalsContain(proposals, "name:string"));
        assertTrue(proposalsContain(proposals, "buf_uaddr:long"));
    }

    @Test
    public void testStaticProbeNormalizationCompletion() throws BadLocationException {
        ICompletionProposal[] proposals = getCompletionsForPrefix("probe kernel.function(\"PATTERNASDF\").");
        assertTrue(proposalsContain(proposals, "kernel.function(string).return"));

        proposals = getCompletionsForPrefix("probe probe process(\"PAT/H/\").");
        assertTrue(proposalsContain(proposals, "process(string).begin"));
        assertTrue(proposalsContain(proposals, "process(string).end"));

        proposals = getCompletionsForPrefix("probe  process(123).");
        assertTrue(proposalsContain(proposals, "process(number).begin"));
        assertTrue(proposalsContain(proposals, "process(number).end"));

        proposals = getCompletionsForPrefix("probe module(\"MPATTERasdfN\").");
        assertTrue(proposalsContain(proposals, "module(string).function(string)"));
        assertTrue(proposalsContain(proposals, "module(string).statement(string)"));
    }

    private static ICompletionProposal[] getCompletionsForPrefix(String prefix) throws BadLocationException {
        MockSTPDocumentProvider provider = new MockSTPDocumentProvider(new Document(TEST_STP_SCRIPT));
        IDocument testDocument = provider.createDocument();
        int offset = TEST_STP_SCRIPT.indexOf("//marker1");
        testDocument.replace(offset, 0, prefix);
        offset += prefix.length();

        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();

        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        offset);
        return proposals;
    }

    @Test
    public void testFunctionCompletion() throws BadLocationException {
        Document testDocument = new Document(TEST_STP_SCRIPT);
        @SuppressWarnings("unused")
        MockSTPEditor editor = new MockSTPEditor(testDocument);

        int offset = TEST_STP_SCRIPT.indexOf("//marker1");
        String prefix = "probe syscall.write{addr}";
        testDocument.replace(offset, 0, prefix);
        offset += prefix.length() - 1;

        STPCompletionProcessor completionProcessor = new STPCompletionProcessor();

        ICompletionProposal[] proposals = completionProcessor
                .computeCompletionProposals(testDocument,
                        offset);

        assertTrue(proposalsContain(proposals, "addr"));
        assertTrue(proposalsContain(proposals, "addr_from_rqst"));
        assertTrue(proposalsContain(proposals, "addr_from_rqst_str"));
    }

    private static boolean proposalsContain(ICompletionProposal[] proposals, String proposal){
        for (ICompletionProposal p : proposals) {
            if (p.getDisplayString().contains(proposal)) {
                return true;
            }
        }
        return false;
    }
}
