class LingoClusteringAlgorithm extends AttrComposite implements ClusteringAlgorithm {

  @Override
  public <T extends Document> List<Cluster<T>> cluster(
      Stream<? extends T> docStream, LanguageComponents languageComponents) {
    List<T> documents = docStream.collect(Collectors.toList());

    // Preprocessing of documents
    final PreprocessingContext context =
        preprocessing.preprocess(documents.stream(), queryHint.get(), languageComponents);

    // Further processing only if there are words to process
    List<Cluster<T>> clusters = new ArrayList<>();
    if (context.hasLabels()) {
      // Term-document matrix building and reduction
      final VectorSpaceModelContext vsmContext = new VectorSpaceModelContext(context);
      final ReducedVectorSpaceModelContext reducedVsmContext =
          new ReducedVectorSpaceModelContext(vsmContext);
      LingoProcessingContext lingoContext = new LingoProcessingContext(reducedVsmContext);

      TermDocumentMatrixBuilder matrixBuilder = this.matrixBuilder;
      matrixBuilder.buildTermDocumentMatrix(vsmContext);
      matrixBuilder.buildTermPhraseMatrix(vsmContext);

      matrixReducer.reduce(
          reducedVsmContext, computeClusterCount(desiredClusterCount.get(), documents.size()));

      // Cluster label building
      clusterBuilder.buildLabels(lingoContext, matrixBuilder.termWeighting);

      // Document assignment
      clusterBuilder.assignDocuments(lingoContext);

      // Cluster merging
      clusterBuilder.merge(lingoContext);

      // Format final clusters
      final LabelFormatter labelFormatter =
          lingoContext.preprocessingContext.languageComponents.get(LabelFormatter.class);

      final int[] clusterLabelIndex = lingoContext.clusterLabelFeatureIndex;
      final BitSet[] clusterDocuments = lingoContext.clusterDocuments;
      final double[] clusterLabelScore = lingoContext.clusterLabelScore;
      for (int i = 0; i < clusterLabelIndex.length; i++) {
        final Cluster<T> cluster = new Cluster<>();

        final int labelFeature = clusterLabelIndex[i];
        if (labelFeature < 0) {
          // Cluster removed during merging
          continue;
        }

        // Add label and score
        cluster.addLabel(context.format(labelFormatter, labelFeature));
        cluster.setScore(clusterLabelScore[i]);

        // Add documents
        final BitSet bs = clusterDocuments[i];
        for (int bit = bs.nextSetBit(0); bit >= 0; bit = bs.nextSetBit(bit + 1)) {
          cluster.addDocument(documents.get(bit));
        }

        // Add cluster
        clusters.add(cluster);
      }
    }

    clusters = SharedInfrastructure.reorderByWeightedScoreAndSize(clusters, this.scoreWeight.get());
    return clusters;
  }

}