Performs protein grouping based only on protein to peptide mappings.

    :param protToPeps: dict, for each protein (=key) contains a set of
        associated peptides (=value). For Example {protein: {peptide, ...}, ...}

    #TODO: REFACTORING!!!

    returns a ProteinInference object

def mappingBasedGrouping(protToPeps):
    """Performs protein grouping based only on protein to peptide mappings.

    :param protToPeps: dict, for each protein (=key) contains a set of
        associated peptides (=value). For Example {protein: {peptide, ...}, ...}

    #TODO: REFACTORING!!!

    returns a ProteinInference object
    """
    inference = ProteinInference(protToPeps)
    pepToProts = inference.pepToProts

    proteinClusters = _findProteinClusters(protToPeps, pepToProts)
    proteins = {}
    for clusterId, proteinCluster in enumerate(proteinClusters, 1):
        clusterProtToPeps = {p: protToPeps[p] for p in proteinCluster}

        #Find sameset proteins, define unique and non unique sameset proteins
        #NOTE: already unique proteins could be excluded to find sameset proteins
        samesetProteins = _findSamesetProteins(clusterProtToPeps)
        mergedProtToPeps = _mergeProteinEntries(samesetProteins,
                                                clusterProtToPeps)
        mergedPepToProts = _invertMapping(mergedProtToPeps)
        uniqueProteins = _findUniqueMappingValues(mergedPepToProts)
        remainingProteins = set(mergedProtToPeps).difference(uniqueProteins)

        # Remove subset proteins and check if remaining proteins become unique
        subsetProteinInfo = _findSubsetProteins(remainingProteins,
                                                mergedProtToPeps,
                                                mergedPepToProts)
        subsetProteins = [p for p, _ in subsetProteinInfo]
        subsetRemovedProtToPeps = _reducedProtToPeps(mergedProtToPeps,
                                                     subsetProteins)
        subsetRemovedPepToProts = _invertMapping(subsetRemovedProtToPeps)
        uniqueSubsetRemoved = _findUniqueMappingValues(subsetRemovedPepToProts)
        remainingProteins = remainingProteins.difference(subsetProteins)
        remainingProteins = remainingProteins.difference(uniqueSubsetRemoved)

        # Find redundant proteins #
        subsumableProteins = _findRedundantProteins(subsetRemovedProtToPeps,
                                                    subsetRemovedPepToProts)
        remainingNonRedundant = remainingProteins.difference(subsumableProteins)
        groupInitiatingProteins = uniqueSubsetRemoved.union(remainingNonRedundant)

        # - Generate protein groups and assign proteins to groups - #
        #Generate protein groups
        clusterGroupIds = set()
        for protein in groupInitiatingProteins:
            proteinIds = AUX.toList(protein)

            groupId = inference.addProteinGroup(proteinIds[0])
            inference.addLeadingToGroups(proteinIds, groupId)
            clusterGroupIds.add(groupId)

        #Add redundant proteins here (must be subsumable I guess)
        for protein in subsumableProteins:
            proteinIds = AUX.toList(protein)

            connectedProteins = _mappingGetValueSet(
                mergedPepToProts, mergedProtToPeps[protein]
            )
            flatConnectedProteins = _flattenMergedProteins(connectedProteins)
            groupIds = _mappingGetValueSet(
                inference._proteinToGroupIds, flatConnectedProteins
            )
            inference.addSubsumableToGroups(proteinIds, groupIds)
            assert len(groupIds) > 1

        #Add subgroup proteins to the respective groups
        #NOTE: proteins that are only a subset of subsumable proteins are not
        #to be added as subset proteins to a group but as subsumable proteins.
        for protein, supersetProteins in subsetProteinInfo:
            proteinIds = AUX.toList(protein)

            #If the protein is a subset of at least one protein, that is not a
            #subsumable protein, then it should be added to the group as subset.
            leadingSuperProteins = supersetProteins.intersection(
                                                    groupInitiatingProteins)
            if leadingSuperProteins:
                flatSupersetProteins = _flattenMergedProteins(
                                                    leadingSuperProteins)
                superGroupIds = _mappingGetValueSet(
                    inference._proteinToGroupIds, flatSupersetProteins
                )
                inference.addSubsetToGroups(proteinIds, superGroupIds)
            #However, if all its super proteins are subsumable, the protein
            #itself is a subsumable protein.
            else:
                flatSupersetProteins = _flattenMergedProteins(supersetProteins)
                superGroupIds = _mappingGetValueSet(
                    inference._proteinToGroupIds, flatSupersetProteins
                )
                inference.addSubsumableToGroups(proteinIds, superGroupIds)
                subsumableProteins.update(proteinIds)
            assert superGroupIds

        # - Define peptide properties - #
        groupToPeps = dict()
        allSubsumablePeps = set()
        for groupId in clusterGroupIds:
            group = inference.groups[groupId]
            if group.subsumableProteins:
                subsumablePeptides = _mappingGetValueSet(
                    protToPeps, group.subsumableProteins
                )
                allSubsumablePeps.update(subsumablePeptides)

            groupPeptides = _mappingGetValueSet(protToPeps, group.proteins)
            groupToPeps[groupId] = groupPeptides
        pepToGroups = _invertMapping(groupToPeps)

        #Get unique peptides from peptide to protein mapping
        uniquePeptides = _findUniqueMappingKeys(mergedPepToProts)
        #Shared peptides have a groupPeptideCount > 1
        nonSharedPeptides = _findUniqueMappingKeys(pepToGroups)
        sharedPeptides = set(pepToGroups).difference(nonSharedPeptides)
        #Subsumable peptides are peptides from subsumable proteins that
        #are not shared peptides of multiple groups
        subsumablePeptides = allSubsumablePeps.difference(sharedPeptides)
        #groupUniquePeptides are the remaining ones (not shared with subsumable
        #proteins, groupPeptideCount == 1, not unique peptides)
        groupUniquePeptides = nonSharedPeptides.difference(subsumablePeptides)
        groupUniquePeptides = groupUniquePeptides.difference(uniquePeptides)

        inference._uniquePeptides.update(uniquePeptides)
        inference._groupUniquePeptides.update(groupUniquePeptides)
        inference._groupSubsumablePeptides.update(subsumablePeptides)
        inference._sharedPeptides.update(sharedPeptides)

        # - Generate protein entries and add them to the inference object - #
        subsetProteinInfoDict = dict(subsetProteinInfo)
        for protein, peptides in viewitems(mergedProtToPeps):
            _uniquePeptides = peptides.intersection(uniquePeptides)
            _groupUniquePeptides = peptides.intersection(groupUniquePeptides)
            _subsumablePeptides = peptides.intersection(subsumablePeptides)
            _sharedPeptides = peptides.intersection(sharedPeptides)
            proteinIds = AUX.toList(protein)
            for proteinId in proteinIds:
                proteinEntry = Protein(proteinId, peptides)
                if protein in groupInitiatingProteins:
                    proteinEntry.isLeading = True
                elif protein in subsumableProteins:
                    proteinEntry.isSubsumable = True
                if protein in subsetProteins:
                    superset = subsetProteinInfoDict[protein]
                    proteinEntry.isSubset = _flattenMergedProteins(superset)
                if len(proteinIds) > 1:
                    proteinEntry.isSameset = set(proteinIds)
                inference.proteins[proteinId] = proteinEntry

                #Add peptides to protein entry
                proteinEntry.uniquePeptides = _uniquePeptides
                proteinEntry.groupUniquePeptides = _groupUniquePeptides
                proteinEntry.groupSubsumablePeptides = _subsumablePeptides
                proteinEntry.sharedPeptides = _sharedPeptides

        # - Save cluster information - #
        for proteinId in proteinCluster:
            inference._proteinToClusterId[proteinId] = clusterId
        inference.clusters[clusterId] = clusterGroupIds

    allProteins = set()
    for proteinGroup in viewvalues(inference.groups):
        allProteins.update(proteinGroup.proteins)
        allProteins.update(proteinGroup.subsumableProteins)
    assert len(allProteins) == len(protToPeps)
    return inference