/*******************************************************************************
 * 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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.function.Supplier;

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.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.intro.IIntroPart;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com._1c.g5.v8.dt.bsl.model.ModuleType;
import com._1c.g5.v8.dt.metadata.mdclass.ScriptVariant;
import com._1c.g5.v8.dt.testing.TestingWorkspace;
import com._1c.g5.wiring.ServiceAccess;
import com.e1c.v8codestyle.bsl.IModuleStructureProvider;
import com.e1c.v8codestyle.internal.bsl.ui.properties.ModuleStructurePropertyPage;
import com.google.common.io.CharStreams;

/**
 * Integration test of the {@link ModuleStructurePropertyPage}.
 *
 * @author Dmitriy Marmyshev
 */
public class ModuleStructurePropertyPageTest
{
    private static final String PROJECT_NAME = "CommonModule";

    private static final String SETTINGS_TEMPLATES_COMMON_MODULE_BSL = ".settings/templates/common_module.bsl";

    private static final String EDTOR_TITLE = "/" + PROJECT_NAME + "/" + SETTINGS_TEMPLATES_COMMON_MODULE_BSL;

    private static final String PROPERTY_PAGE_ID = "com.e1c.v8codestyle.bsl.ui.properties.moduleStructurePropertyPage";

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

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

    private IProject project;

    @Before
    public void setUp() throws CoreException
    {
        project = testingWorkspace.getProject(PROJECT_NAME);

        if (!project.exists() || !project.isAccessible())
        {
            testingWorkspace.cleanUpWorkspace();
            project = this.testingWorkspace.setUpProject(PROJECT_NAME, getClass());
        }
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);

        IIntroPart introPart = PlatformUI.getWorkbench().getIntroManager().getIntro();
        PlatformUI.getWorkbench().getIntroManager().closeIntro(introPart);
    }

    @After
    public void shutDown()
    {
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
    }

    /**
     * Test open and close module structure property page.
     *
     * @throws Exception the exception
     */
    @Test
    public void testOpenAndCloseModuleStructurePropertyPage() throws Exception
    {
        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        PreferenceDialog dialog = PreferencesUtil.createPropertyDialogOn(shell, project, PROPERTY_PAGE_ID, null, null);

        dialog.setBlockOnOpen(false);
        shell.getDisplay().syncExec(dialog::open);
        waitEventSetnd(dialog);
        Object selected = dialog.getSelectedPage();
        assertTrue(selected instanceof ModuleStructurePropertyPage);
        shell.getDisplay().syncExec(dialog::close);
    }

    /**
     * Test open module structure property page.
     *
     * @throws Exception the exception
     */
    @Test
    public void testOpenModuleTemplate() throws Exception
    {

        IModuleStructureProvider moduleStructureProvider = ServiceAccess.get(IModuleStructureProvider.class);
        Supplier<InputStream> templateProvider =
            moduleStructureProvider.getModuleStructureTemplate(project, ModuleType.COMMON_MODULE, false, null);
        assertNull(templateProvider);
        templateProvider =
            moduleStructureProvider.getModuleStructureTemplate(project, null, false, ScriptVariant.ENGLISH);
        assertNull(templateProvider);
        templateProvider = moduleStructureProvider.getModuleStructureTemplate(project, ModuleType.COMMON_MODULE, false,
            ScriptVariant.ENGLISH);
        assertNotNull(templateProvider);

        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        PreferenceDialog dialog = PreferencesUtil.createPropertyDialogOn(shell, project, PROPERTY_PAGE_ID, null, null);

        dialog.setBlockOnOpen(false);
        shell.getDisplay().syncExec(dialog::open);
        Object selected = dialog.getSelectedPage();
        assertTrue(selected instanceof ModuleStructurePropertyPage);
        ModuleStructurePropertyPage page = (ModuleStructurePropertyPage)selected;

        CheckboxTableViewer viewer = getViewer(page);
        assertFalse(viewer.getChecked(ModuleType.COMMON_MODULE));
        viewer.setChecked(ModuleType.COMMON_MODULE, true);

        Button applyButton = getApplyButtonControl(page);
        applyButton.notifyListeners(SWT.Selection, new Event());
        waitEventSetnd(dialog);

        Button buttonOpen = getOpenButtonControl(page);
        assertNotNull(buttonOpen);

        viewer.setSelection(new StructuredSelection(ModuleType.FORM_MODULE));
        waitEventSetnd(dialog);
        assertFalse(buttonOpen.isEnabled());

        viewer.setSelection(new StructuredSelection(ModuleType.COMMON_MODULE));
        waitEventSetnd(dialog);
        assertTrue(buttonOpen.isEnabled());

        buttonOpen.notifyListeners(SWT.Selection, new Event());
        assertNull(dialog.getShell());
        waitEventSetnd();

        // check editor content equals base template
        IFile file = project.getFile(SETTINGS_TEMPLATES_COMMON_MODULE_BSL);
        assertTrue(file.exists());

        try (InputStream in = file.getContents();
            Reader fileReader = new InputStreamReader(in, StandardCharsets.UTF_8);
            InputStream template = templateProvider.get();
            Reader templateReader = new InputStreamReader(template, StandardCharsets.UTF_8);)
        {
            String templateText = CharStreams.toString(templateReader);
            assertNotNull(templateText);
            String fileText = CharStreams.toString(fileReader);
            assertEquals(templateText, fileText);
        }

        // wait until open editor
        waitEventSetnd();
        IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
        assertNotNull(editor);
        assertEquals(EDTOR_TITLE, editor.getTitle());

        String newContent = "New template";
        try (InputStream in = new ByteArrayInputStream(newContent.getBytes(StandardCharsets.UTF_8));)
        {
            file.setContents(in, true, true, new NullProgressMonitor());
        }

        templateProvider = moduleStructureProvider.getModuleStructureTemplate(project, ModuleType.COMMON_MODULE, false,
            ScriptVariant.ENGLISH);

        try (InputStream template = templateProvider.get();
            Reader templateReader = new InputStreamReader(template, StandardCharsets.UTF_8);)
        {
            String templateText = CharStreams.toString(templateReader);
            assertNotNull(templateText);
            assertEquals(newContent, templateText);
        }

        // re-open properties to check selection
        dialog = PreferencesUtil.createPropertyDialogOn(shell, project, PROPERTY_PAGE_ID, null, null);

        dialog.setBlockOnOpen(false);
        shell.getDisplay().syncExec(dialog::open);
        selected = dialog.getSelectedPage();
        assertTrue(selected instanceof ModuleStructurePropertyPage);
        page = (ModuleStructurePropertyPage)selected;

        viewer = getViewer(page);
        assertTrue(viewer.getChecked(ModuleType.COMMON_MODULE));

        viewer.setChecked(ModuleType.COMMON_MODULE, false);

        applyButton = getApplyButtonControl(page);
        applyButton.notifyListeners(SWT.Selection, new Event());
        waitEventSetnd(dialog);

        shell.getDisplay().syncExec(dialog::close);

        assertFalse(file.exists());

    }

    private CheckboxTableViewer getViewer(ModuleStructurePropertyPage page) throws Exception
    {
        Field field = page.getClass().getDeclaredField("checkBoxViewer");
        field.setAccessible(true);
        return (CheckboxTableViewer)field.get(page);
    }

    private Button getApplyButtonControl(ModuleStructurePropertyPage page) throws Exception
    {
        Field field = PreferencePage.class.getDeclaredField("applyButton");
        field.setAccessible(true);
        return (Button)field.get(page);
    }

    private Button getOpenButtonControl(ModuleStructurePropertyPage page) throws Exception
    {
        Field field = page.getClass().getDeclaredField("openButton");
        field.setAccessible(true);
        return (Button)field.get(page);
    }

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

    private void waitEventSetnd()
    {

        Display display = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getDisplay();
        while (!display.isDisposed() && display.readAndDispatch())
        {
            // do it
        }
    }

}
