class BranchAwareSmellTypeAnalysis implements Query {

    @Override
    public void query() throws QueryException {
        Smell smell;
        Commit previousCommit;
        Commit commit = Commit.empty();
        Integer previousBranch;
        Integer currentBranch = -1;

        Map<String, Object> instance;
        while (smells.hasNext()) {
            instance = smells.next();
            previousCommit = commit;
            previousBranch = currentBranch;
            commit = Commit.fromInstance(instance);
            smell = Smell.fromPaprikaInstance(instance, smellType);
            try {
                currentBranch = fetchCommitBranch(commit);
            } catch (BranchNotFoundException e) {
                logger.warn("[" + projectId + "] ==> Unable to guess branch for commit (" + commit.sha + "), skipping", e.getMessage());
                continue;
            }

            // We create the new BranchAnalyzer if needed.
            if (!branchAnalyzers.containsKey(currentBranch)) {
                logger.debug("[" + projectId + "] => Initializing new branch: " + currentBranch);
                initializeBranch(currentBranch);
            }

            // We set the commit ordinal, branch-wise to enable our BranchAnalyzer
            // to correctly handle gaps.
            commit.setBranchOrdinal(fetchCommitOrdinal(currentBranch, commit));
            branchAnalyzers.get(currentBranch).notifyCommit(commit);

            // On commit change, we ensure to merge SmellPresence from the merged commit if necessary.
            if (!previousCommit.equals(commit)) {
                synchronizeMergeSmells(commit, currentBranch);
            }

            // Once the previous Smells are all set, notify our newly found smell.
            branchAnalyzers.get(currentBranch).notifySmell(smell);

            // When we are sure that we passed the last branch commit, we will finalize the branch analysis,
            // i.e. setting introductions and refactoring for the last branch commit.
            if (!previousCommit.equals(commit) && isLastBranchCommit(previousCommit, previousBranch)) {
                finalizeBranch(previousBranch);
                branchAnalyzers.remove(previousBranch);
            }
        }

        // We should only perform operations for branch 0 since all other commits are looped around.
        // On top of that, we may have missed some branch finalization because of lost commits.
        for (int branchId : branchAnalyzers.keySet()) {
            finalizeBranch(branchId);
        }
    }

}

class BranchAwareSmellTypeAnalysisTest  {

    @Test
    public void testHandleRenamedSmellMultipleCommits() throws QueryException {

        ArgumentCaptor<Smell> smellCaptor = ArgumentCaptor.forClass(Smell.class);
        addSmell(firstCommit, firstSmell);
        addSmell(secondCommit, secondSmell);
        addSmell(thirdCommit, secondSmell);
        mockCommitBranch(firstCommit, 0, 0);
        mockCommitBranch(secondCommit, 0, 1);
        mockCommitBranch(thirdCommit, 0, 2);

        // This means that the firstSmell instance has been renamed to second smell in the secondCommit
        Smell expectedSecondSmell = mockSmellRenamed(secondCommit, secondSmell, firstSmell);
        mockSmellId(expectedSecondSmell);
        mockSmellRenamed(thirdCommit, secondSmell, firstSmell);
        mockLastBranchCommit(0, thirdCommit.sha);

        getAnalysis().query();
        debugSmellInsertions();

        verify(persistence, times(2)).execute(any());
        verify(smellQueries).smellInsertionStatement(projectId, firstSmell);
        // We introduce the new smell instance definition with renamed_from filled in.
        // Since we use a captor we have to check all invocations of smellInsertionStatement...
        verify(smellQueries, times(2)).smellInsertionStatement(eq(projectId), smellCaptor.capture());
        // Check that the renamed commit has a set parent
        Smell renamed = smellCaptor.getAllValues().get(1);
        assertEquals(expectedSecondSmell, renamed);
        assertEquals(firstSmell, renamed.parent);

        verify(persistence, times(4)).addStatements(any());
        verify(smellQueries).smellCategoryInsertionStatement(projectId, firstCommit.sha, firstSmell, SmellCategory.PRESENCE);
        verify(smellQueries).smellCategoryInsertionStatement(projectId, firstCommit.sha, firstSmell, SmellCategory.INTRODUCTION);

        verify(smellQueries).smellCategoryInsertionStatement(projectId, secondCommit.sha, expectedSecondSmell, SmellCategory.PRESENCE);
        verify(smellQueries).smellCategoryInsertionStatement(projectId, thirdCommit.sha, expectedSecondSmell, SmellCategory.PRESENCE);
    }

}