package org.jboss.windup.rules.files.condition.regex;

import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.github.rwitzel.streamflyer.core.AfterModification;
import com.github.rwitzel.streamflyer.regex.MatchProcessor;
import com.github.rwitzel.streamflyer.regex.MatchProcessorResult;
import com.github.rwitzel.streamflyer.regex.OnStreamMatcher;
import com.github.rwitzel.streamflyer.regex.RegexModifier;
import com.github.rwitzel.streamflyer.regex.addons.util.DoNothingProcessor;
import com.github.rwitzel.streamflyer.util.ModificationFactory;
import com.github.rwitzel.streamflyer.util.statistics.LineColumnAwareModificationFactory;

/**
 * Extends {@link RegexModifier} to support tracking the line number and column, and to fire {@link StreamRegexMatchedEvent} events when content is
 * matched by the regular expression.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
public class StreamRegexMatcher extends RegexModifier {
    private final StreamRegexMatcherProcessor streamRegexMatcherProcessor;

    public static StreamRegexMatcher create(String regex, StreamRegexMatchListener listener) {
        StreamRegexMatcherProcessor streamRegexMatcherProcessor = new StreamRegexMatcherProcessor(listener);
        return new StreamRegexMatcher(regex, 0, streamRegexMatcherProcessor);
    }

    private StreamRegexMatcher(String regex, int flags, StreamRegexMatcherProcessor streamRegexMatcherProcessor) {
        super(regex, flags, streamRegexMatcherProcessor, 2048, 2048);
        this.streamRegexMatcherProcessor = streamRegexMatcherProcessor;
    }

    @Override
    public AfterModification modify(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
                                    boolean endOfStreamHit) {
        this.streamRegexMatcherProcessor.setFirstCharIndex(firstModifiableCharacterInBuffer);
        return super.modify(characterBuffer, firstModifiableCharacterInBuffer, endOfStreamHit);
    }

    @Override
    protected void init(OnStreamMatcher matcher, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
                        int newNumberOfChars) {
        super.init(matcher, matchProcessor, minimumLengthOfLookBehind, newNumberOfChars);
        ModificationFactory delegate = new ModificationFactory(minimumLengthOfLookBehind, newNumberOfChars);
        this.factory = new LineColumnAwareModificationFactory(delegate);
        this.matchProcessor = matchProcessor;
        this.matcher = matcher;
        this.newNumberOfChars = newNumberOfChars;

        ((StreamRegexMatcherProcessor) matchProcessor).setLineColumnAwareModificationFactory((LineColumnAwareModificationFactory) this.factory);
    }

    private static class StreamRegexMatcherProcessor extends DoNothingProcessor implements MatchProcessor {
        private int firstCharIndex;
        private final StreamRegexMatchListener listener;
        private LineColumnAwareModificationFactory lineColumnAwareModificationFactory;

        public StreamRegexMatcherProcessor(StreamRegexMatchListener listener) {
            this.listener = listener;
        }

        public void setLineColumnAwareModificationFactory(LineColumnAwareModificationFactory lineColumnAwareModificationFactory) {
            this.lineColumnAwareModificationFactory = lineColumnAwareModificationFactory;
        }

        public void setFirstCharIndex(int firstCharIndex) {
            this.firstCharIndex = firstCharIndex;
        }

        @Override
        public MatchProcessorResult process(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer, MatchResult matchResult) {
            long unmatchedStartLine = lineColumnAwareModificationFactory.getCurrentLine();
            long unmatchedStartColumn = lineColumnAwareModificationFactory.getCurrentColumn();
            int unmatchedStart = firstCharIndex;
            int unmatchedEnd = matchResult.start();
            String unmatched = characterBuffer.substring(unmatchedStart, unmatchedEnd);

            Matcher matcher = Pattern.compile("\r\n|\r|\n").matcher(unmatched);
            int numLines = 0;
            int endOfLastLineBreak = 0;
            while (matcher.find()) {
                numLines++;
                endOfLastLineBreak = matcher.end();
            }
            long lineNumber = unmatchedStartLine + numLines;
            long columnNumber;
            if (numLines == 0) {
                columnNumber = unmatchedStartColumn + unmatched.length();
            } else {
                columnNumber = unmatched.length() - endOfLastLineBreak; // length of last line in 'unmatched'
            }

            String matchText = matchResult.group();
            StreamRegexMatchedEvent event = new StreamRegexMatchedEvent(matchText, lineNumber, columnNumber);
            listener.regexMatched(event);
            return super.process(characterBuffer, firstModifiableCharacterInBuffer, matchResult);
        }
    }
}
