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

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;



/*******************************************************************************
 * Knihovní třída {@code IO} obsahuje sadu metod
 * pro jednoduchý vstup a výstup prostřednictvím dialogových oken
 * spolu s metodou zastavující běh programu na daný počet milisekund
 * a metodu převádějící texty na ASCII jednoduchým odstraněním diakritiky.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2020-Winter
 */
public final class IO
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Přepravka pro nulové velikosti okrajů. */
    private static final Insets ZERO_BORDER = new Insets(0, 0, 0, 0);

    /** Rozdíl mezi tloušťkou rámečku okna ohlašovanou před a po
     *  volání metody {@link #setResizable(boolean)}.
     *  Tento rozdíl ve Windows ovlivňuje nastavení velikosti a pozice.
     *  Při {@code setResizable(true)} jsou jeho hodnoty větší,
     *  a proto se spočte se jako "true" - "false". */
    private static final Insets INSETS_DIF;

    /** Informace o tom, budou-li se opravovat pozice a rozměry oken. */
    private static final boolean CORRECT;



//\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Pozice dialogových oken. */
    private static Point windowLocation = new Point(0,0);

    /** Příznak testovacího režimu - je-li nastaven na {@code true},
     *  metoda {@link #inform(Object)} neotevírá dialogové okno
     *  a metoda {@link #pause(int)} nečeká. */
    private static boolean testMode = false;

    /** Pomocná výjimka umožňující určit, odkud bylo voláno dialogové okno,
     *  z nějž uživatel program ukončil. */
    private static Throwable callStack;



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

    /***************************************************************************
     * Windows Vista + Windows 7 se neumějí dohodnout s Javou na skutečné
     *  velikosti oken a jejich rámů a v důsledku toho nefunguje správně
     *  ani umísťování oken na zadané souřadnice.
     *  Následující statický konstruktor se snaží zjistit chování aktuálního
     *  operačního systému a podle toho připravit potřebné korekce.
     *  Doufejme, že záhy přestane být potřeba.
     */
    static {
        String os = System.getProperty("os.name");
        if (os.startsWith("Windows")) {
            JFrame frame = new JFrame();
            frame.setLocation(-1000, -1000);
            frame.setResizable(true);
            frame.pack();
            Insets insTrue  = frame.getInsets();
            frame.setResizable(false);
            Insets insFalse = frame.getInsets();
            Insets insets;
            insets = new Insets(insTrue.top    - insFalse.top,
                                insTrue.left   - insFalse.left,
                                insTrue.bottom - insFalse.bottom,
                                insTrue.right  - insFalse.right);
            if (ZERO_BORDER.equals(insets)) {
                //Nevěřím mu, určitě kecá
                int decrement = (insTrue.left == 8)  ?  5  :  1;
                insets = new Insets(decrement, decrement, decrement, decrement);
            }
            INSETS_DIF = insets;
//            CORRECT = true;
            CORRECT = ! ZERO_BORDER.equals(INSETS_DIF);
        }
        else {
            INSETS_DIF = ZERO_BORDER;
            CORRECT  = false;
        }
    }



//\CF== CLASS (STATIC) FACTORY METHODS =========================================
//\CG== CLASS (STATIC) GETTERS AND SETTERS =====================================
//\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS ===========================

    /** Systémový čas posledního volání metody {@link #duration()} */
    private static long last = System.currentTimeMillis();
    /***************************************************************************
     * Vrátí počet milisekund uplynuvších od posledního volání této metody.
     *
     * @return Počet milisekund uplynuvších od posledního volání
     */
    public static int duration()
    {
        long now = System.currentTimeMillis();
        int  result = (int)(now - last);
        last = now;
        return result;
    }


    /***************************************************************************
     * Počká zadaný počet milisekund.
     * Na přerušení nijak zvlášť nereaguje - pouze skončí dřív.
     * Před tím však nastaví příznak, aby volající metoda poznala,
     * že vlákno bylo žádáno o přerušení.
     *
     * @param milliseconds Počet milisekund, po něž se má čekat
     */
    public static void pause(int milliseconds)
    {
        if (testMode) {
            Informant.informer.hold(milliseconds);
        }
        else {
            try {
                Thread.sleep(milliseconds);
            }catch(InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }


    /***************************************************************************
     * Zbaví zadaný text diakritických znamének; současně ale odstraní také
     * všechny další znaky nespadající do tabulky ASCII.
     *
     * @param text Text určený k "odháčkování"
     * @return  "Odháčkovaný" text
     */
    public static String toASCII(String text)
    {
        return ToASCII.text(text);
    }


    /***************************************************************************
     * Nastaví pozici příštího dialogového okna.
     *
     * @param x  Vodorovná souřadnice
     * @param y  Svislá souřadnice
     */
    public static void setDialogsPosition(int x, int y)
    {
        windowLocation = new Point(x, y);
//        if (CORRECT) {
//            windowLocation.x += INSETS_DIF.left;
//            windowLocation.y += INSETS_DIF.top + INSETS_DIF.bottom;
//        }
    }


    /***************************************************************************
     * Zobrazí dialogové okno se zprávou a umožní uživateli odpovědět
     * <b>ANO</b> nebo <b>NE</b>. Vrátí informaci o tom, jak uživatel odpověděl.
     * Neodpoví-li a zavře dialog, ukončí program.
     *
     * @param question Zobrazovaný text otázky.
     * @return <b>{@code true}</b> Odpověděl-li uživatel <b>ANO</b>,
     *         <b>{@code false}</b> odpověděl-li <b>NE</b>
     */
    public static boolean confirm(Object question)
    {
        return (choose(question, "ANO", "NE") == 0);
    }


    /***************************************************************************
     * Zobrazí dialogové okno se otázkou, na níž má uživatel odpovědět stiskem
     * některého z tlačítek, jejichž popisky naznačují možné odpovědi.
     * Vrátí pořadí stisknutého tlačítka.
     * Pokud uživatel neodpoví a zavře dialog, metoda ukončí program.
     *
     * @param question Zobrazovaný text otázky
     * @param buttons  Popisky na tlačítcích
     * @return Pořadí stisknutého tlačítka, přičemž první tlačítko zleva
     *         na pořadí 0
     */
    public static int choose(Object question, String... buttons)
    {
        JOptionPane jop = new JOptionPane(
                              question,
                              JOptionPane.QUESTION_MESSAGE, //Message type
                              0,                            //Option type
                              null,                         //Icon
                              buttons,                      //Options
                              null                          //InitialValue
                          );
        processJOP(jop);
        String answer = (String)jop.getValue();
        if (answer == null) {
            exit(1);
        }
        for (int index = 0;   index < buttons.length;   index++) {
            if (answer.equals(buttons[index])) {
                return index;
            }
        }
        return -1;
    }


    /***************************************************************************
     * Zobrazí dialogové okno s výzvou k zadání reálné hodnoty;
     * při zavření okna zavíracím tlačítkem ukončí aplikaci.
     *
     * @param prompt        Text výzvy oznamující uživateli, co má zadat
     * @param defaultDouble Implicitní hodnota.
     * @return Uživatelem zadaná hodnota, resp. potvrzená implicitní hodnota.
     */
    public static double enter(Object prompt, double defaultDouble)
    {
        String defVal = Double.toString(defaultDouble).trim();
        String answer = enter(prompt, defVal);
        return Double.parseDouble(answer);
    }


    /***************************************************************************
     * Zobrazí dialogové okno s výzvou k zadání celočíselné hodnoty;
     * při zavření okna nebo stisku tlačítka Cancel
     * se celá aplikace ukončí.
     *
     * @param prompt     Text výzvy oznamující uživateli, co má zadat
     * @param defaultInt Implicitní hodnota.
     * @return Uživatelem zadaná hodnota, resp. potvrzená implicitní hodnota.
     */
    public static int enter(Object prompt, int defaultInt)
    {
        String defVal = Integer.toString(defaultInt).trim();
        String answer = enter(prompt, defVal);
        return Integer.parseInt(answer);
    }


    /***************************************************************************
     * Zobrazí dialogové okno s výzvou k zadání textové hodnoty;
     * při zavření okna nebo stisku tlačítka Cancel
     * se celá aplikace ukončí.
     *
     * @param prompt        Text výzvy oznamující uživateli, co má zadat
     * @param defaultString Implicitní hodnota
     * @return Uživatelem zadaná hodnota, resp. potvrzená implicitní hodnota
     */
    public static String enter(Object prompt, String defaultString)
    {
        JOptionPane pane = new JOptionPane(
                               prompt,
                               JOptionPane.QUESTION_MESSAGE,   //Message type
                               JOptionPane.DEFAULT_OPTION  //Option type - OK
                               );
        pane.setWantsInput(true);
        pane.setInitialSelectionValue(defaultString);
        processJOP(pane);
        String answer = pane.getInputValue().toString();
        return answer;
    }


    /***************************************************************************
     * Zobrazí dialogové okno s výzvou k zadání
     * některého z textů v rozbalovacím seznamu;
     * při zavření okna nebo stisku tlačítka Cancel
     * se celá aplikace ukončí.
     *
     * @param prompt  Text výzvy oznamující uživateli, co má zadat
     * @param options Texty, z nichž si může uživatel vybrat
     * @return Uživatelem zadaná hodnota
     */
    public static String select(Object prompt, String... options)
    {
        JOptionPane jop = new JOptionPane(
                              prompt,
                              JOptionPane.QUESTION_MESSAGE, //Message type
                              JOptionPane.OK_CANCEL_OPTION, //Option type - OK
                              null,                         //Icon
                              null,                         //Options
                              null                          //InitialValue
                          );
        jop.setWantsInput(true);
        jop.setSelectionValues(options);
        jop.setInitialSelectionValue(null);
        processJOP(jop);
        String answer = jop.getInputValue().toString();
        return answer;
    }


    /***************************************************************************
     * Zobrazí dialogové okno se zprávou a počká,
     * až uživatel stiskne tlačítko OK;
     * při zavření okna zavíracím tlačítkem ukončí celou aplikaci.
     *
     * @param text Zobrazovaný text
     */
    public static void inform(Object text)
    {
        if (testMode) {
            Informant.informer.test(text);
        }
        else {
            JOptionPane jop = new JOptionPane(
                              text,                            //Sended message
                              JOptionPane.INFORMATION_MESSAGE  //Message type
                            );
            processJOP(jop);
        }
    }



//\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS ===========================

    /***************************************************************************
     * Ukončí běh programu a vyhodí výjimku oznamující tuto skutečnost
     * a umožňující zjistit, kde se v danou chvíli program nacházel.
     *
     * @param returnIndex Kód ukončení
     */
    private static void exit(int returnIndex)
    {
        callStack.printStackTrace(System.out);
        System.exit(returnIndex);
    }


    /***************************************************************************
     * Vytvoří dialogové okno zadaného objektu typu {@link JOptionPane},
     * definuje je jako nemodální a počká, až bude zavřeno,
     * přičemž obdrženou hodnotu uloží do atributu {@code value}.
     * Zavře-li uživatel okno ze systémové nabídky, ukončí aplikaci.
     *
     * @param pane Zpracovávaná instance typu {@link JOptionPane}
     */
    private static void processJOP(JOptionPane pane)
    {
        callStack = new Throwable(
            "Program byl předčasně ukončen uživatelem z dialogového okna");

//        final int WAITING=0, CANCELLED=1;
        final AtomicBoolean answered = new AtomicBoolean(false);

        final JDialog dialog = pane.createDialog(null, "Information");

        dialog.addWindowListener(new WinAdapter(dialog, answered));

        dialog.setModal(false);
        dialog.setVisible(true);
        dialog.setLocation(windowLocation);
        dialog.toFront();
        dialog.setAlwaysOnTop(true);
//        dialog.setAlwaysOnTop(false);

        //Čeká, dokud uživatel neodpoví nebo nezavře okno
        synchronized(answered) {
            while (! answered.get()) {
                try {
                    answered.wait();
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }



//##############################################################################
//\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===============
//\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===============



//##############################################################################
//\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===================================

    /***************************************************************************
     * Třída {@code IO} je knihovní třídou,
     * a proto nesmí umožňovat vytváření svých instancí.
     */
    private IO() {}



//\IA== INSTANCE ABSTRACT METHODS ==============================================
//\IG== INSTANCE GETTERS AND SETTERS ===========================================
//\IM== INSTANCE REMAINING NON-PRIVATE METHODS =================================
//\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =================================



//##############################################################################
//\NT== NESTED DATA TYPES ======================================================

////////////////////////////////////////////////////////////////////////////////
//\N0C /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Třída {@code Correction} je knihovní třídou poskytující metody
     * pro opravy nejrůznějších nesrovnalostí týkajících se práce
     * s grafickým vstupem a výstupem.
     */
    public static class Correction
    {
    //\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==========
    //\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==========



    //##########################################################################
    //\CI== CLASS (STATIC) INITIALIZER (CLASS CONSTRUCTOR) =====================
    //\CF== CLASS (STATIC) FACTORY METHODS =====================================
    //\CG== CLASS (STATIC) GETTERS AND SETTERS =================================
    //\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS =======================

        /***********************************************************************
         * Ve Windows 7 používajících definuje Java jinou velikost okna,
         * než odpovídá velikosti panelu obrázku.
         *
         * @param cont   Kontejner, jehož rozměry upravujeme
         */
        public static void windowLocation(Container cont)//, Insets insDif)
        {
            Point  loc;
//            if (CORRECT) {
//                loc = cont.getLocation();
//                cont.setLocation(loc.x + INSETS_DIF.left,
//                                 loc.y + INSETS_DIF.top);
//            }
        }


        /***********************************************************************
         * Ve Windows 7 definuje Java jinou velikost okna,
         * než odpovídá velikosti panelu obrázku.
         *
         * @param cont     Kontejner, jehož rozměry upravujeme
         */
        public static void windowSize(Container cont)
        {
            Dimension dim;
//            if (CORRECT) {
//                dim = cont.getSize();
//                cont.setSize(dim.width - INSETS_DIF.left - INSETS_DIF.right,
//                             dim.height- INSETS_DIF.top  - INSETS_DIF.bottom);
//            }
        }



    //\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS =======================



    //##########################################################################
    //\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===========
    //\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===========



    //##########################################################################
    //\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===============================

        /***********************************************************************
         * Soukromý konstruktor bránící vytvoření instance.
         */
        private Correction()
        {
        }



    //\IA== INSTANCE ABSTRACT METHODS ==========================================
    //\IG== INSTANCE GETTERS AND SETTERS =======================================
    //\IM== INSTANCE REMAINING NON-PRIVATE METHODS =============================
    //\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =============================



    //##########################################################################
    //\NT== NESTED DATA TYPES ==================================================
    }



////////////////////////////////////////////////////////////////////////////////
//\N1C /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Instance třídy {@code Informant} obstarává komunikaci mezi
     * testovanými a testovacími objekty.
     */
    public static class Informant
    {
    //\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==========

        /** Prostředník, který přihlášeným testovacím programům přeposílá
         *  zprávy o zavolání definovaných metod. */
        public static final Informant informer = new Informant();



    //\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==========



    //##########################################################################
    //\CI== CLASS (STATIC) INITIALIZER (CLASS CONSTRUCTOR) =====================
    //\CF== CLASS (STATIC) FACTORY METHODS =====================================
    //\CG== CLASS (STATIC) GETTERS AND SETTERS =================================
    //\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS =======================
    //\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS =======================



    //##########################################################################
    //\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===========

        /** Seznam přihlášených testovacích programů,
         *  kterým budou přeposílány zprávy o volání zadaných metod. */
        private final List<ITesterIO> list = new ArrayList<>();



    //\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===========



    //##########################################################################
    //\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===============================

        /***********************************************************************
         * Soukromý konstruktor bránící vytvoření instance.
         */
        private Informant()
        {
        }



    //\IA== INSTANCE ABSTRACT METHODS ==========================================
    //\IG== INSTANCE GETTERS AND SETTERS =======================================
    //\IM== INSTANCE REMAINING NON-PRIVATE METHODS =============================

        /***********************************************************************
         * Přidá zadaný objekt mezi objekty,
         * kterým oznamuje zavolání definovaných metod.
         *
         * @param tester Přidávaný testovací objekt
         */
        public void register(ITesterIO tester)
        {
            if (list.contains(tester)) { return; }
            list.add(tester);
            testMode = true;
        }


        /***********************************************************************
         * Odebere zadaný objekt ze seznamu objektů,
         * kterým oznamuje zavolání definovaných metod.
         *
         * @param tester Odebíraný testovací objekt
         */
        public void unregister(ITesterIO tester)
        {
            list.remove(tester);
            if (list.isEmpty()) {
                testMode = false;
            }
        }



    //\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =============================

        /***********************************************************************
         * Oznámí zavolání metody {@link IO#pause(int)}
         * a předá v parametru zadanou dobu čekání.
         *
         * @param ms Zadaná doba čekání v milisekundách
         */
        private void hold(int ms)
        {
            for (ITesterIO it : list) {
                it.pause(ms);
            }
        }


        /***********************************************************************
         * Oznámí zavolání metody {@link IO#inform(Object)}
         * a předá v parametru vypisovaný text.
         *
         * @param message Zobrazovaný text
         */
        private void test(Object message)
        {
            for (ITesterIO it : list) {
                it.inform(message);
            }
        }



    //##########################################################################
    //\NT== NESTED DATA TYPES ==================================================
    }



////////////////////////////////////////////////////////////////////////////////
//\N2I /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Instance rozhraní {@code ITesterIO} představují testovací objekty,
     * které chtějí být zpravovány o zajímavých událostech.
     */
    public
    interface ITesterIO
    {
    //\CC== CLASS (STATIC) CONSTANTS ===========================================
    //\CM== CLASS (STATIC) METHODS =============================================



    //##########################################################################
    //\AG== ABSTRACT GETTERS AND SETTERS =======================================
    //\AM== REMAINING ABSTRACT METHODS =========================================
    //\DG== DEFAULT GETTERS AND SETTERS ========================================
    //\DM== REMAINING DEFAULT METHODS ==========================================

        /***********************************************************************
         * Reakce na oznámení o zavolání metody {@link IO#pause(int)}
         * mající počkat předanou dobu.
         *
         * @param ms Zadaná doba čekání v milisekundách
         */
        default
        public void pause(int ms)
        {
        }


        /***********************************************************************
         * Reakce na oznámení o zavolání metody {@link IO#inform(Object)}
         * a vypisující předaný text.
         *
         * @param message Zobrazovaný text
         */
        default
        public void inform(Object message)
        {
        }



    //##########################################################################
    //\NT== NESTED DATA TYPES ==================================================
    }



////////////////////////////////////////////////////////////////////////////////
//\N3C /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Třída {@code ToASCII} je knihovní třídou poskytující metodu na
     * odstranění diakritiky ze zadaného textu a následné převedení všech znaků,
     * jejichž kód je stále větší než 127, na příslušné kódové
     * únikové posloupnosti (escape sekvence).
     */
    private static class ToASCII
    {
    //\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==========

        /** Mapa s převody znaků do ASCII. */
        private static final Map<Character,String> CONVERSION =
                                                   new HashMap<>(64);



    //\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==========



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

        static {
            /** Převody UNICODE znaků na jejich ASCII ekvivalenty. */
            String[][] pairs = {

                {"Á", "A"},  {"á", "a"},    {"Ä", "AE"}, {"ä", "ae"},
                {"Č", "C"},  {"č", "c"},
                {"Ď", "D"},  {"ď", "d"},
                {"Ë", "E"},  {"ë", "e"},
                {"É", "E"},  {"é", "e"},    {"Ě", "E"},  {"ě", "e"},
                {"Í", "I"},  {"í", "i"},    {"Ï", "IE"}, {"ï", "ie"},
                {"Ĺ", "L"},  {"ĺ", "l"},    {"Ľ", "L"},  {"ľ", "l"},
                {"Ň", "N"},  {"ň", "n"},
                {"Ó", "O"},  {"ó", "o"},    {"Ö", "OE"}, {"ö", "oe"},
                {"Ô", "O"},  {"ô", "o"},
                {"Ŕ", "R"},  {"ŕ", "r"},    {"Ř", "R"},  {"ř", "r"},
                {"Š", "S"},  {"š", "s"},
                {"Ť", "T"},  {"ť", "t"},
                {"Ú", "U"},  {"ú", "u"},    {"Ü", "UE"}, {"ü", "ue"},
                {"Ů", "U"},  {"ů", "u"},
                {"Ý", "Y"},  {"ý", "y"},    {"Ÿ", "YE"}, {"ÿ", "ye"},
                {"Ž", "Z"},  {"ž", "z"},
                {"ß", "ss"},
                {"‹", "<"},  {"›", ">"},    {"«", "<<"}, {"»", ">>"},
                {"©", "(c)"},{"®", "(R)"},
                {"„", "\""}, {"“", "\""},   {"”", "\""},
                {"‚", "\'"}, {"‘", "\'"},   {"’", "\'"},
                {"×", "x"},  {"÷", ":"},
                {"–", "-"},  {"—", "-"},    //ndash, mdash
                {"¦", "|"},
//                {"",""},
            };
            for (String[] ss : pairs) {
                CONVERSION.put(Character.valueOf(ss[0].charAt(0)),  ss[1]);
            }
        }



    //\CF== CLASS (STATIC) FACTORY METHODS =====================================
    //\CG== CLASS (STATIC) GETTERS AND SETTERS =================================
    //\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS =======================

        /***********************************************************************
         * Převede zadaný text do ekvivalentního tvaru bez diakritických
         * znamének a dalších ne-ASCII znaků.
         * Znaky chybějící v převodní tabulce převede na standardní
         * sekvenci {@code \}{@code uHHHH}.
         *
         * @param text Text určený k převodu
         * @return  Převedený text
         */
        public static String text(CharSequence text)
        {
            final int LENGTH = text.length();
            final StringBuilder sb = new StringBuilder(LENGTH);
            for (int i = 0;   i < LENGTH;   i++) {
                char c = text.charAt(i);
                if (c < 128) {
                    sb.append(c);
                }else if (CONVERSION.containsKey(c)) {
                    sb.append(CONVERSION.get(c));
                }else {
                    sb.append(expand(c));
                }
            }
            return sb.toString();
        }



    //\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS =======================

        /***********************************************************************
         * Rozepíše zadaný znak do příslušné únikové kódové posloupnosti
         * (escape sekvence).
         *
         * @param c Převáděný znak
         * @return Text ve formátu \\uXXXX
         */
        private static String expand(char c) {
            return String.format("\\u%04x", (int)c);
        }



    //##########################################################################
    //\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===========
    //\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===========



    //##########################################################################
    //\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===============================

        /***********************************************************************
         * Soukromý konstruktor bránící vytvoření instance.
         */
        private ToASCII()
        {
        }



    //\IA== INSTANCE ABSTRACT METHODS ==========================================
    //\IG== INSTANCE GETTERS AND SETTERS =======================================
    //\IM== INSTANCE REMAINING NON-PRIVATE METHODS =============================
    //\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =============================



    //##########################################################################
    //\NT== NESTED DATA TYPES ==================================================
    }



////////////////////////////////////////////////////////////////////////////////
//\N4C /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Instance třídy {@code WindoewAdapter} představují adaptéry
     * umožňující snadněji zadávat reakce na události okna.
     *
     * @author  Rudolf PECINOVSKÝ
     * @version 2020-Winter
     */
    public static class WinAdapter extends WindowAdapter
    {
    //\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==========
    //\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==========



    //##########################################################################
    //\CI== CLASS (STATIC) INITIALIZER (CLASS CONSTRUCTOR) =====================
    //\CF== CLASS (STATIC) FACTORY METHODS =====================================
    //\CG== CLASS (STATIC) GETTERS AND SETTERS =================================
    //\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS =======================
    //\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS =======================



    //##########################################################################
    //\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===========

        private final JDialog dialog;
        private final AtomicBoolean answered;



    //\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===========



    //##########################################################################
    //\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===============================

        /***********************************************************************
         * Vytvoří ovladač reagující na uzavření a na deaktivaci zadaného okna.
         *
         * @param dialog   Dialogové okno, na jehož události se má reagovat
         * @param answered Proměnná, do níž se uloží, že uživatel odpověděl
         */
        public WinAdapter(JDialog dialog, AtomicBoolean answered)
        {
            this.dialog   = dialog;
            this.answered = answered;
        }



    //\IA== INSTANCE ABSTRACT METHODS ==========================================
    //\IG== INSTANCE GETTERS AND SETTERS =======================================
    //\IM== INSTANCE REMAINING NON-PRIVATE METHODS =============================

        /***********************************************************************
         * Při zavírání okna za systémové nabídky zavře celou aplikaci.
         *
         * @param e Zpracovávaná událost
         */
        @Override
        public void windowClosing(WindowEvent e) {
            System.exit(1);
        }


        /***********************************************************************
         * Zapamatuje si pozici okna při jeho deaktivaci pro příště
         * a není-li přitom okno viditelné, prohlásí, že uživatel odpověděl.
         *
         * @param e Zpracovávaná událost
         */
        @Override
        public void windowDeactivated(WindowEvent e) {
            windowLocation = dialog.getLocation();
            if (! dialog.isShowing()) {
                dialog.dispose();
                synchronized(answered) {
                    answered.set(true);
                    answered.notifyAll();
                }
            }
        }



    //\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =============================



    //##########################################################################
    //\NT== NESTED DATA TYPES ==================================================
    }
}
