/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu)
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributors:
 *   Jan Koehnlein - Initial API and implementation
 *******************************************************************************/
package com.github.jknack.antlr4ide.ui.railroad.actions;

import org.eclipse.draw2d.IFigure;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.xtext.Constants;
import org.eclipse.xtext.ui.editor.IURIEditorOpener;
import org.eclipse.xtext.ui.editor.XtextEditor;

import com.github.jknack.antlr4ide.ui.railroad.RailroadSelectionProvider;
import com.github.jknack.antlr4ide.ui.railroad.RailroadView;
import com.github.jknack.antlr4ide.ui.railroad.RailroadViewPreferences;
import com.github.jknack.antlr4ide.ui.railroad.figures.IEObjectReferer;
import com.github.jknack.antlr4ide.ui.railroad.figures.ISelectable;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

@Singleton
public class RailroadSelectionLinker implements IPropertyChangeListener {

  @Inject
  @Named(Constants.LANGUAGE_NAME)
  private String language;

  @Inject
  private IURIEditorOpener uriEditorOpener;

  @Inject
  private RailroadView view;

  private XtextEditor currentEditor;

  private ISelectable currentSelectedNode;

  @Inject
  private RailroadViewPreferences preferences;

  private ISelectionChangedListener diagramSelectionChangeListener = new ISelectionChangedListener() {
    @Override
    public void selectionChanged(final SelectionChangedEvent event) {
      if (view.getControl().isFocusControl()) {
        diagramSelectionChanged(event);
      }
    }
  };

  private ISelectionChangedListener textSelectionChangeListener = new ISelectionChangedListener() {
    @Override
    public void selectionChanged(final SelectionChangedEvent event) {
      if (preferences.isLinkWithEditor() && view.getSite().getPage().getActivePart() != view) {
        textSelectionChanged(event);
      }
    }
  };

  public void activate() {
    view.getSite().getSelectionProvider().addSelectionChangedListener(diagramSelectionChangeListener);
    preferences.getPreferenceStore().addPropertyChangeListener(this);
    if (preferences.isLinkWithEditor()) {
      selectGrammarText(currentSelectedNode, false);
    }
  }

  public void deactivate() {
    view.getSite().getSelectionProvider().removeSelectionChangedListener(diagramSelectionChangeListener);
    if (currentEditor != null) {
      removeTextSelectionListener(currentEditor);
    }
    preferences.getPreferenceStore().removePropertyChangeListener(this);
  }

  protected void selectFigure(IFigure selectedFigure) {
    while (selectedFigure != null && !(selectedFigure instanceof IEObjectReferer)) {
      selectedFigure = selectedFigure.getParent();
    }
    if (selectedFigure != null) {
      if (currentSelectedNode != null) {
        currentSelectedNode.setSelected(false);
      }
      if (selectedFigure instanceof ISelectable) {
        ((ISelectable) selectedFigure).setSelected(true);
        currentSelectedNode = (ISelectable) selectedFigure;
      } else {
        currentSelectedNode = null;
      }
    }
  }

  protected void selectGrammarText(final IFigure selectedFigure, final boolean isActivateEditor) {
    if (selectedFigure != null) {
      IEObjectReferer element = (IEObjectReferer) selectedFigure;
      final URI grammarElementURI = element.getEObjectURI();
      if (grammarElementURI != null) {
        final boolean isSelectElement = selectedFigure instanceof ISelectable;
        // enqueue to make sure the diagram is updated before
        if (isActivateEditor && isSelectElement) {
          Display.getDefault().asyncExec(new Runnable() {
            @Override
            public void run() {
              uriEditorOpener.open(grammarElementURI, isSelectElement);
            }
          });
        } else {
          uriEditorOpener.open(grammarElementURI, isSelectElement);
          view.getSite().getPage().activate(view);
        }
      }
    }
  }

  public void diagramSelectionChanged(final SelectionChangedEvent event) {
    ISelection selection = event.getSelection();
    if (selection instanceof IStructuredSelection) {
      IStructuredSelection structuredSelection = (IStructuredSelection) selection;
      if (structuredSelection.size() == 1) {
        Object selectedElement = structuredSelection.getFirstElement();
        if (selectedElement instanceof IFigure) {
          IFigure selectedFigure = (IFigure) selectedElement;
          selectFigure(selectedFigure);
          boolean isDoubleClick = event instanceof RailroadSelectionProvider.DoubleClickEvent;
          if ((currentEditor == null || !currentEditor.getInternalSourceViewer().getTextWidget()
              .isFocusControl())
              && (isDoubleClick || preferences.isLinkWithEditor())) {
            selectGrammarText(selectedFigure, isDoubleClick);
          }
        }
      }
    }
  }

  public void textSelectionChanged(final SelectionChangedEvent event) {
    ISelection selection = event.getSelection();
    if (selection instanceof ITextSelection && !selection.isEmpty()) {
      ITextSelection textSelection = (ITextSelection) selection;
      int offset = textSelection.getOffset();
      IFigure figureToBeSelected = findFigureForTextOffset(view.getContents(), offset, null);
      if (figureToBeSelected != null) {
        selectFigure(figureToBeSelected);
        view.reveal(figureToBeSelected);
      }
    }
  }

  protected ISelectable findFigureForTextOffset(final IFigure figure, final int offset, ISelectable currentBestFigure) {
    if(figure == null) {
      return null;
    }
    if (figure instanceof ISelectable) {
      Region textRegion = ((ISelectable) figure).getTextRegion();
      if (textRegion != null && textRegion.getOffset() <= offset
          && textRegion.getOffset() + textRegion.getLength() >= offset) {
        if(currentBestFigure == null || currentBestFigure.getTextRegion().getLength() > textRegion.getLength()) {
          currentBestFigure = (ISelectable) figure;
        }
      }
    }
    for (Object child : figure.getChildren()) {
      if (child instanceof IFigure) {
        currentBestFigure = findFigureForTextOffset((IFigure) child, offset, currentBestFigure);
      }
    }
    return currentBestFigure;
  }

  @Override
  public void propertyChange(final PropertyChangeEvent event) {
    if (event.getProperty().equals(RailroadViewPreferences.LINK_WITH_EDITOR_KEY)
        && event.getNewValue() == Boolean.TRUE) {
      if (currentSelectedNode != null) {
        selectGrammarText(currentSelectedNode, true);
      }
    }
  }

  public void setXtextEditor(final XtextEditor xtextEditor) {
    if (currentEditor != null) {
      removeTextSelectionListener(currentEditor);
    }
    if (language.equals(xtextEditor.getLanguageName())) {
      currentEditor = xtextEditor;
      ISelectionProvider selectionProvider = xtextEditor.getSelectionProvider();
      if (selectionProvider instanceof IPostSelectionProvider) {
        ((IPostSelectionProvider) selectionProvider)
            .addPostSelectionChangedListener(textSelectionChangeListener);
      } else {
        selectionProvider.addSelectionChangedListener(textSelectionChangeListener);
      }
    }
  }

  protected void removeTextSelectionListener(final XtextEditor editor) {
    ISelectionProvider selectionProvider = editor.getSelectionProvider();
    if (selectionProvider != null) {
      if (selectionProvider instanceof IPostSelectionProvider) {
        ((IPostSelectionProvider) selectionProvider)
            .removePostSelectionChangedListener(textSelectionChangeListener);
      } else {
        selectionProvider.removeSelectionChangedListener(textSelectionChangeListener);
      }
    }
  }
}