package de.duehl.basics.debug;

/*
 * Copyright 2021 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.List;

import de.duehl.basics.datetime.Timestamp;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse stellt Hilfsmethoden rund um Ausgaben zum Debugging bereit.
 *
 * @version 1.01     2021-04-12
 * @author Christian Dühl
 */

public class DebugHelper {

    /*
     * TODO siehe CallerDeterminer
     */

    /** Gibt den Stacktrace aus. */
    public static void printStackTrace() {
        say(stackTraceToString());
    }

    /** Erstellt einen String mit dem Inhalt des StackTraces. */
    public static String stackTraceToString() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        StringBuilder builder = new StringBuilder();
        for (StackTraceElement element : stackTraceElements) {
            builder.append(element.toString());
            builder.append("\n");
        }
        return builder.toString();
    }

    /** Gibt den Stacktrace sinnvoll aus. */
    public static void printStackTraceSmall() {
        System.out.print(stackTraceToStringSmall());
    }

    /** Erstellt einen String mit dem sinnvollen Inhalt des StackTraces. */
    public static String stackTraceToStringSmall() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        StringBuilder builder = new StringBuilder();
        boolean start = true;
        for (StackTraceElement element : stackTraceElements) {
            String className = element.getClassName();
            if (className.startsWith("java.awt") || className.startsWith("java.security")) {
                continue;
            }
            String pureClassName = Text.getLastPartAfterDivider(className, ".");
            if (start) {
                start = false;
                if (pureClassName.equals("DebugHelper") || pureClassName.equals("Thread")) {
                    start = true;
                }
            }
            if (!start) { // kein else !!
                builder.append(pureClassName);
                builder.append(" ");
                builder.append(element.getMethodName());
                builder.append("()");

                int lineNumber = element.getLineNumber();
                if (lineNumber >= 0) {
                    builder.append(" ");
                    builder.append(lineNumber);
                }

                builder.append(" - ");

                String fileName = element.getFileName();
                if (fileName == null) {
                    builder.append("unknown source");
                }
                else {
                    if (lineNumber >= 0) {
                        builder.append("(" + fileName + ":" + lineNumber + ")");
                    }
                    else {
                        builder.append("(" + fileName + ")");
                    }
                }
                builder.append("\n");
            }
        }
        return builder.toString();
    }

    /** Gibt einen Zeilenumbruch aus. */
    public static void say() {
        say("");
    }

    /** Gibt den übergebenen Text gefolgt von einem Zeilenumbruch aus. */
    public static void say(String message) {
        System.out.println(message);
    }

    /** Gibt den übergebenen Text aus, ohne einen Zeilenumbruch anzufügen. */
    public static void sayWithoutLineBreak(String message) {
        System.out.print(message);
    }

    /**
     * Gibt den übergebenen Text gefolgt von einem Zeilenumbruch aus. Dabei wird vor den
     * übergebenen Text noch die Klasse und die Methode eingefügt, die diese Methode aufriefen.
     */
    public static void sayWithClassAndMethod(String message) {
        String fullMessage = buildClassAndMethodMessage(message);
        say(fullMessage);
    }

    private static String buildClassAndMethodMessage(String message) {
        StackTraceElement[] stes = Thread.currentThread().getStackTrace();
        /*
        CollectionsHelper.printListNice(CollectionsHelper.arrayToList(stes));
        System.out.println("0 : " + stes[0]);
        System.out.println("1 : " + stes[1]);
        System.out.println("2 : " + stes[2]);
        System.out.println("3 : " + stes[3]);
        System.out.println("4 : " + stes[4]);
        */
        /*
         * Beispiel für einen Aufruf aus dem HitGroupsBuilder in der Methode buildHitGroups
         * über das dortige say zu dieser Methode:
         * 0 : java.lang.Thread.getStackTrace
         *     (Thread.java:1552)
         * 1 : de.duehl.basics.debug.DebugHelper.buildClassAndMethodMessage
         *     (DebugHelper.java:116)
         * 2 : de.duehl.basics.debug.DebugHelper.sayWithClassAndMethodAndTime
         *     (DebugHelper.java:146)
         * 3 : de.heinsundpartner.impressum.debug.Debug.say
         *     (Debug.java:18)
         * 4 : de.heinsundpartner.impressum.dbtool.dbworkerviewer.logic.DBWorkerViewerLogic.
         *     cancelTimer(DBWorkerViewerLogic.java:193)
         * Daher interessiert hier der Index 4!
         *
         * Hat sich irgendwie geändert, Stand 12.04.2021:
         * 0 : java.base/java.lang.Thread.getStackTrace
         *     (Thread.java:1596)
         * 1 : de.duehl.basics.debug.DebugHelper.buildClassAndMethodMessage
         *     (DebugHelper.java:128)
         * 2 : de.duehl.basics.debug.DebugHelper.sayWithClassAndMethodAndTime
         *     (DebugHelper.java:173)
         * 3 : de.heinsundpartner.hr.watcher.tool.logic.WhatExistsLogic.quit
         *     (WhatExistsLogic.java:175)
         * 4 : de.heinsundpartner.hr.watcher.tool.ui.WhatExistsGui.lambda$3(WhatExistsGui.java:242)
         *
         * Hier interessiert plötzlich Index 3! Ah weil kein say dazwischen ist!
         *
         * Dann baue ich ein Say ein statt hier zu ändern!
         */
        StackTraceElement ste = stes[4];
        String fullClassName = ste.getClassName();
        List<String> parts = Text.splitByDot(fullClassName);
        String className = parts.get(parts.size() - 1);
        String methodName = ste.getMethodName();
        String lineNumber = "[Zeile " + ste.getLineNumber() + "]";


        String fullMessage = className + "#" + methodName + "() " + lineNumber + ": " + message;
        return fullMessage;
    }

    public static void sayWithClassAndMethodAndTime(String message) {
        String fullMessage = buildClassAndMethodMessage(message);
        String now = Timestamp.actualDateAndTime(", ");
        say(now + " - " + fullMessage);
    }

    public static void sayWithClassAndMethodAndTimeAndMillis(String message) {
        String fullMessage = buildClassAndMethodMessage(message);
        String now = Timestamp.actualDateAndTime(", ");
        say(now + " (" + System.currentTimeMillis() + ") - " + fullMessage);
    }

    /**
     * Logt '-' Zeichen in der gegebenen Anzahl.
     * Beispiel: partLine(3) logt "---".
     *
     * @param length
     *            Die gegebene Anzahl.
     */
    private static void partLine(int length) {
        for (int i = 0; i < length; i++) {
            sayWithoutLineBreak("-");
        }
    }

    /**
     * Logt eine Zeile mit der gegebenen Anzahl '-' Zeichen.
     *
     * @param length
     *            Die gegebene Anzahl.
     */
    public static void line(int length) {
        partLine(length);
        say();
    }

    /** Logt eine Zeile mit 80 '-' Zeichen. */
    public static void line() {
        line(80);
    }

    /**
     * Fügt den übergebenen String in eine Zeile mit 80 Bindestrichen ein.
     *
     * @param text
     *            Einzufügender String.
     */
    public static void embedTextIntoLine(String text) {
        embedTextIntoLine(text, 80);
    }

    /**
     * Fügt den übergebenen String in eine Zeile mit so vielen Bindestrichen wie
     * angeben ein.
     *
     * @param text
     *            Einzufügender String.
     * @param length
     *            Anzahl (oder Breite der Zeile).
     */
    public static void embedTextIntoLine(String text, int length) {
        int textLength = text.length();

        if (textLength + 4 > length) {
            System.out.println(text);
        }
        else {
            int frontLength = (length - textLength - 2) / 2;
            int backLength  = length - textLength - 2 - frontLength;
            partLine(frontLength);
            sayWithoutLineBreak(" " + text + " ");
            partLine(backLength);
            say();
        }
    }

}
