content: /*******************************************************************************
   * 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.internal.bsl.ui.views;
  
  import java.util.List;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.emf.ecore.EObject;
  import org.eclipse.jface.text.IMarkSelection;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.viewers.ISelection;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.jface.viewers.TreeViewer;
  import org.eclipse.ui.ISelectionListener;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.xtext.nodemodel.INode;
  import org.eclipse.xtext.resource.EObjectAtOffsetHelper;
  import org.eclipse.xtext.resource.XtextResource;
  import org.eclipse.xtext.ui.editor.XtextEditor;
  import org.eclipse.xtext.ui.editor.model.IXtextDocument;
  import org.eclipse.xtext.util.CancelIndicator;
  import org.eclipse.xtext.util.concurrent.CancelableUnitOfWork;
  
  import com._1c.g5.v8.dt.bsl.comment.DocumentationCommentProperties;
  import com._1c.g5.v8.dt.bsl.common.IBslPreferences;
  import com._1c.g5.v8.dt.bsl.documentation.comment.BslCommentUtils;
  import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment;
  import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment.Description;
  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.BslDocumentationComment.Section;
  import com._1c.g5.v8.dt.bsl.documentation.comment.BslMultiLineCommentDocumentationProvider;
  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;
  import com._1c.g5.v8.dt.bsl.documentation.comment.TypeSection.FieldDefinition;
  import com._1c.g5.v8.dt.bsl.documentation.comment.TypeSection.LinkContainsTypeDefinition;
  import com._1c.g5.v8.dt.bsl.documentation.comment.TypeSection.TypeDefinition;
  import com._1c.g5.v8.dt.bsl.model.Method;
  import com._1c.g5.v8.dt.bsl.model.Module;
  import com._1c.g5.v8.dt.bsl.ui.editor.BslXtextDocument;
  import com._1c.g5.v8.dt.common.Pair;
  import com._1c.g5.v8.dt.core.platform.IResourceLookup;
  
  /**
   * The listener interface for receiving {@link ISelection} events.
   * The class that is interested in processing a  {@link ISelection}
   * event implements this interface, and the object created
   * with that class is registered with a component using the
   * SelectionService <code>addPostSelectionListener</code> method. When
   * the ISelection event occurs, that object's appropriate
   * method is invoked.
   *
   * @author Dmitriy Marmyshev
   */
  public class BslDocCommentSelectionListener
      implements ISelectionListener
  {
  
      private final TreeViewer viewer;
  
      private final IResourceLookup resourceLookup;
  
      private final IBslPreferences bslPreferences;
  
      private final BslMultiLineCommentDocumentationProvider commentProvider;
  
      private final EObjectAtOffsetHelper bslObjectAtOffsetHelper;
  
      /**
       * Instantiates a new bsl doc comment selection listener.
       *
       * @param viewer the viewer, cannot be {@code null}.
       * @param resourceLookup the resource lookup service, cannot be {@code null}.
       * @param bslPreferences the bsl preferences, cannot be {@code null}.
       * @param bslObjectAtOffsetHelper the bsl object at offset helper, cannot be {@code null}.
       * @param commentProvider the comment provider, cannot be {@code null}.
       */
      public BslDocCommentSelectionListener(TreeViewer viewer, IResourceLookup resourceLookup,
          IBslPreferences bslPreferences, EObjectAtOffsetHelper bslObjectAtOffsetHelper,
          BslMultiLineCommentDocumentationProvider commentProvider)
      {
          this.viewer = viewer;
          this.resourceLookup = resourceLookup;
          this.bslPreferences = bslPreferences;
          this.commentProvider = commentProvider;
          this.bslObjectAtOffsetHelper = bslObjectAtOffsetHelper;
  
      }
  
      @Override
      public void selectionChanged(IWorkbenchPart part, ISelection selection)
      {
  
          BslDocumentationComment doc = null;
          if (selection instanceof IStructuredSelection
              && ((IStructuredSelection)selection).getFirstElement() instanceof Method)
          {
              doc = getDocComment((Method)((IStructuredSelection)selection).getFirstElement());
              viewer.setInput(doc);
          }
          else if (selection instanceof ITextSelection || selection instanceof IMarkSelection)
          {
              int offset = 0;
  
              if (selection instanceof ITextSelection)
              {
                  ITextSelection ts = (ITextSelection)selection;
                  offset = ts.getOffset();
              }
              else if (selection instanceof IMarkSelection)
              {
                  IMarkSelection ts = (IMarkSelection)selection;
                  offset = ts.getOffset();
              }
  
              Pair<BslDocumentationComment, Object> result = getSelectedDocModel(part, offset);
              if (result != null)
              {
                  doc = result.first;
              }
              viewer.setInput(doc);
              if (result != null && result.second != null)
              {
                  viewer.setSelection(new StructuredSelection(result.second));
              }
              else
              {
                  viewer.setSelection(new StructuredSelection());
              }
          }
  
      }
  
      private Pair<BslDocumentationComment, Object> getSelectedDocModel(IWorkbenchPart part, final int offset)
      {
          XtextEditor target = part.getAdapter(XtextEditor.class);
          if (target == null)
          {
              return null;
          }
  
          IXtextDocument xtextDoc = target.getDocument();
          CancelableUnitOfWork<Pair<BslDocumentationComment, Object>, XtextResource> unit = new CancelableUnitOfWork<>()
          {
              @Override
              public Pair<BslDocumentationComment, Object> exec(XtextResource res, CancelIndicator monitor)
                  throws Exception
              {
                  if (monitor.isCanceled())
                  {
                      return null;
                  }
  
                  if (res.getContents() != null && !res.getContents().isEmpty())
                  {
                      EObject obj = res.getContents().get(0);
  
                      // do only for bsl module
                      if (obj instanceof Module)
                      {
                          EObject subObject = bslObjectAtOffsetHelper.resolveElementAt(res, offset);
                          if (subObject instanceof Method)
                          {
                              if (monitor.isCanceled())
                              {
                                  return null;
                              }
  
                              BslDocumentationComment docComment = getDocComment((Method)subObject);
                              Object selected = getSelected(docComment, subObject, offset);
                              return Pair.newPair(docComment, selected);
                          }
                          else if (subObject != null)
                          {
                              for (EObject e = subObject; e != null; e = e.eContainer())
                              {
                                  if (monitor.isCanceled())
                                  {
                                      return null;
                                  }
                                  if (e instanceof Method)
                                  {
                                      return Pair.newPair(getDocComment((Method)e), null);
                                  }
                              }
                          }
                      }
                  }
                  return null;
              }
  
          };
  
          if (xtextDoc instanceof BslXtextDocument)
          {
              return ((BslXtextDocument)xtextDoc).readOnlyDataModelWithoutSync(unit);
          }
          return xtextDoc.readOnly(unit);
      }
  
      private BslDocumentationComment getDocComment(Method method)
      {
          IProject project = resourceLookup.getProject(method);
          DocumentationCommentProperties props = bslPreferences.getDocumentCommentProperties(project);
  
          return BslCommentUtils.parseTemplateComment(method, props.oldCommentFormat(), commentProvider);
      }
  
      private Object getSelected(BslDocumentationComment docComment, EObject method, int offset)
      {
          if (docComment == null)
          {
              return null;
          }
  
          List<INode> nodes = commentProvider.getDocumentationNodes(method);
          if (nodes.isEmpty())
          {
              return null;
          }
  
          INode actual = null;
          for (INode node : nodes)
          {
              if (node.getOffset() > offset)
              {
                  break;
              }
              actual = node;
          }
  
          if (actual != null && actual.getEndOffset() > offset)
          {
              int actualOffset = offset - actual.getOffset();
              int line = nodes.indexOf(actual);
  
              if (line > -1 && actualOffset > 0)
              {
                  return getSelected(docComment, line, actualOffset);
              }
          }
  
          return null;
      }
  
      private Object getListSelected(List<?> objects, int line, int offset)
      {
          if (objects == null)
          {
              return null;
          }
          for (Object object : objects)
          {
              Object selected = getSelected(object, line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          return null;
      }
  
      private Object getSelected(Object object, int line, int offset)
      {
  
          if (object instanceof TextPart)
          {
              TextPart section = (TextPart)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getText().length() > offset)
              {
                  return section;
              }
          }
          else if (object instanceof LinkPart)
          {
              LinkPart section = (LinkPart)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getLinkTextOffset() + section.getLinkText().length() > offset)
              {
                  return section;
              }
          }
          else if (object instanceof TagPart section)
          {
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getContent().length() > offset)
              {
                  return section;
              }
          }
          else if (object instanceof FieldDefinition)
          {
              FieldDefinition section = (FieldDefinition)object;
              if (section.getLineNumber() == line && section.getNameOffset() < offset
                  && section.getNameOffset() + section.getName().length() > offset)
              {
                  return section;
              }
              Object selected = getSelected(section.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getTypeSections(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof LinkContainsTypeDefinition)
          {
              LinkContainsTypeDefinition section = (LinkContainsTypeDefinition)object;
              Object selected = getSelected(section.getLink(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getContainTypes(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getFieldDefinitionExtension(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(section.getLinkToExtensionFields(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof TypeDefinition)
          {
              TypeDefinition section = (TypeDefinition)object;
              if (section.getLineNumber() == line && section.getNameOffset() < offset
                  && section.getNameOffset() + section.getTypeName().length() > offset)
              {
                  return section;
              }
  
              Object selected = getListSelected(section.getContainTypes(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getFieldDefinitionExtension(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(section.getLinkToExtensionFields(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof BslDocumentationComment)
          {
              BslDocumentationComment root = (BslDocumentationComment)object;
  
              Object selected = getSelected(root.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(root.getParametersSection(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(root.getCallOptionsSection(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(root.getExampleSection(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getSelected(root.getReturnSection(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof Description)
          {
              return getListSelected(((Description)object).getParts(), line, offset);
          }
          else if (object instanceof ParametersSection)
          {
              ParametersSection section = (ParametersSection)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getHeaderKeywordLength() > offset)
              {
                  return section;
              }
              Object selected = getSelected(section.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getParameterDefinitions(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof ReturnSection)
          {
              ReturnSection section = (ReturnSection)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getHeaderKeywordLength() > offset)
              {
                  return section;
              }
              Object selected = getSelected(section.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getReturnTypes(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof TypeSection)
          {
              TypeSection section = (TypeSection)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getHeaderKeywordLength() > offset)
              {
                  return section;
              }
              Object selected = getSelected(section.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
              selected = getListSelected(section.getTypeDefinitions(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          else if (object instanceof Section)
          {
              Section section = (Section)object;
              if (section.getLineNumber() == line && section.getOffset() < offset
                  && section.getOffset() + section.getHeaderKeywordLength() > offset)
              {
                  return section;
              }
              Object selected = getSelected(section.getDescription(), line, offset);
              if (selected != null)
              {
                  return selected;
              }
          }
          return null;
      }
  }
  