/*
 * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/**
 * @test
 * @bug     6769027
 * @summary Source line should be displayed immediately after the first diagnostic line
 * @author  Maurizio Cimadamore
 * @run main/othervm T6769027
 */
import java.net.URI;
import java.util.regex.Matcher;
import javax.tools.*;
import com.sun.tools.javac.util.*;

public class T6769027 {

    enum OutputKind {
        RAW("rawDiagnostics","rawDiagnostics"),
        BASIC("","");

        String key;
        String value;

        void init(Options opts) {
            opts.put(key, value);
        }

        OutputKind(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    enum CaretKind {
        DEFAULT("", ""),
        SHOW("showCaret","true"),
        HIDE("showCaret","false");

        String key;
        String value;

        void init(Options opts) {
            opts.put(key, value);
        }

        CaretKind(String key, String value) {
            this.key = key;
            this.value = value;
        }

        boolean isEnabled() {
            return this == DEFAULT || this == SHOW;
        }
    }

    enum SourceLineKind {
        DEFAULT("", ""),
        AFTER_SUMMARY("sourcePosition", "top"),
        BOTTOM("sourcePosition", "bottom");

        String key;
        String value;

        void init(Options opts) {
            opts.put(key, value);
        }

        SourceLineKind(String key, String value) {
            this.key = key;
            this.value = value;
        }

        boolean isAfterSummary() {
            return this == DEFAULT || this == AFTER_SUMMARY;
        }
    }

    enum XDiagsSource {
        DEFAULT(""),
        SOURCE("source"),
        NO_SOURCE("-source");

        String flag;

        void init(Options opts) {
            if (this != DEFAULT) {
                String flags = opts.get("diags");
                flags = flags == null ? flag : flags + "," + flag;
                opts.put("diags", flags);
            }
        }

        XDiagsSource(String flag) {
            this.flag = flag;
        }

        String getOutput(CaretKind caretKind, IndentKind indent, OutputKind outKind) {
            String spaces = (outKind == OutputKind.BASIC) ? indent.string : "";
            return "\n" + spaces + "This is a source line" +
                   (caretKind.isEnabled() ? "\n" + spaces + "     ^" : "");
        }
    }

    enum XDiagsCompact {
        DEFAULT(""),
        COMPACT("short"),
        NO_COMPACT("-short");

        String flag;

        void init(Options opts) {
            if (this != DEFAULT) {
                String flags = opts.get("diags");
                flags = flags == null ? flag : flags + "," + flag;
                opts.put("diags", flags);
            }
        }

        XDiagsCompact(String flag) {
            this.flag = flag;
        }
    }

    enum ErrorKind {
        SINGLE("single",
            "compiler.err.single: Hello!",
            "KXThis is a test error message Hello!"),
        DOUBLE("double",
            "compiler.err.double: Hello!",
            "KXThis is a test error message.\n" +
            "KXYThis is another line of the above error message Hello!");

        String key;
        String rawOutput;
        String nonRawOutput;

        String key() {
            return key;
        }

        ErrorKind(String key, String rawOutput, String nonRawOutput) {
            this.key = key;
            this.rawOutput = rawOutput;
            this.nonRawOutput = nonRawOutput;
        }

        String getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent) {
            return outKind == OutputKind.RAW ?
                rawOutput :
                nonRawOutput.replace("X", summaryIndent.string).replace("Y", detailsIndent.string).replace("K", "");
        }

        String getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent, String indent) {
            return outKind == OutputKind.RAW ?
                rawOutput :
                nonRawOutput.replace("X", summaryIndent.string).replace("Y", detailsIndent.string).replace("K", indent);
        }
    }

    enum MultilineKind {
        NONE(0),
        DOUBLE(1),
        NESTED(2),
        DOUBLE_NESTED(3);

        static String[][] rawTemplates = {
            {"", ",{(E),(E)}", ",{(E,{(E)})}", ",{(E,{(E)}),(E,{(E)})}"}, //ENABLED
            {"", "", "", "",""}, //DISABLED
            {"", ",{(E)}", ",{(E,{(E)})}", ",{(E,{(E)})}"}, //LIMIT_LENGTH
            {"", ",{(E),(E)}", ",{(E)}", ",{(E),(E)}"}, //LIMIT_DEPTH
            {"", ",{(E)}", ",{(E)}", ",{(E)}"}}; //LIMIT_BOTH

        static String[][] basicTemplates = {
            {"", "\nE\nE", "\nE\nQ", "\nE\nQ\nE\nQ"}, //ENABLED
            {"", "", "", "",""}, //DISABLED
            {"", "\nE", "\nE\nQ", "\nE\nQ"}, //LIMIT_LENGTH
            {"", "\nE\nE", "\nE", "\nE\nE"}, //LIMIT_DEPTH
            {"", "\nE", "\nE", "\nE"}}; //LIMIT_BOTH


        int index;

        MultilineKind (int index) {
            this.index = index;
        }

        boolean isDouble() {
            return this == DOUBLE || this == DOUBLE_NESTED;
        }

        boolean isNested() {
            return this == NESTED || this == DOUBLE_NESTED;
        }

        String getOutput(OutputKind outKind, ErrorKind errKind, MultilinePolicy policy,
                IndentKind summaryIndent, IndentKind detailsIndent, IndentKind multiIndent) {
            String constIndent = (errKind == ErrorKind.DOUBLE) ?
                summaryIndent.string + detailsIndent.string :
                summaryIndent.string;
            constIndent += multiIndent.string;

            String errMsg1 = errKind.getOutput(outKind, summaryIndent, detailsIndent, constIndent);
            String errMsg2 = errKind.getOutput(outKind, summaryIndent, detailsIndent, constIndent + constIndent);

            errMsg1 = errMsg1.replaceAll("compiler.err", "compiler.misc");
            errMsg1 = errMsg1.replaceAll("error message", "subdiagnostic");
            errMsg2 = errMsg2.replaceAll("compiler.err", "compiler.misc");
            errMsg2 = errMsg2.replaceAll("error message", "subdiagnostic");

            String template = outKind == OutputKind.RAW ?
                rawTemplates[policy.index][index] :
                basicTemplates[policy.index][index];

            template = template.replaceAll("E", errMsg1);
            return template.replaceAll("Q", errMsg2);
        }
    }

    enum MultilinePolicy {
        ENABLED(0, "multilinePolicy", "enabled"),
        DISABLED(1, "multilinePolicy", "disabled"),
        LIMIT_LENGTH(2, "multilinePolicy", "limit:1:*"),
        LIMIT_DEPTH(3, "multilinePolicy", "limit:*:1"),
        LIMIT_BOTH(4, "multilinePolicy", "limit:1:1");

        String name;
        String value;
        int index;

        MultilinePolicy(int index, String name, String value) {
            this.name = name;
            this.value = value;
            this.index = index;
        }

        void init(Options options) {
            options.put(name, value);
        }
    }

    enum PositionKind {
        NOPOS(Position.NOPOS, "- ", "error: "),
        POS(5, "Test.java:1:6: ", "/Test.java:1: error: ");

        int pos;
        String rawOutput;
        String nonRawOutput;

        PositionKind(int pos, String rawOutput, String nonRawOutput) {
            this.pos = pos;
            this.rawOutput = rawOutput;
            this.nonRawOutput = nonRawOutput;
        }

        JCDiagnostic.DiagnosticPosition pos() {
            return new JCDiagnostic.SimpleDiagnosticPosition(pos);
        }

        String getOutput(OutputKind outputKind) {
            return outputKind == OutputKind.RAW ?
                rawOutput :
                nonRawOutput;
        }
    }

    static class MyFileObject extends SimpleJavaFileObject {
        private String text;
        public MyFileObject(String text) {
            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
            this.text = text;
        }
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return text;
        }
    }

    enum IndentKind {
        NONE(""),
        CUSTOM("   ");

        String string;

        IndentKind(String indent) {
            string = indent;
        }
    }

    class MyLog extends Log {
        MyLog(Context ctx) {
            super(ctx);
        }

        @Override
        protected java.io.PrintWriter getWriterForDiagnosticType(JCDiagnostic.DiagnosticType dt) {
            return new java.io.PrintWriter(System.out);
        }

        @Override
        protected boolean shouldReport(JavaFileObject jfo, int pos) {
            return true;
        }
    }

    int nerrors = 0;

    void exec(OutputKind outputKind, ErrorKind errorKind, MultilineKind multiKind,
            MultilinePolicy multiPolicy, PositionKind posKind, XDiagsSource xdiagsSource,
            XDiagsCompact xdiagsCompact, CaretKind caretKind, SourceLineKind sourceLineKind,
            IndentKind summaryIndent, IndentKind detailsIndent, IndentKind sourceIndent,
            IndentKind subdiagsIndent) {
        Context ctx = new Context();
        Options options = Options.instance(ctx);
        outputKind.init(options);
        multiPolicy.init(options);
        xdiagsSource.init(options);
        xdiagsCompact.init(options);
        caretKind.init(options);
        sourceLineKind.init(options);
        String indentString = "";
        indentString = (summaryIndent == IndentKind.CUSTOM) ? "3" : "0";
        indentString += (detailsIndent == IndentKind.CUSTOM) ? "|3" : "|0";
        indentString += (sourceIndent == IndentKind.CUSTOM) ? "|3" : "|0";
        indentString += (subdiagsIndent == IndentKind.CUSTOM) ? "|3" : "|0";
        options.put("diagsIndentation", indentString);
        MyLog log = new MyLog(ctx);
        JavacMessages messages = JavacMessages.instance(ctx);
        messages.add("tester");
        JCDiagnostic.Factory diags = JCDiagnostic.Factory.instance(ctx);
        log.useSource(new MyFileObject("This is a source line"));
        JCDiagnostic d = diags.error(log.currentSource(),
            posKind.pos(),
            errorKind.key(), "Hello!");
        if (multiKind != MultilineKind.NONE) {
            JCDiagnostic sub = diags.fragment(errorKind.key(), "Hello!");
            if (multiKind.isNested())
                sub = new JCDiagnostic.MultilineDiagnostic(sub, List.of(sub));
            List<JCDiagnostic> subdiags = multiKind.isDouble() ?
                List.of(sub, sub) :
                List.of(sub);
            d = new JCDiagnostic.MultilineDiagnostic(d, subdiags);
        }
        String diag = log.getDiagnosticFormatter().format(d, messages.getCurrentLocale());
        checkOutput(diag,
                outputKind,
                errorKind,
                multiKind,
                multiPolicy,
                posKind,
                xdiagsSource,
                xdiagsCompact,
                caretKind,
                sourceLineKind,
                summaryIndent,
                detailsIndent,
                sourceIndent,
                subdiagsIndent);
    }

    void test() {
        for (OutputKind outputKind : OutputKind.values()) {
            for (ErrorKind errKind : ErrorKind.values()) {
                for (MultilineKind multiKind : MultilineKind.values()) {
                    for (MultilinePolicy multiPolicy : MultilinePolicy.values()) {
                        for (PositionKind posKind : PositionKind.values()) {
                            for (XDiagsSource xdiagsSource : XDiagsSource.values()) {
                                for (XDiagsCompact xdiagsCompact : XDiagsCompact.values()) {
                                    for (CaretKind caretKind : CaretKind.values()) {
                                        for (SourceLineKind sourceLineKind : SourceLineKind.values()) {
                                            for (IndentKind summaryIndent : IndentKind.values()) {
                                                for (IndentKind detailsIndent : IndentKind.values()) {
                                                    for (IndentKind sourceIndent : IndentKind.values()) {
                                                        for (IndentKind subdiagsIndent : IndentKind.values()) {
                                                            exec(outputKind,
                                                                errKind,
                                                                multiKind,
                                                                multiPolicy,
                                                                posKind,
                                                                xdiagsSource,
                                                                xdiagsCompact,
                                                                caretKind,
                                                                sourceLineKind,
                                                                summaryIndent,
                                                                detailsIndent,
                                                                sourceIndent,
                                                                subdiagsIndent);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if (nerrors != 0)
            throw new AssertionError(nerrors + " errors found");
    }

    void printInfo(String msg, OutputKind outputKind, ErrorKind errorKind, MultilineKind multiKind,
            MultilinePolicy multiPolicy, PositionKind posKind, XDiagsSource xdiagsSource,
            XDiagsCompact xdiagsCompact, CaretKind caretKind, SourceLineKind sourceLineKind,
            IndentKind summaryIndent, IndentKind detailsIndent, IndentKind sourceIndent,
            IndentKind subdiagsIndent, String errorLine) {
        String sep = "*********************************************************";
        String desc = "raw=" + outputKind + " pos=" + posKind + " key=" + errorKind.key() +
                " multiline=" + multiKind +" multiPolicy=" + multiPolicy.value +
                " diags= " + java.util.Arrays.asList(xdiagsSource.flag, xdiagsCompact.flag) +
                " caret=" + caretKind + " sourcePosition=" + sourceLineKind +
                " summaryIndent=" + summaryIndent + " detailsIndent=" + detailsIndent +
                " sourceIndent=" + sourceIndent + " subdiagsIndent=" + subdiagsIndent;
        System.out.println(sep);
        System.out.println(desc);
        System.out.println(sep);
        System.out.println(msg);
        System.out.println("Diagnostic formatting problem - expected diagnostic...\n" + errorLine);
    }

    void checkOutput(String msg, OutputKind outputKind, ErrorKind errorKind, MultilineKind multiKind,
            MultilinePolicy multiPolicy, PositionKind posKind, XDiagsSource xdiagsSource,
            XDiagsCompact xdiagsCompact, CaretKind caretKind, SourceLineKind sourceLineKind,
            IndentKind summaryIndent, IndentKind detailsIndent, IndentKind sourceIndent,
            IndentKind subdiagsIndent) {
        boolean shouldPrintSource = posKind == PositionKind.POS &&
                xdiagsSource != XDiagsSource.NO_SOURCE &&
                (xdiagsSource == XDiagsSource.SOURCE ||
                outputKind == OutputKind.BASIC);
        String errorLine = posKind.getOutput(outputKind) +
                errorKind.getOutput(outputKind, summaryIndent, detailsIndent);
        if (xdiagsCompact != XDiagsCompact.COMPACT)
            errorLine += multiKind.getOutput(outputKind, errorKind, multiPolicy, summaryIndent, detailsIndent, subdiagsIndent);
        String[] lines = errorLine.split("\n");
        if (xdiagsCompact == XDiagsCompact.COMPACT) {
            errorLine = lines[0];
            lines = new String[] {errorLine};
        }
        if (shouldPrintSource) {
            if (sourceLineKind.isAfterSummary()) {
                String sep = "\n";
                if (lines.length == 1) {
                    errorLine += "\n";
                    sep = "";
                }
                errorLine = errorLine.replaceFirst("\n",
                        Matcher.quoteReplacement(xdiagsSource.getOutput(caretKind, sourceIndent, outputKind) + sep));
            }
            else
                errorLine += xdiagsSource.getOutput(caretKind, sourceIndent, outputKind);
        }

        if (!msg.equals(errorLine)) {
            printInfo(msg,
                    outputKind,
                    errorKind,
                    multiKind,
                    multiPolicy,
                    posKind,
                    xdiagsSource,
                    xdiagsCompact,
                    caretKind,
                    sourceLineKind,
                    summaryIndent,
                    detailsIndent,
                    sourceIndent,
                    subdiagsIndent,
                    errorLine);
            nerrors++;
        }
    }

    public static void main(String... args) throws Exception {
        new T6769027().test();
    }
}
