/*******************************************************************************
 * Copyright (C) 2021, 1C-Soft LLC and others.
 *
 * 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:
 *     1C-Soft LLC - initial API and implementation
 *******************************************************************************/
package com.e1c.v8codestyle.bsl.ui.itests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.intro.IIntroPart;
import org.eclipse.xtext.util.StringInputStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment;
import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment.ParametersSection;
import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment.ReturnSection;
import com._1c.g5.v8.dt.bsl.documentation.comment.LinkPart;
import com._1c.g5.v8.dt.bsl.documentation.comment.TagPart;
import com._1c.g5.v8.dt.bsl.documentation.comment.TextPart;
import com._1c.g5.v8.dt.bsl.documentation.comment.TypeSection.FieldDefinition;
import com._1c.g5.v8.dt.bsl.documentation.comment.TypeSection.TypeDefinition;
import com._1c.g5.v8.dt.bsl.ui.editor.BslXtextEditor;
import com._1c.g5.v8.dt.testing.TestingWorkspace;
import com._1c.g5.v8.dt.ui.util.OpenHelper;
import com.e1c.v8codestyle.internal.bsl.ui.views.BslDocCommentView;

/**
 * Integration test of the {@link BslDocCommentViewTest} that process selection in BSL editor and reacts in the view.
 *
 * @author Dmitriy Marmyshev
 */
public class BslDocCommentViewTest
{
    private static final String PROJECT_NAME = "CommonModule";

    private static final String COMMON_MODULE_FILE_NAME = "/src/CommonModules/CommonModule/Module.bsl";

    @Rule
    public TestingWorkspace testingWorkspace = new TestingWorkspace(true, false);

    protected static final String FOLDER_RESOURCE = "/resources/";

    private IProject project;

    private OpenHelper openHelper = new OpenHelper();

    @Before
    public void setUp() throws CoreException
    {
        IIntroPart introPart = PlatformUI.getWorkbench().getIntroManager().getIntro();
        PlatformUI.getWorkbench().getIntroManager().closeIntro(introPart);

        boolean closed = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
        assertTrue(closed);
        Display display = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getDisplay();
        while (!display.isDisposed() && display.readAndDispatch())
        {
            // do it
        }

        project = testingWorkspace.getProject(PROJECT_NAME);

        if (!project.exists() || !project.isAccessible())
        {
            testingWorkspace.cleanUpWorkspace();
            project = this.testingWorkspace.setUpProject(PROJECT_NAME, getClass());
        }
    }

    @After
    public void shutDown()
    {
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
        Display display = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getDisplay();
        while (!display.isDisposed() && display.readAndDispatch())
        {
            // do it
        }
    }

    /**
     * Load module and open Bsl documentation comment view, select elements in doc comment test
     * then check selected in view.
     *
     * @throws Exception the exception
     */
    @Test
    public void testOpenModuleAndSelectElements() throws Exception
    {
        updateModule(FOLDER_RESOURCE + "doc-comment-view.bsl");

        IFile file = project.getFile(COMMON_MODULE_FILE_NAME);

        IViewPart view =
            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(BslDocCommentView.ID);
        assertTrue(view instanceof BslDocCommentView);
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(view);

        IEditorPart fEditor = openHelper.openEditor(file, new TextSelection(14, 1));
        assertTrue(fEditor instanceof FormEditor);

        IEditorPart editor = ((FormEditor)fEditor).getActiveEditor();
        assertTrue(editor instanceof BslXtextEditor);

        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(editor);

        BslXtextEditor bslEditor = (BslXtextEditor)editor;

        selectText(bslEditor, 14, 1, view);
        Object first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof TextPart);
        TextPart textPart = (TextPart)first;
        assertEquals(0, textPart.getLineNumber());
        assertEquals("Method description", textPart.getText());

        selectText(bslEditor, 92, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof TextPart);
        textPart = (TextPart)first;
        assertEquals(4, textPart.getLineNumber());
        assertEquals("- has not type for key", textPart.getText());

        selectText(bslEditor, 77, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof FieldDefinition);
        FieldDefinition fieldPart = (FieldDefinition)first;
        assertEquals(4, fieldPart.getLineNumber());
        assertEquals("Key1", fieldPart.getName());

        selectText(bslEditor, 50, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof FieldDefinition);
        fieldPart = (FieldDefinition)first;
        assertEquals(3, fieldPart.getLineNumber());
        assertEquals("Parameters", fieldPart.getName());

        selectText(bslEditor, 33, 0, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof ParametersSection);
        ParametersSection sectionPart = (ParametersSection)first;
        assertEquals(2, sectionPart.getLineNumber());

        selectText(bslEditor, 64, 0, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof TypeDefinition);
        TypeDefinition typePart = (TypeDefinition)first;
        assertEquals(3, typePart.getLineNumber());
        assertEquals("Structure", typePart.getTypeName());

        selectText(bslEditor, 226, 0, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof ReturnSection);
        ReturnSection returnPart = (ReturnSection)first;
        assertEquals(1, returnPart.getLineNumber());

        selectText(bslEditor, 245, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof LinkPart);
        LinkPart linkPart = (LinkPart)first;
        assertEquals(2, linkPart.getLineNumber());
        assertEquals("Test", linkPart.getLinkText());
        assertEquals("See Test", linkPart.getInitialContent());

        selectText(bslEditor, 250, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof LinkPart);
        linkPart = (LinkPart)first;
        assertEquals(2, linkPart.getLineNumber());
        assertEquals("Test", linkPart.getLinkText());
        assertEquals("See Test", linkPart.getInitialContent());

        selectText(bslEditor, 283, 1, view);
        TreeViewer viewer = getViewer((BslDocCommentView)view);
        ITreeSelection selection = viewer.getStructuredSelection();
        assertTrue(selection.isEmpty());
        Object input = viewer.getInput();
        assertTrue(input instanceof BslDocumentationComment);

        selectText(bslEditor, 107, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof TagPart);
        TagPart tagPart = (TagPart)first;
        assertEquals(5, tagPart.getLineNumber());
        assertEquals("tag1", tagPart.getTagName());
        assertEquals(1, tagPart.getParameters().size());

        selectText(bslEditor, 195, 1, view);
        first = getViewerSelection((BslDocCommentView)view);
        assertTrue(first instanceof TagPart);
        tagPart = (TagPart)first;
        assertEquals(0, tagPart.getLineNumber());
        assertEquals("tag2", tagPart.getTagName());
        assertEquals(1, tagPart.getParameters().size());
    }

    private Object getViewerSelection(BslDocCommentView view) throws Exception
    {
        TreeViewer viewer = getViewer(view);
        ITreeSelection selection = viewer.getStructuredSelection();
        assertFalse(selection.isEmpty());
        return selection.getFirstElement();
    }

    private void selectText(BslXtextEditor bslEditor, int offset, int lenght, IViewPart view) throws Exception
    {
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(bslEditor);

        boolean[] selected = new boolean[1];
        ISelectionListener listener = (part, selection) -> {
            if (selection instanceof TextSelection)
            {
                selected[0] = true;
            }
        };
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().addPostSelectionListener(listener);
        Display display = bslEditor.getShell().getDisplay();
        display.syncExec(() -> {
            bslEditor.getSelectionProvider().setSelection(new TextSelection(offset, lenght));
        });
        waitEventSetnd(bslEditor);

        for (int i = 0; !selected[0] && i < 10; i++)
        {
            Thread.sleep(500);
            waitEventSetnd(bslEditor);
        }

        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().removePostSelectionListener(listener);
    }

    private TreeViewer getViewer(BslDocCommentView view) throws Exception
    {
        Field field = view.getClass().getDeclaredField("viewer");
        field.setAccessible(true);
        return (TreeViewer)field.get(view);
    }

    private void updateModule(String pathToResource) throws Exception
    {
        IFile file = project.getFile(COMMON_MODULE_FILE_NAME);

        try (InputStream inputStream = getClass().getResourceAsStream(pathToResource);
            InputStream in = new StringInputStream(
                new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
                    .collect(Collectors.joining("\n")),
                StandardCharsets.UTF_8.name()))
        {
            if (file.exists())
            {
                file.setContents(in, true, true, new NullProgressMonitor());
            }
            else
            {
                file.create(in, true, new NullProgressMonitor());
            }
        }
        testingWorkspace.waitForBuildCompletion();
    }

    private void waitEventSetnd(IWorkbenchPart part)
    {
        Display display = part.getSite().getShell().getDisplay();
        while (!display.isDisposed() && display.readAndDispatch())
        {
            // do it
        }
    }
}
