<commit_before>﻿using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Application.Settings;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.CodeCompletion;
using JetBrains.ReSharper.Feature.Services.CSharp.CodeCompletion.Infrastructure;
using JetBrains.ReSharper.Feature.Services.Lookup;
using JetBrains.ReSharper.Feature.Services.Tips;
using JetBrains.ReSharper.PostfixTemplates.Settings;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Impl;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExpectedTypes;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve.Filters;
using JetBrains.ReSharper.Psi.Pointers;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Services;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.TextControl;
using JetBrains.Util;

// todo: caret placement for void methods? after ;? formatting?

namespace JetBrains.ReSharper.PostfixTemplates.CodeCompletion
{
  [Language(typeof(CSharpLanguage))]
  public class CSharpStaticMethodsItemProvider : CSharpItemsProviderBase<CSharpCodeCompletionContext>
  {
    protected override bool IsAvailable(CSharpCodeCompletionContext context)
    {
      var completionType = context.BasicContext.CodeCompletionType;
      return completionType == CodeCompletionType.AutomaticCompletion
          || completionType == CodeCompletionType.BasicCompletion;
    }

    protected override bool AddLookupItems(CSharpCodeCompletionContext context,
                                           GroupedItemsCollector collector)
    {
      var referenceExpression = CommonUtils.FindReferenceExpression(context.UnterminatedContext) ??
                                CommonUtils.FindReferenceExpression(context.TerminatedContext);
      if (referenceExpression == null) return false;

      var qualifier = referenceExpression.QualifierExpression;
      if (qualifier == null) return false;

      IType qualifierType = qualifier.Type(), filterType = qualifierType;
      if (!qualifierType.IsResolved)
      {
        qualifierType = IsEnumTypeReference(qualifier);
        if (qualifierType == null) return false;

        filterType = qualifier.GetPredefinedType().Type;
      }

      var settingsStore = qualifier.GetSettingsStore();
      if (!settingsStore.GetValue(PostfixSettingsAccessor.ShowStaticMethods))
        return false;

      // prepare symbol table of suitable static methods
      var accessContext = new ElementAccessContext(qualifier);
      var declaredTypes = DeclaredTypesCollector.Accept(qualifierType);

      // collect all declared types
      var psiModule = context.PsiModule;
      var commonSymbolTable = declaredTypes.Aggregate(
        seed: EmptySymbolTable.INSTANCE,
        func: (table, type) => table.Merge(type.GetSymbolTable(psiModule)));

      var filteredSymbolTable = commonSymbolTable.Filter(
        new SuitableStaticMethodsFilter(filterType, qualifier),
        OverriddenFilter.INSTANCE, new AccessRightsFilter(accessContext));

      var innerCollector = new GroupedItemsCollector();
      GetLookupItemsFromSymbolTable(filteredSymbolTable, innerCollector, context, false);

      // decorate static lookup elements
      foreach (var item in innerCollector.Items)
      {
        var lookupItem = item as DeclaredElementLookupItem;
        if (lookupItem == null) continue;

        lookupItem.TextColor = SystemColors.GrayText;
        SubscribeAfterComplete(lookupItem);
        collector.AddToBottom(lookupItem);
      }

      return true;
    }

    private static void SubscribeAfterComplete([NotNull] DeclaredElementLookupItem lookupItem)
    {
      // sorry, ugly as fuck :(
      lookupItem.AfterComplete += (ITextControl textControl, ref TextRange range,
                                   ref TextRange decoration, TailType tailType,
                                   ref Suffix suffix, ref IRangeMarker marker) =>
      {
        var solution = lookupItem.Solution;
        var psiServices = solution.GetPsiServices();
        psiServices.CommitAllDocuments();

        var methods = GetAllMethods(lookupItem);
        if (methods.Count == 0) return;

        var ownerType = methods[0].GetContainingType().NotNull();

        var hasMultipleParams = HasMultipleParameters(methods);
        if (!hasMultipleParams) // put caret 'foo(arg){here};'
        {
          var documentRange = new DocumentRange(textControl.Document, decoration);
          marker = documentRange.EndOffsetRange().CreateRangeMarker();
          // todo: review formatting here + check void return type?
        }

        var referenceExpression = TextControlToPsi
          .GetElement<IReferenceExpression>(solution, textControl.Document, range.StartOffset);
        if (referenceExpression == null) return;

        // 'remember' qualifier textually
        var qualifierExpression = referenceExpression.QualifierExpression.NotNull();
        var qualifierText = qualifierExpression.GetText();
        var referencePointer = referenceExpression.CreateTreeElementPointer();

        // wrap type reference with typeof() expression for enum static methods
        if (IsEnumTypeReference(qualifierExpression) != null) qualifierText = "typeof(" + qualifierText + ")";

        // append ref argument modifier if all overloads are with first byref parameter
        if (FirstArgumentAlwaysByRef(methods)) qualifierText = "ref " + qualifierText;

        // append ', ' if all overloads with >1 arguments
        if (HasOnlyMultipleParameters(methods)) qualifierText += ", ";

        var parenthesisRange = decoration.SetStartTo(range.EndOffset);
        var parenthesisMarker = parenthesisRange.CreateRangeMarker(textControl.Document);

        // insert qualifier as first argument
        var argumentPosition = TextRange.FromLength(decoration.EndOffset - (parenthesisRange.Length/2), 0);
        textControl.Document.ReplaceText(argumentPosition, qualifierText);

        // replace qualifier with type (predefined/user type)<commit_msg>Cleanup<commit_after>﻿using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Application.Settings;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.CodeCompletion;
using JetBrains.ReSharper.Feature.Services.CSharp.CodeCompletion.Infrastructure;
using JetBrains.ReSharper.Feature.Services.Lookup;
using JetBrains.ReSharper.Feature.Services.Tips;
using JetBrains.ReSharper.PostfixTemplates.Settings;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Impl;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExpectedTypes;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve.Filters;
using JetBrains.ReSharper.Psi.Pointers;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Services;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.TextControl;
using JetBrains.Util;

// todo: caret placement for void methods? after ;? formatting?

namespace JetBrains.ReSharper.PostfixTemplates.CodeCompletion
{
  [Language(typeof(CSharpLanguage))]
  public class CSharpStaticMethodsItemProvider : CSharpItemsProviderBase<CSharpCodeCompletionContext>
  {
    protected override bool IsAvailable(CSharpCodeCompletionContext context)
    {
      var completionType = context.BasicContext.CodeCompletionType;
      return completionType == CodeCompletionType.AutomaticCompletion
          || completionType == CodeCompletionType.BasicCompletion;
    }

    protected override bool AddLookupItems(CSharpCodeCompletionContext context,
                                           GroupedItemsCollector collector)
    {
      var referenceExpression = CommonUtils.FindReferenceExpression(context.UnterminatedContext) ??
                                CommonUtils.FindReferenceExpression(context.TerminatedContext);
      if (referenceExpression == null) return false;

      var qualifier = referenceExpression.QualifierExpression;
      if (qualifier == null) return false;

      IType qualifierType = qualifier.Type(), filterType = qualifierType;
      if (!qualifierType.IsResolved)
      {
        qualifierType = IsEnumTypeReference(qualifier);
        if (qualifierType == null) return false;

        filterType = qualifier.GetPredefinedType().Type;
      }

      var settingsStore = qualifier.GetSettingsStore();
      if (!settingsStore.GetValue(PostfixSettingsAccessor.ShowStaticMethods))
        return false;

      // prepare symbol table of suitable static methods
      var accessContext = new ElementAccessContext(qualifier);
      var declaredTypes = DeclaredTypesCollector.Accept(qualifierType);

      // collect all declared types
      var psiModule = context.PsiModule;
      var commonSymbolTable = declaredTypes.Aggregate(
        seed: EmptySymbolTable.INSTANCE,
        func: (table, type) => table.Merge(type.GetSymbolTable(psiModule)));

      var filteredSymbolTable = commonSymbolTable.Filter(
        new SuitableStaticMethodsFilter(filterType, qualifier),
        OverriddenFilter.INSTANCE, new AccessRightsFilter(accessContext));

      var innerCollector = new GroupedItemsCollector();
      GetLookupItemsFromSymbolTable(filteredSymbolTable, innerCollector, context, false);

      // decorate static lookup elements
      foreach (var item in innerCollector.Items)
      {
        var lookupItem = item as DeclaredElementLookupItem;
        if (lookupItem == null) continue;

        lookupItem.TextColor = SystemColors.GrayText;
        SubscribeAfterComplete(lookupItem);
        collector.AddToBottom(lookupItem);
      }

      return true;
    }

    private static void SubscribeAfterComplete([NotNull] DeclaredElementLookupItem lookupItem)
    {
      // sorry, ugly as fuck :(
      lookupItem.AfterComplete += (ITextControl textControl, ref TextRange range,
                                   ref TextRange decoration, TailType tailType,
                                   ref Suffix suffix, ref IRangeMarker marker) =>
      {
        var solution = lookupItem.Solution;
        var psiServices = solution.GetPsiServices();
        psiServices.CommitAllDocuments();

        var methods = GetAllMethods(lookupItem);
        if (methods.Count == 0) return;

        var ownerType = methods[0].GetContainingType().NotNull();

        var hasMultipleParams = HasMultipleParameters(methods);
        if (!hasMultipleParams) // put caret 'foo(arg){here};'
        {
          var documentRange = new DocumentRange(textControl.Document, decoration);
          marker = documentRange.EndOffsetRange().CreateRangeMarker();
          // todo: review formatting here + check void return type?
        }

        var referenceExpression = TextControlToPsi
          .GetElement<IReferenceExpression>(solution, textControl.Document, range.StartOffset);
        if (referenceExpression == null) return;

        // 'remember' qualifier textually
        var qualifierExpression = referenceExpression.QualifierExpression.NotNull();
        var qualifierText = qualifierExpression.GetText();
        var referencePointer = referenceExpression.CreateTreeElementPointer();

        if (IsEnumTypeReference(qualifierExpression) != null)
        {
          qualifierText = "typeof(" + qualifierText + ")";
        }

        if (FirstArgumentAlwaysByRef(methods)) qualifierText = "ref " + qualifierText;
        if (HasOnlyMultipleParameters(methods)) qualifierText += ", ";

        var parenthesisRange = decoration.SetStartTo(range.EndOffset);
        var parenthesisMarker = parenthesisRange.CreateRangeMarker(textControl.Document);

        // insert qualifier as first argument
        var argPosition = TextRange.FromLength(decoration.EndOffset - (parenthesisRange.Length/2), 0);
        textControl.Document.ReplaceText(argPosition, qualifierText);

        // replace qualifier with type (predefined/user type)
{'fraction_non_alphanumeric': 0.08191515348912955, 'fraction_numerical': 0.0007328393453301848, 'mean_word_length': 3.576005961251863, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 2, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7480371', 'n_tokens_mistral': 3227, 'n_tokens_neox': 3125, 'n_words': 678}