@Nullable
  private CompletionVariant computeNextVariant(final Editor editor,
                                               @Nullable final String prefix,
                                               @Nullable CompletionVariant lastProposedVariant,
                                               final CompletionData data,
                                               PsiFile file,
                                               boolean includeWordsFromOtherFiles,
                                               boolean weAlreadyDoBestAttempt
  ) {
    final List<CompletionVariant> variants = computeVariants(editor, new CamelHumpMatcher(StringUtil.notNullize(prefix)), file, includeWordsFromOtherFiles);
    if (variants.isEmpty()) {
      return weAlreadyDoBestAttempt ? null:computeNextVariant(editor, prefix, null, data, file, !includeWordsFromOtherFiles, true);
    }

    if (lastProposedVariant != null) { // intern lastProposedVariant
      for (CompletionVariant variant : variants) {
        if (variant.variant.equals(lastProposedVariant.variant)) {
          if (lastProposedVariant.offset > data.startOffset && variant.offset > data.startOffset) lastProposedVariant = variant;
          if (lastProposedVariant.offset < data.startOffset && variant.offset < data.startOffset) lastProposedVariant = variant;
          if (includeWordsFromOtherFiles && lastProposedVariant.editor == variant.editor) lastProposedVariant = variant;
        }
      }
    }


    if (lastProposedVariant == null) {
      CompletionVariant result = null;

      if (myForward) {
        if (includeWordsFromOtherFiles) {
          return variants.get(variants.size() - 1);
        }
        for (CompletionVariant variant : variants) {
          if (variant.offset < data.startOffset) {
            result = variant;
          }
          else if (result == null) {
            result = variant;
            break;
          }
        }
      }
      else {
        if (includeWordsFromOtherFiles) {
          return variants.get(0);
        }
        for (CompletionVariant variant : variants) {
          if (variant.offset > data.startOffset) {
            return variant;
          }
        }

        return variants.iterator().next();
      }

      return result;
    }


    if (myForward) {
      CompletionVariant result = null;
      for (CompletionVariant variant : variants) {
        if (variant == lastProposedVariant) {
          if (result == null) {
            return computeNextVariant(editor, prefix, null, data, file, !includeWordsFromOtherFiles, true);
          }
          return result;
        }
        result = variant;
      }

      return variants.get(variants.size() - 1);
    }
    else {
      for (Iterator<CompletionVariant> i = variants.iterator(); i.hasNext();) {
        CompletionVariant variant = i.next();
        if (variant == lastProposedVariant) {
          if (i.hasNext()) {
            return i.next();
          }
          else {
            return computeNextVariant(editor, prefix, null, data, file, !includeWordsFromOtherFiles, true);
          }
        }
      }

    }

    return null;
  }
@Nullable
  private CompletionVariant computeNextVariant(final Editor editor,
                                               @Nullable final String prefix,
                                               @Nullable CompletionVariant lastProposedVariant,
                                               final CompletionData data,
                                               PsiFile file,
                                               boolean includeWordsFromOtherFiles,
                                               boolean weAlreadyDoBestAttempt
  )