package shapes77.dbg;
/* Saved in UTF-8 codepage: Příliš žluťoučký kůň úpěl ďábelské ódy. ÷ × ¤ */

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;



/********************************************************************************
 * Knihovní třída {@code Printer} poskytuje metody pro podmíněný výstup
 * ladicích tisků do předem zadaného proudu, kterým muže byt standardní výstup,
 * standardní chybový výstup, libovolný proud typu {@link PrintStream}
 * nebo soubor, který se v případě potřeby nejprve vytvoří.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2020-Winter
 */
public final class Printer
{
//    static { Systém.out.println("CLASS - Printer - START"); }
//    { Systém.out.println("INSTANCE - Printer - START"); }
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Mapa přiřazující zadané výstupní proudy odpovídajícím
     *  odsazujícím proudům. */
    private static final Map<OutputStream, IndentingPrintStream>
                                  OUTPUT_2_INDENTING_STREAM = new HashMap<>();
//
//    /** Seznam výstupních proudů, do nichž se bude zapisovat. */
//    private final static List<PrintStream> PRINT_STREAMS = new ArrayList<>();
//
//    /** Seznam odsazujících výstupních proudů, vytvořených vhodnou dekorací
//     *  zadaných výstupních proudů. */
//    private final static List<IndentingPrintStream> INDENTING_PRINT_STREAMS =
//                                                    new ArrayList<>();



//== VARIABLE CLASS ATTRIBUTES =================================================

    /** Proměnná, jejiž hodnota ovlivňuje,
     *  zda se budou požadované tisky provádět nebo ne.
     *  Čím vyšší hodnota, tím víc bude realizovaných kontrolních tisků. */
    public static int DEBUG_LEVEL = 0;

//    /** Nastavitelný výstupní proud. */
//    private static PrintStream out = System.out;



//##############################################################################
//== STATIC INITIALIZER (CLASS CONSTRUCTOR) ====================================

    static {
        addOutputStream(System.out);
    }



//== CLASS GETTERS AND SETTERS =================================================

    /****************************************************************************
     * Vrátí aktuální hodnotu proměnné ovlivňující hladinu kontrolních tisků.
     *
     * @return Aktuální hodnota proměnné ovlivňující hladinu kontrolních tisků;
     *         čím vyšší hodnota, tím víc bude realizovaných kontrolních tisků
     */
    public static int getDEBUG_LEVEL()
    {
        return DEBUG_LEVEL;
    }


    /****************************************************************************
     * Nastaví aktuální hodnotu proměnné ovlivňující hladinu kontrolních tisků.
     *
     * @param newLevel Požadovaná nová hladina;
     *        čím vyšší hodnota, tím víc bude realizovaných kontrolních tisků,
     *        0 = prakticky se netiskne
     */
    public static void setDEBUG_LEVEL(int newLevel)
    {
        DBG.DEBUG_LEVEL = newLevel;
    }


    /****************************************************************************
     * Vrátí proud, do nějž odcházejí ladící tisky
     *
     * @return Proud, do nějž odcházejí ladící tisky
     */
    public static Collection<OutputStream> getOutputStreams()
    {
        return OUTPUT_2_INDENTING_STREAM.keySet();
    }



//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /****************************************************************************
     * Přidá zadaný tiskový proud mezi proudy, do nichž se zapisuje
     * a vrátí informaci o tom, jestli se tím seznam změnil,
     * tj. jestli nebyl zadaný proud před žádostí o přidání již v seznamu.
     *
     * @param outputStream Přidávaný tiskový proud
     * @return Byl-li proud v seznamu, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public static boolean addOutputStream(OutputStream outputStream)
    {
        if (OUTPUT_2_INDENTING_STREAM.containsKey(outputStream))
        {
            return false;
        } else {
            IndentingPrintStream ips = IndentingPrintStream.
                                       indentifyPrintStream(outputStream);
            OUTPUT_2_INDENTING_STREAM.put(outputStream, ips);
            return true;
        }
    }


    /****************************************************************************
     * Je-li jako parametr zadaná hodnota {@code out} nebo
     * {@code err}, přidá do seznamu příslušný standardní proud;
     * jiná podoba řetězce definuje cestu k souboru,
     * který má být přidán do seznamu výstupních proudů
     * Pokud tento soubor neexistuje, metoda se jej pokusí vytvořit.
     *
     * @param path Název / cesta k proudu přidávanému do seznamu
     */
    public static void addOutputStream(String path)
    {
        switch (path) {
            case "out":
                addOutputStream(System.out);
                break;

            case "err":
                addOutputStream(System.err);
                break;

            default:
                File file = new File(path);
                try {
                    if (! file.exists()) {
                        file.createNewFile();
                    }
                    FileOutputStream out = new FileOutputStream(file);
                    addOutputStream(out);
                } catch (IOException ex2) {
                    throw new RuntimeException("\nSoubor " + file +
                        " se nepodařilo najít ani vytvořit", ex2);
                }
                break;
        }
    }


    /****************************************************************************
     * Odebere zadaný tiskový proud ze seznamu proudů, do nichž se zapisuje
     * a vrátí informaci o tom, jestli se tím seznam změnil,
     * tj. jestli byl zadaný proud před žádostí o odebrání v seznamu.
     *
     * @param outputStream Odebíraný tiskový proud
     * @return Byl-li proud v seznamu, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public static boolean removeOutputStream(OutputStream outputStream)
    {
        IndentingPrintStream stream =
                             OUTPUT_2_INDENTING_STREAM.remove(outputStream);
        return (stream != null);
    }


    /****************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadaný text
     * bez závěrečného odřádkování.
     *
     * @param text Tištěný text
     */
    public static void pr(String text)
    {
        if (DEBUG_LEVEL > 0 ) {
            print(text);
        }
    }


    /****************************************************************************
     * Je-li {@code DEBUG_LEVEL >= hladina}, vytiskne zadaný text
     * bez závěrečného odřádkování.
     *
     * @param printLevel Hladina významnosti určující, zda se bude tisknout
     * @param text       Text určený k vytištění
     */
    public static void pr(int printLevel, String text)
    {
        if (DEBUG_LEVEL >= printLevel ) {
            print(text);
        }
    }


    /****************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadaný text
     * v zadaném formátu.
     *
     * @param format Formát specifikující způsob tisku následujících parametrů
     * @param parameters Tištěné parametry
     */
    public static void prf(String format, Object... parameters)
    {
        if (DEBUG_LEVEL > 0 ) {
            printf(format, parameters);
        }
    }


    /****************************************************************************
     * Je-li {@code DEBUG_LEVEL >= hladina}, vytiskne zadaný text
     * v zadaném formátu.
     *
     * @param printLevel Hladina významnosti určující, zda se bude tisknout
     * @param format     Formát tisku následujících parametrů
     * @param parameters Tištěné parametry
     */
    public static void prf(int printLevel, String format, Object... parameters)
    {
        if (DEBUG_LEVEL >= printLevel ) {
            printf(format, parameters);
        }
    }


    /****************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadaný text
     * a odřádkuje.
     *
     * @param text    Text určený k vytištění
     */
    public static void prln(String text)
    {
        if (DEBUG_LEVEL > 0 ) {
            println(text);
        }
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL >= printLevel}, vytiskne zadaný text
     * a odřádkuje.
     *
     * @param printLevel Hladina významnosti určující, zda se bude tisknout
     * @param text       Text určený k vytištění
     */
    public static void prln(int printLevel, String text)
    {
        if (DEBUG_LEVEL >= printLevel ) {
            println(text);
        }
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadaného pole
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param header Záhlaví vytištěného pole
     * @param array  Pole objektů, jejichž podpisy se budou tisknout
     */
    public static void prNln(String header, Object[] array)
    {
        prNln(-1, header, array);
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > printLevel}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadaného pole
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param printLevel Hladina významnosti určující, zda se bude tisknout
     * @param header     Záhlaví vytištěného pole
     * @param array      Pole objektů, jejichž podpisy se budou tisknout
     */
    public static void prNln(int printLevel, String header, Object[] array)
    {
        prNln(printLevel, header, Arrays.stream(array));
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadané kolekce
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param header      Záhlaví vytištěné kolekce
     * @param collection  Kolekce objektů, jejichž podpisy se budou tisknout
     */
    public static void prNln(String header, Collection<?> collection)
    {
        prNln(-1, header, collection);
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > printLevel}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadané kolekce
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param printLevel Hladina významnosti určující, zda se bude tisknout
     * @param header     Záhlaví vytištěné kolekce
     * @param collection Kolekce objektů, jejichž podpisy se budou tisknout
     */
    public static void prNln(int printLevel, String header,
                                             Collection<?> collection)
    {
        prNln(printLevel, header, collection.stream());
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadaného proudu
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param header Záhlaví vytištěného pole
     * @param stream Pole objektů, jejichž podpisy se budou tisknout
     */
    public static void prNln(String header, Stream<?> stream)
    {
        prNln(-1, header, stream);
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > printLevel}, vytiskne zadané záhlaví
     * a pod ním podpisy jednotlivých prvků zadaného pole
     * každý na samostatný řádek odsazený proti záhlaví.
     *
     * @param printLevel  Hladina významnosti určující, zda se bude tisknout
     * @param header      Záhlaví vytištěného pole
     * @param stream      Pole objektů, jejichž podpisy se budou tisknout
     */
    @SuppressWarnings( {"rowtypes", "unchecked"})
    public static void prNln(int printLevel, String header,
                                             Stream<?> stream)
    {
        if (DEBUG_LEVEL >= printLevel) {
            StringBuilder sb = new StringBuilder(header).append(": ");
            stream.forEachOrdered((item) ->
                                  { sb.append('\n').append(item); });
            sb.append('\n');
            println(sb.toString());
        }
    }


    /***************************************************************************
     * Počká zadaný počet milisekund.
     * Při přerusení jednoduše skonči s nastaveným příkazem přerušení,
     * aby na ně mohl volající program zareagovat.
     *
     * @param milliseconds   Počet milisekund, po něž se má čekat.
     */
    public static void wait( int milliseconds )
    {
        try {
            Thread.sleep( milliseconds);
        }catch( InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================

    /***************************************************************************
     * Vytiskne zadaný text bez závěrečného odřádkování.
     *
     * @param text Tištěný text
     */
    private static void print(String text)
    {
        printf("%s", text);
    }


    /***************************************************************************
     * Vytiskne zadaný text v zadaném formátu.
     *
     * @param format Formát specifikující způsob tisku následujících parametrů
     * @param parameters Tištěné parametry
     */
    private static void printf(String format, Object... parameters)
    {
        OUTPUT_2_INDENTING_STREAM.values().stream().
            forEachOrdered((IndentingPrintStream ps) -> {
                ps.printf(format, parameters);
                ps.flush();
            });
    }


    /***************************************************************************
     * Je-li {@code DEBUG_LEVEL > 0}, vytiskne zadaný text
     * a odřádkuje.
     *
     * @param text    Text určený k vytištění
     */
    private static void println(String text)
    {
        printf("%s\n", text);
    }



//##############################################################################
//== CONSTANT INSTANCE ATTRIBUTES ==============================================
//== VARIABLE INSTANCE ATTRIBUTES ==============================================



//##############################################################################
//== CONSTRUCTORS AND FACTORY METHODS ==========================================

    /** Soukromý konstruktor zabraňující vytvoření instance.*/
    private Printer() {}



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================
//== OTHER NON-PRIVATE INSTANCE METHODS ========================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================



//##############################################################################
//== NESTED DATA TYPES =========================================================
}
