/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.cpd;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class MatchCollector {
    private List<Match> matchList = new ArrayList<>();
    private Map<Integer, Map<Integer, Match>> matchTree = new TreeMap<>();
    private MatchAlgorithm ma;

    public MatchCollector(MatchAlgorithm ma) {
        this.ma = ma;
    }

    public void collect(List<TokenEntry> marks) {
        // first get a pairwise collection of all maximal matches
        for (int i = 0; i < marks.size() - 1; i++) {
            TokenEntry mark1 = marks.get(i);
            for (int j = i + 1; j < marks.size(); j++) {
                TokenEntry mark2 = marks.get(j);
                int diff = mark1.getIndex() - mark2.getIndex();
                if (-diff < ma.getMinimumTileSize()) {
                    continue;
                }
                if (hasPreviousDupe(mark1, mark2)) {
                    continue;
                }

                // "match too small" check
                int dupes = countDuplicateTokens(mark1, mark2);
                if (dupes < ma.getMinimumTileSize()) {
                    continue;
                }
                // is it still too close together
                if (diff + dupes >= 1) {
                    continue;
                }
                reportMatch(mark1, mark2, dupes);
            }
        }
    }

    private void reportMatch(TokenEntry mark1, TokenEntry mark2, int dupes) {
        Map<Integer, Match> matches = matchTree.get(dupes);
        if (matches == null) {
            matches = new TreeMap<>();
            matchTree.put(dupes, matches);
            addNewMatch(mark1, mark2, dupes, matches);
        } else {
            Match matchA = matchTree.get(dupes).get(mark1.getIndex());
            Match matchB = matchTree.get(dupes).get(mark2.getIndex());

            if (matchA == null && matchB == null) {
                addNewMatch(mark1, mark2, dupes, matches);
            } else if (matchA == null) {
                matchB.addTokenEntry(mark1);
                matches.put(mark1.getIndex(), matchB);
            } else if (matchB == null) {
                matchA.addTokenEntry(mark2);
                matches.put(mark2.getIndex(), matchA);
            }
        }
    }

    private void addNewMatch(TokenEntry mark1, TokenEntry mark2, int dupes, Map<Integer, Match> matches) {
        Match match = new Match(dupes, mark1, mark2);
        matches.put(mark1.getIndex(), match);
        matches.put(mark2.getIndex(), match);
        matchList.add(match);
    }

    @SuppressWarnings("PMD.CompareObjectsWithEquals")
    public List<Match> getMatches() {
        Collections.sort(matchList);
        return matchList;
    }

    private boolean hasPreviousDupe(TokenEntry mark1, TokenEntry mark2) {
        return mark1.getIndex() != 0 && !matchEnded(ma.tokenAt(-1, mark1), ma.tokenAt(-1, mark2));
    }

    private int countDuplicateTokens(TokenEntry mark1, TokenEntry mark2) {
        int index = 0;
        while (!matchEnded(ma.tokenAt(index, mark1), ma.tokenAt(index, mark2))) {
            index++;
        }
        return index;
    }

    private boolean matchEnded(TokenEntry token1, TokenEntry token2) {
        return token1.getIdentifier() != token2.getIdentifier()
                || TokenEntry.EOF.equals(token1)
                || TokenEntry.EOF.equals(token2);
    }
}
