/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2023 DBeaver Corp and others
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jkiss.dbeaver.ui.editors.entity.properties;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.*;
import org.eclipse.ui.internal.services.INestable;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.MultiPageEditorSite;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.ui.*;
import org.jkiss.dbeaver.ui.controls.folders.TabbedFolderPage;
import org.jkiss.dbeaver.ui.editors.*;
import org.jkiss.dbeaver.ui.editors.entity.EntityEditorDescriptor;

/**
 * EditorWrapperSection
 */
public class TabbedFolderPageEditor extends TabbedFolderPage implements IDatabaseEditorContributorUser, ISaveablePart, IRefreshablePart, DBPAdaptable, ISearchContextProvider {

    private static final Log log = Log.getLog(TabbedFolderPageEditor.class);

    private IDatabaseEditor mainEditor;
    private EntityEditorDescriptor editorDescriptor;
    private IEditorPart editor;
    private IEditorActionBarContributor actionContributor;
    private IEditorSite nestedEditorSite;

    public TabbedFolderPageEditor(IDatabaseEditor mainEditor, EntityEditorDescriptor editorDescriptor) {
        this.mainEditor = mainEditor;
        this.editorDescriptor = editorDescriptor;
    }

    public IEditorPart getEditor() {
        return editor;
    }

    @Override
    public void createControl(Composite parent) {
        editor = editorDescriptor.createEditor();

        final IWorkbenchPartSite ownerSite = this.mainEditor.getSite();
        if (ownerSite instanceof MultiPageEditorSite) {
            final MultiPageEditorPart ownerMultiPageEditor = ((MultiPageEditorSite) ownerSite).getMultiPageEditor();
            nestedEditorSite = new TabbedFolderPageEditorSite(ownerMultiPageEditor, editor);

            // Add property change forwarding
            // We need it to tell owner editor about dirty state change
            if (ownerMultiPageEditor instanceof IPropertyChangeReflector) {
                editor.addPropertyListener(new IPropertyListener() {
                    @Override
                    public void propertyChanged(Object source, int propId) {
                        ((IPropertyChangeReflector) ownerMultiPageEditor).handlePropertyChange(propId);
                    }
                });
            }

        } else {
            nestedEditorSite = new SubEditorSite(mainEditor.getEditorSite());
        }

        try {
            IEditorInput editorInput = mainEditor.getEditorInput();
            if (editorInput instanceof IDatabaseEditorInput) {
                editorInput = editorDescriptor.getNestedEditorInput((IDatabaseEditorInput)editorInput);
            }
            editor.init(nestedEditorSite, editorInput);
        } catch (PartInitException e) {
            DBWorkbench.getPlatformUI().showError("Create SQL viewer", null, e);
        }
        editor.createPartControl(parent);

        if (editor instanceof ISingleControlEditor) {
            // Use focus to active selection provider and contributed actions
            Control editorControl = ((ISingleControlEditor) editor).getEditorControl();
            assert editorControl != null;
            editorControl.addFocusListener(new FocusListener() {
                @Override
                public void focusGained(FocusEvent e) {
                    ISelectionProvider selectionProvider = editor.getSite().getSelectionProvider();
                    mainEditor.getSite().setSelectionProvider(selectionProvider);
                    //selectionProvider.setSelection(selectionProvider.getSelection());

                    if (actionContributor != null) {
                        actionContributor.setActiveEditor(editor);
                    }
                    activateNestedSite(true);
                }

                @Override
                public void focusLost(FocusEvent e) {
                    activateNestedSite(false);
                    if (actionContributor != null) {
                        actionContributor.setActiveEditor(null);
                    }
                }
            });
        }

        parent.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                if (editor != null) {
                    editor.dispose();
                    editor = null;
                }
            }
        });
    }

    @Override
    public void dispose() {
        if (nestedEditorSite instanceof MultiPageEditorSite) {
            ((MultiPageEditorSite) nestedEditorSite).dispose();
            nestedEditorSite = null;
        }
        super.dispose();
    }

    @Override
    public void setFocus() {
        if (editor != null) {
            editor.setFocus();
        }
    }

    @Override
    public void aboutToBeShown() {
        if (editor instanceof IActiveWorkbenchPart) {
            ((IActiveWorkbenchPart) editor).activatePart();
        }
    }

    @Override
    public void aboutToBeHidden() {
        if (editor instanceof IActiveWorkbenchPart) {
            ((IActiveWorkbenchPart) editor).deactivatePart();
        }
    }

    @SuppressWarnings("deprecation")
    private void activateNestedSite(boolean activate) {
        if (nestedEditorSite instanceof INestable) {
            if (activate) {
                ((INestable) nestedEditorSite).activate();
            } else {
                ((INestable) nestedEditorSite).deactivate();
            }
        }
        if (nestedEditorSite instanceof MultiPageEditorSite) {
            final IKeyBindingService keyBindingService = ((MultiPageEditorSite) nestedEditorSite).getMultiPageEditor().getEditorSite()
                .getKeyBindingService();
            if (keyBindingService instanceof INestableKeyBindingService) {
                ((INestableKeyBindingService) keyBindingService).activateKeyBindingService(activate ? nestedEditorSite : null);
            }
        }
    }

    @Override
    public RefreshResult refreshPart(Object source, boolean force) {
        if (editor instanceof IRefreshablePart) {
            return ((IRefreshablePart) editor).refreshPart(source, force);
        }
        return RefreshResult.IGNORED;
    }

    @Override
    public <T> T getAdapter(Class<T> adapter) {
        if (editor != null) {
            if (adapter.isAssignableFrom(editor.getClass())) {
                return adapter.cast(editor);
            } else {
                return editor.getAdapter(adapter);
            }
        }
        return null;
    }

    @Override
    public IEditorActionBarContributor getContributor(IDatabaseEditorContributorManager manager) {
        Class<? extends IEditorActionBarContributor> contributorClass = editorDescriptor.getContributorClass();
        if (contributorClass == null) {
            return null;
        }
        this.actionContributor = manager.getContributor(contributorClass);
        if (this.actionContributor == null) {
            try {
                this.actionContributor = contributorClass.getConstructor().newInstance();
            } catch (Exception e) {
                log.error(e);
            }
        }
        return this.actionContributor;
    }

    @Override
    public void doSave(IProgressMonitor monitor) {
        if (editor != null) {
            editor.doSave(monitor);
        }
    }

    @Override
    public void doSaveAs() {
        if (editor != null) {
            editor.doSaveAs();
        }
    }

    @Override
    public boolean isDirty() {
        return editor != null && editor.isDirty();
    }

    @Override
    public boolean isSaveAsAllowed() {
        return editor != null && editor.isSaveAsAllowed();
    }

    @Override
    public boolean isSaveOnCloseNeeded() {
        return editor != null && editor.isSaveOnCloseNeeded();
    }

    @Override
    public boolean isSearchPossible() {
        return editor instanceof ISearchContextProvider || editor instanceof ITextEditor;
    }

    @Override
    public boolean isSearchEnabled() {
        return isSearchPossible();
    }

    @Override
    public boolean performSearch(SearchType searchType) {
        if (editor instanceof ISearchContextProvider) {
            return ((ISearchContextProvider) editor).performSearch(searchType);
        } else if (editor instanceof ITextEditor) {
            String teAction;
            switch (searchType) {
                case NONE: teAction = ITextEditorActionConstants.FIND; break;
                case NEXT: teAction = ITextEditorActionConstants.FIND_NEXT; break;
                case PREVIOUS: teAction = ITextEditorActionConstants.FIND_PREVIOUS; break;
                default: return false;
            }
            IAction action = ((ITextEditor) editor).getAction(teAction);
            if (action != null) {
                action.run();
                return true;
            }
        }
        return false;
    }

    private class TabbedFolderPageEditorSite extends MultiPageEditorSite implements INestedEditorSite {

        public TabbedFolderPageEditorSite(MultiPageEditorPart multiPageEditor, IEditorPart editor) {
            super(multiPageEditor, editor);
        }
        
        @Override
        public String getId() {
            String id = editorDescriptor.getId();
            return id;
        }

        @NotNull
        @Override
        public IEditorPart getFolderEditor() {
            return mainEditor;
        }
    }

}