package com.sleekbyte.tailor.listeners;

import com.sleekbyte.tailor.common.Location;
import com.sleekbyte.tailor.common.MaxLengths;
import com.sleekbyte.tailor.common.Messages;
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;

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

    private static final String SUPPRESS_VIOLATIONS = "// tailor:disable";
    private Printer printer;
    private File inputFile;
    private MaxLengths maxLengths;
    private LineNumberReader reader;
    private int numOfLines = 0;

    /**
     * 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 maxLengths the restrictions for maximum lengths
     */
    public FileListener(Printer printer, File inputFile, MaxLengths maxLengths) throws IOException {
        this.printer = printer;
        this.inputFile = inputFile;
        this.maxLengths = maxLengths;
        this.reader = new LineNumberReader(Files.newBufferedReader(inputFile.toPath()));
    }

    @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.endsWith(SUPPRESS_VIOLATIONS)) {
                this.printer.ignoreLine(lineNumber);
            }

            if (SourceFileUtil.lineTooLong(lineLength, this.maxLengths.maxLineLength)) {
                lineLengthViolation(lineNumber, lineLength);
            }
            if (SourceFileUtil.lineHasTrailingWhitespace(lineLength, line)) {
                trailingWhitespaceViolation(lineNumber, lineLength);
            }
        }

        verifyFileLength();
        verifyNewlineTerminated();
        verifyNoLeadingWhitespace();
    }

    private void lineLengthViolation(int lineNumber, int lineLength) {
        String lengthVersusLimit = " (" + lineLength + "/" + this.maxLengths.maxLineLength + ")";
        // Mark error on first character beyond limit
        Location location = new Location(lineNumber, this.maxLengths.maxLineLength + 1);
        this.printer.error(Messages.LINE + Messages.EXCEEDS_CHARACTER_LIMIT + lengthVersusLimit, location);
    }

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

    private void verifyFileLength() {
        if (SourceFileUtil.fileTooLong(this.numOfLines, this.maxLengths.maxFileLength)) {
            String lengthVersusLimit = " (" + this.numOfLines + "/" + this.maxLengths.maxFileLength + ")";
            // Mark error on first line beyond limit
            Location location = new Location(this.maxLengths.maxFileLength + 1);
            this.printer.error(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(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(Messages.FILE + Messages.LEADING_WHITESPACE, location);
        }
    }

    int getNumOfLines() {
        return numOfLines;
    }

}

@@ -511,35 +511,31 @@
  String 
-SUPPRESS_VIOLATIONS
+DISABLE_PATTERN
  = %22// t
@@ -1977,16 +1977,23 @@
 f (line.
+trim().
 endsWith
@@ -1997,27 +1997,23 @@
 ith(
-SUPPRESS_VIOLATIONS
+DISABLE_PATTERN
 )) %7B

Allow whitespace following DISABLE_PATTERN