package com.sleekbyte.tailor.output;

import com.sleekbyte.tailor.common.Location;
import com.sleekbyte.tailor.common.Rules;
import com.sleekbyte.tailor.common.Severity;
import com.sleekbyte.tailor.format.Formatter;
import com.sleekbyte.tailor.utils.Pair;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Generates and outputs formatted analysis messages for Xcode.
 */
public final class Printer implements Comparable<Printer> {

    private File inputFile;
    private Severity maxSeverity;
    private Formatter formatter;
    private Map<String, ViolationMessage> msgBuffer = new HashMap<>();
    private Set<Pair<Integer, Integer>> ignoredRegions = new HashSet<>();
    private boolean shouldPrintParseErrorMessage = false;

    /**
     * Constructs a printer for the specified input file, maximum severity, and color setting.
     *
     * @param inputFile The source file to verify
     * @param maxSeverity The maximum severity of any emitted violation messages
     * @param formatter Format to print in
     */
    public Printer(File inputFile, Severity maxSeverity, Formatter formatter) {
        this.inputFile = inputFile;
        this.maxSeverity = maxSeverity;
        this.formatter = formatter;
    }

    /**
     * Prints warning message.
     *
     * @param rule Rule associated with warning
     * @param warningMsg Warning message to print
     * @param location Location object containing line and column number for printing
     */
    public void warn(Rules rule, String warningMsg, Location location) {
        print(rule, Severity.WARNING, warningMsg, location);
    }

    public void warn(String warningMsg, Location location) {
        print(null, Severity.WARNING, warningMsg, location);
    }

    /**
     * Prints error message.
     *
     * @param rule Rule associated with error
     * @param errorMsg Error message to print
     * @param location Location object containing line and column number for printing
     */
    public void error(Rules rule, String errorMsg, Location location) {
        print(rule, Severity.min(Severity.ERROR, maxSeverity), errorMsg, location);
    }

    public void error(String errorMsg, Location location) {
        print(null, Severity.min(Severity.ERROR, maxSeverity), errorMsg, location);
    }

    // Visible for testing only
    public static String genOutputStringForTest(Rules rule, String filePath, int line, Severity severity, String msg) {
        return new ViolationMessage(rule, filePath, line, 0, severity, msg).toString();
    }

    // Visible for testing only
    public static String genOutputStringForTest(String filePath, int line, int column, Severity severity, String msg) {
        return new ViolationMessage(filePath, line, column, severity, msg).toString();
    }

    // Visible for testing only
    public static String genOutputStringForTest(Rules rule, String filePath, int line, int column, Severity severity,
                                                String msg) {
        return new ViolationMessage(rule, filePath, line, column, severity, msg).toString();
    }

    public List<ViolationMessage> getViolationMessages() {
        return new ArrayList<>(this.msgBuffer.values());
    }

    /**
     * Calls formatter to display all violation or error messages.
     *
     * @throws IOException if formatter cannot retrieve canonical path from inputFile
     */
    public void printAllMessages() throws IOException {
        if (shouldPrintParseErrorMessage) {
            printParseErrorMessage();
        } else {
            List<ViolationMessage> outputList = getViolationMessages().stream()
                .filter(this::filterIgnoredRegions).collect(Collectors.toList());

            Collections.sort(outputList);
            formatter.displayViolationMessages(outputList, inputFile);
        }
    }

    public long getNumErrorMessages() {
        return getNumMessagesWithSeverity(Severity.ERROR);
    }

    public long getNumWarningMessages() {
        return getNumMessagesWithSeverity(Severity.WARNING);
    }

    /**
     * Suppress analysis output for a given region.
     *
     * @param start line number where the region begins
     * @param end line number where the region ends
     */
    public void ignoreRegion(int start, int end) {
        this.ignoredRegions.add(new Pair<>(start, end));
    }

    public void setShouldPrintParseErrorMessage(boolean shouldPrintError) {
        shouldPrintParseErrorMessage = shouldPrintError;
    }

    /**
     * Print all rules along with their descriptions to STDOUT.
     */
    public static void printRules() {
        Rules[] rules = Rules.values();

        AnsiConsole.out.println(Ansi.ansi().render(String.format("@|bold %d rules available|@%n", rules.length)));
        for (Rules rule : rules) {
            AnsiConsole.out.println(Ansi.ansi().render(String.format("@|bold %s|@%n"
                + "@|underline Description:|@ %s%n"
                + "@|underline Style Guide:|@ %s%n", rule.getName(), rule.getDescription(), rule.getLink())));
        }
    }

    @Override
    public int compareTo(Printer printer) {
        return this.inputFile.compareTo(printer.inputFile);
    }

    @Override
    public boolean equals(Object printerObject) {
        if (!(printerObject instanceof Printer)) {
            return false;
        }
        return this.inputFile.equals(((Printer) printerObject).inputFile);
    }

    @Override
    public int hashCode() {
        assert false : "hashCode not designed";
        return 100;
    }

    private void print(Rules rule, Severity severity, String msg, Location location) {
        ViolationMessage violationMessage;
        if (rule != null) {
            violationMessage = new ViolationMessage(rule, location.line, location.column, severity, msg);
        } else {
            violationMessage = new ViolationMessage(location.line, location.column, severity, msg);
        }

        try {
            violationMessage.setFilePath(this.inputFile.getCanonicalPath());
        } catch (IOException e) {
            System.err.println("Error in getting canonical path of input file: " + e.getMessage());
        }
        this.msgBuffer.put(violationMessage.toString(), violationMessage);
    }

    private void printParseErrorMessage() throws IOException {
        formatter.displayParseErrorMessage(inputFile);
    }

    private long getNumMessagesWithSeverity(Severity severity) {
        return msgBuffer.values().stream()
            .filter(this::filterIgnoredRegions)
            .filter(msg -> msg.getSeverity().equals(severity)).count();
    }

    private boolean filterIgnoredRegions(ViolationMessage msg) {
        for (Pair<Integer, Integer> ignoredRegion : ignoredRegions) {
            if (ignoredRegion.getFirst() <= msg.getLineNumber()
                && msg.getLineNumber() <= ignoredRegion.getSecond()) {
                return false;
            }
        }
        return true;
    }
}

@@ -2416,159 +2416,8 @@
  %7D%0A%0A
-    public void error(String errorMsg, Location location) %7B%0A        print(null, Severity.min(Severity.ERROR, maxSeverity), errorMsg, location);%0A    %7D%0A%0A
     

Remove unused code