package com.sleekbyte.tailor.listeners;

import com.sleekbyte.tailor.common.ConstructLengths;
import com.sleekbyte.tailor.common.Location;
import com.sleekbyte.tailor.common.Messages;
import com.sleekbyte.tailor.common.Rules;
import com.sleekbyte.tailor.output.Printer;
import com.sleekbyte.tailor.utils.SourceFileUtil;

import java.io.File;
import java.io.IOException;
import java.io.LineNumberReader;
import java.nio.file.Files;
import java.util.Set;

/**
 * Listener for verifying source files.
 */
public final class FileListener implements AutoCloseable {

    private static final String DISABLE_PATTERN = "// tailor:disable";
    private Printer printer;
    private File inputFile;
    private ConstructLengths constructLengths;
    private LineNumberReader reader;
    private int numOfLines = 0;
    private Set<Rules> enabledRules;

    /**
     * Constructs a file listener with the specified printer, input file, and max lengths restrictions.
     *
     * @param printer    the printer to use for displaying violation messages
     * @param inputFile  the source file to verify
     * @param constructLengths the restrictions for maximum lengths
     */
    public FileListener(Printer printer, File inputFile, ConstructLengths constructLengths, Set<Rules> enabledRules)
        throws IOException {
        this.printer = printer;
        this.inputFile = inputFile;
        this.constructLengths = constructLengths;
        this.reader = new LineNumberReader(Files.newBufferedReader(inputFile.toPath()));
        this.enabledRules = enabledRules;
    }

    @Override
    public void close() throws IOException {
        this.reader.close();
    }

    /**
     * Verify that all source file specific rules are satisfied.
     */
    public void verify() throws IOException {
        int lineLength;
        int lineNumber;
        for (String line = this.reader.readLine(); line != null; line = this.reader.readLine()) {
            lineLength = line.length();
            lineNumber = this.reader.getLineNumber();
            // Count the number of lines in a file
            this.numOfLines++;

            // Suppress all violations on lines ending with the given pattern
            if (line.trim().endsWith(DISABLE_PATTERN)) {
                this.printer.ignoreLine(lineNumber);
            }

            if (enabledRules.contains(Rules.MAX_LINE_LENGTH)
                && SourceFileUtil.lineTooLong(lineLength, this.constructLengths.maxLineLength)) {
                lineLengthViolation(lineNumber, lineLength, this.constructLengths.maxLineLength, Rules.MAX_LINE_LENGTH,
                    Messages.EXCEEDS_CHARACTER_LIMIT);
            }

            if (enabledRules.contains(Rules.TRAILING_WHITESPACE)
                    && SourceFileUtil.lineHasTrailingWhitespace(lineLength, line)) {
                trailingWhitespaceViolation(lineNumber, lineLength);
            }
        }

        if (enabledRules.contains(Rules.MAX_FILE_LENGTH)) {
            verifyFileLength();
        }

        if (enabledRules.contains(Rules.TERMINATING_NEWLINE)) {
            verifyNewlineTerminated();
        }

        if (enabledRules.contains(Rules.LEADING_WHITESPACE)) {
            verifyNoLeadingWhitespace();
        }
    }

    private void lineLengthViolation(int lineNumber, int lineLength, int lengthLimit,  Rules rule, String msg) {
        String lengthVersusLimit = " (" + lineLength + "/" + lengthLimit + ")";
        // Mark error on first line beyond limit
        Location location = new Location(lineNumber, lengthLimit + 1);
        this.printer.error(rule, Messages.LINE + msg + lengthVersusLimit, location);
    }

    private void trailingWhitespaceViolation(int lineNumber, int lineLength) {
        Location location = new Location(lineNumber, lineLength);
        this.printer.warn(Rules.TRAILING_WHITESPACE, Messages.LINE + Messages.TRAILING_WHITESPACE, location);
    }

    private void verifyFileLength() {
        if (SourceFileUtil.fileTooLong(this.numOfLines, this.constructLengths.maxFileLength)) {
            String lengthVersusLimit = " (" + this.numOfLines + "/" + this.constructLengths.maxFileLength + ")";
            // Mark error on first line beyond limit
            Location location = new Location(this.constructLengths.maxFileLength + 1);
            this.printer.error(Rules.MAX_FILE_LENGTH, Messages.FILE + Messages.EXCEEDS_LINE_LIMIT + lengthVersusLimit,
                location);
        }
    }

    private void verifyNewlineTerminated() throws IOException {
        if (!SourceFileUtil.singleNewlineTerminated(this.inputFile)) {
            Location location = new Location(this.numOfLines);
            this.printer.error(Rules.TERMINATING_NEWLINE, Messages.FILE + Messages.NEWLINE_TERMINATOR, location);
        }
    }

    private void verifyNoLeadingWhitespace() throws IOException {
        if (SourceFileUtil.hasLeadingWhitespace(this.inputFile)) {
            Location location = new Location(1, 1);
            this.printer.warn(Rules.LEADING_WHITESPACE, Messages.FILE + Messages.LEADING_WHITESPACE, location);
        }
    }

    protected int getNumOfLines() {
        return numOfLines;
    }

}

@@ -591,24 +591,29 @@
 ing DISABLE_
+LINE_
 PATTERN = %22/
@@ -631,16 +631,206 @@
 sable%22;%0A
+    private static final String DISABLE_REGION_BEGIN_PATTERN = %22// tailor:disable-region-begin%22;%0A    private static final String DISABLE_REGION_END_PATTERN = %22// tailor:disable-region-end%22;%0A
     priv
@@ -2024,24 +2024,114 @@
 lineNumber;%0A
+        int disableBlockStartLineNumber = -1;%0A        int disableBlockEndLineNumber = -1;%0A
         for 
@@ -2409,101 +2409,635 @@
     
-// Suppress all violations on lines ending with the given pattern%0A            if (line.trim()
+String trimmedLine = line.trim();%0A            if (trimmedLine.equals(DISABLE_REGION_BEGIN_PATTERN)) %7B%0A                // Suppress all violations after the given pattern%0A                disableBlockStartLineNumber = lineNumber;%0A            %7D else if (trimmedLine.equals(DISABLE_REGION_END_PATTERN)) %7B%0A                // Suppress all violations before the given pattern%0A                disableBlockEndLineNumber = lineNumber;%0A            %7D%0A%0A            /** Suppress all violations on lines:%0A             * 1. ending with DISABLE_LINE_PATTERN%0A             * 2. inside the disable region%0A             */%0A            if (trimmedLine
 .end
@@ -3050,24 +3050,29 @@
 DISABLE_
+LINE_
 PATTERN)
 ) %7B%0A    
@@ -3055,32 +3055,136 @@
 LE_LINE_PATTERN)
+%0A                %7C%7C lineInsideRegion(disableBlockStartLineNumber, disableBlockEndLineNumber, lineNumber)
 ) %7B%0A            
@@ -4149,24 +4149,24 @@
 itespace();%0A
-
         %7D%0A  
@@ -4162,32 +4162,229 @@
        %7D%0A    %7D%0A%0A
+    private boolean lineInsideRegion(int regionStart, int regionEnd, int lineNumber) %7B%0A        return regionStart %3E -1%0A            && lineNumber %3E= regionEnd%0A            && regionEnd == -1;%0A    %7D%0A%0A
     private void

Add support for disabled/ignored regions