package robotcz;
/* Saved in UTF-8 codepage: Příliš žluťoučký kůň úpěl ďábelské ódy. ÷ × ¤
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import static robotcz.IKarel.*;



/*******************************************************************************
 * Instance třídy {@code RobotWorld} reprezentuje svět robotů
 * koncipovaný jako šachovnice, na jejíž pole je možno pokládat značky.
 * Svět si pamatuje roboty, kteří se v něm vyskytují,
 * spolu s jejich pozicemi a natočeními
 * a dokáže je prostřednictvím přihlášených posluchačů zobrazit.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 1.05.9267_2023-03-11
 */
public class RobotWorld
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Délka čekání po kroku a otočce. */
    public static int PAUSE = 300;

    /** Implicitní velikost světa robotů = implicitní počet řádků a sloupců. */
    public static final int DEFAULT_SIZE = 10;

    /** Maximální povolený počet značek na jednom políčku světa robotů. */
    public static final int MAX_MARKERS = 19;

    /** Znak reprezentující zeď při zadávání světa stringy. */
    public static char WALL_CHAR = '#';

    /** Číslo reprezentující přítomnost zdi na daném políčku. */
    public static final int WALL = MAX_MARKERS + 1;

    /** Základ číselné soustavy pro převod znaků na čísla představující
     *  počet značek na daném políčku. */
    private static final int RADIX = 36;



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

    /** Odkaz na aktuální (jediný živý) svět robotů. */
    private static volatile RobotWorld activeWorld = null;



//##############################################################################
//\CF== CLASS (STATIC) FACTORY METHODS =========================================

    /***************************************************************************
     * Vytvoří svět s implicitním počtem řádků a sloupců prázdných polí.
     * Existuje-li již nějaká instance světa robotů, nejprve ji deaktivuje.
     *
     * @return Svět s implicitním počtem řádků a sloupců prázdných polí
     */
    public static RobotWorld createWorld()
    {
        return createWorld(DEFAULT_SIZE, DEFAULT_SIZE);
    }


    /***************************************************************************
     * Vytvoří svět se zadaným počtem řádků a sloupců prázdných polí.
     * Existuje-li již nějaká instance světa robotů, nejprve ji deaktivuje.
     *
     * @param rows Požadovaný počet řádků
     * @param cols Požadovaný počet sloupců
     * @return Svět se zadaným počtem řádků a sloupců prázdných polí
     */
    public static RobotWorld createWorld(int rows, int cols)
    {
        return createNewWorld(new int[rows][cols]);
    }


    /***************************************************************************
     * Vytvoří svět se zadaným počtem řádků a sloupců polí se zadaným obsahem.
     * Existuje-li již nějaká instance světa robotů, nejprve ji deaktivuje.
     *
     * @param world Pole stringů reprezentujících jednotlivé řádky světa robotů.
     *              Jednotlivé znaky reprezentují odpovídající políčka
     *              a definují, jaký je zde počet značek či zda je tu zeď.
     *              Všechny stringy musejí být stejně dlouhé.
     * @return Svět se zadanými poli
     * @see #WALL_CHAR
     */
    public static RobotWorld createWorld(String... world)
    {
        return createNewWorld(string2int(world));
    }


    /***************************************************************************
     * Pokud žádná aktivní instance světa robotů neexistuje, vytvoří svět
     * se zadaným počtem řádků a sloupců s definovaným obsahem.
     * Pokud aktivní svět existuje, vyhodí IllegalStateException.
     *
     * @param fields Pole polí čísel reprezentujících jednotlivá políčka světa.
     *              Jednotlivá pole reprezentují řádky, hodnoty prvků defují
     *              počet značek (kladná čísla) nebo zeď (číslo -1 nebo hodnota
     *              konstanty {@link #WALL}) na odpovídajícím políčku světa.
     *              Všechny řádky musejí být stejně dlouhé.
     * @return Svět se zadanými poli
     * @throws IllegalStateException pokud existuje aktivní svět
     */
    public static RobotWorld createWorld(int[]... fields)
    {
        for (int[] row : fields) {
            for (int i=0;   i < row.length;   i++) {
                if (row[i] == -1) {
                    row[i] = WALL;
                }
            }
        }
        return createNewWorld(fields);
    }



//\CG== CLASS (STATIC) GETTERS AND SETTERS =====================================

    /***************************************************************************
     * Vrátí aktuálně platnou (= živou) instanci světa robotů;
     * pokud žádná neexistuje, vrátí {@code null}.
     *
     * @return Aktuálně platná instance světa robotů nebo {@code null}
     */
    public static RobotWorld activeWorld()
    {
        return activeWorld;
    }



//\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS ===========================

    /***************************************************************************
     * Zruší aktuální svět, aby bylo možno vytvořit nový.
     * Pokud žádný aktuální svět neexistuje, neudělá nic.
     * Při té příležitosti oznámí všem posluchačům, že se svět ruší
     * a že mají uvolnit všechny blokované zdroje.
     */
    public static void destroyWorld()
    {
        if (activeWorld == null) { return; }
        activeWorld.robots.clear();
        for (IRobotWorldListener listener : activeWorld.listeners) {
            listener.release();
        }
        activeWorld = null;
    }



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

    /***************************************************************************
     * Existuje-li již nějaká instance světa robotů, zruší ji (deaktivuje).
     * Poté vytvoří svět s definovaným obsahem.
     *
     * @param fields Pole polí čísel reprezentujících jednotlivá políčka světa.
     *              Jednotlivá pole reprezentují řádky, hodnoty prvků definují
     *              počet značek (kladná čísla) nebo zeď (hodnota konstanty
     *              {@link #WALL}) na odpovídajícím políčku světa.
     *              Všechny řádky musejí být stejně dlouhé.
     * @return Svět se zadanými poli
     */
    private static RobotWorld createNewWorld(int[]... fields)
    {
        if (activeWorld != null) {
            destroyWorld();
        }
        synchronized(RobotWorld.class) {
            if (activeWorld == null) {
                activeWorld = new RobotWorld(fields);
                return activeWorld;
            }
        }
        String msg = "\nAktivní svět roboů již existuje a smí být jen jeden."
                   + "\nPřed vytvořením nového musíte zrušit ten původní";
        IO.inform("Vyhozena výjimka" + msg);
        throw new IllegalStateException (msg);
    }//public static void prepareWorld(int[][] world)


    /***************************************************************************
     * Převede pole stringů na odpovídající pole celočíselných polí
     * jejichž prvky definují obsah odpovídajících políček světa robotů.
     *
     * @param s Pole stringů reprezentujících jednotlivé řádky světa robotů.
     *          Jednotlivé znaky reprezentují odpovídající políčka
     *          a definují, jaký je zde počet značek či zda je tu zeď.
     *          Všechny stringy musejí být stejně dlouhé.
     * @return Pole celočíselných polí jejichž prvky definují obsah
     *         odpovídajících políček světa robotů
     */
    private static int[][] string2int(String[] s)
    {
        int rows = s.length;
        int cols = s[0].length();
        int[][] rc = new int[rows][cols];
        for (int row=0;   row < rows;   row++)
        {
            if (s[row].length() != cols)
            {
                String msg =
                    "\nZadaná reprezentace světa robotů není obdélníkové pole"
                  + "\nVšechny řádky musejí mít stejný počet polí, resp."
                  + "\nvšechny stringy musejí mít stejný počet znaků.";
//                    "\nThe given representation of robots' world "
//                    + "is not a rectangular array.\n"
//                    + "All rows must have the same number of fields, i.e.\n"
//                    + "all pattern strings must have the same length."    ;
            IO.inform("Vyhozena výjimka" + msg);
                throw new RuntimeException(msg);
            }
            for (int col=0;   col < cols;   col++) {
                rc[row][col] = c2i(s[row].charAt(col));
            }
        }
        return rc;
    }//public static int[][] string2int(String[] s)


    /***************************************************************************
     * Převede znak reprezentující počet značek nebo zeď
     * na odpovídající celé číslo.
     *
     * @param c Převáděný znak, přičemž se předpokládá, že znaky reprezentují
     *          čísla v soustavě o základu definovaným konstantou {@link #RADIX}
     *          a zeď jako znak definovaný konstantou {@link #WALL_CHAR}.
     *          Zadaný znak musí respektovat maximální přípustný počet značek
     *          na jednom políčku.
     * @returns Počet značek nebo příznak zdi
     */
    private static int c2i (char c)
    {
        int m;
        if(c == WALL_CHAR) {
            m = WALL;
        }
        else {
            m = Character.digit(c, RADIX);
            if ((m < 0) || (m > MAX_MARKERS)) {
                String msg = "\nUnexpected character \"" + c + "\"";
            IO.inform("Vyhozena výjimka" + msg);
                throw new RuntimeException(msg);
            }
        }
        return m;
    }//public static final int c2i (char c)



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

    /** Pole uchovávající počty značek na jednotlivých políčcích. */
    private final int[][] fields;

    /** Dimension of constructed world. */
    private final int cols, rows;

    /** Seznam robotů na dvorku. */
    private final Map<IKarel, WRobot> robots;

    /** Seznam robotů na dvorku. */
    private final Collection<WRobot> wrobots;

    /** Seznam posluchačů událostí ve světě robotů. */
    private final Collection<IRobotWorldListener> listeners;



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



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

    /***************************************************************************
     * Vytvoří nový svět s velikostí a počtem značek na jednotlivých polích
     * definovaných zadaným argumentem.
     *
     * @param fields Počet značek, resp. příznak zdi na jednotlivých polích
     */
    private RobotWorld(int[][] fields)
    {
        rows   = fields   .length;
        cols   = fields[0].length;
        robots = new LinkedHashMap<>();
        wrobots= robots.values();

        this.fields = new int[rows][cols];
        for (int row=0;   row < rows;   row++) {
            if (fields[row].length != cols) {
                throw new RuntimeException(
                   "\nZadaná reprezentace nedefinuje obdélníkový dvorek.");
            }
            this.fields[row] = fields[row].clone();
        }
        listeners = new ArrayList<>();
    }



//\IA== INSTANCE ABSTRACT METHODS ==============================================
//\IG== INSTANCE GETTERS AND SETTERS ===========================================

    /***************************************************************************
     * Vrátí počet sloupců daného světa.
     *
     * @return Počet sloupců daného světa
     */
    public int cols()
    {
        checkWorldAlive();
        return cols;
    }


    /***************************************************************************
     * Vrátí počet řádků daného světa.
     *
     * @return Počet řádků daného světa
     */
    public int rows()
    {
        checkWorldAlive();
        return rows;
    }


    /***************************************************************************
     * Vrátí pole s počty značek, resp. příznaky zdi na jednotlivých polích.
     *
     * @return Pole s počty značek, resp. příznaky zdi na jednotlivých polích
     */
    public int[][] fields()
    {
        checkWorldAlive();
        int[][] result = new int[rows][];
        for (int row=0;   row<rows;   row++) {
            result[row] = fields[row].clone();
        }
        return result;
    }



//\IM== INSTANCE REMAINING NON-PRIVATE METHODS =================================

    /***************************************************************************
     * Přidá zadaného posluchače.
     *
     * @param listener Přidávaný posluchač
     */
    public
    void addListener(IRobotWorldListener listener)
    {
        checkWorldAlive();
        listeners.add(listener);
    }


    /***************************************************************************
     * Odebere zadaného posluchače.
     *
     * @param listener Odebíraný posluchač
     */
    public
    void removeListener(IRobotWorldListener listener)
    {
        checkWorldAlive();
        listeners.remove(listener);
    }


    /***************************************************************************
     * Přidá zadaného robota mezi spravované a zobrazované, umístí jej
     * v levém dolním rohu otočeného na východ a přidělí mu implicitní barvu.
     *
     * @param robot Přidávaný robot
     */
    public
    void addRobot(IKarel robot)
    {
        addRobot(robot, rows-1, 0, DEFAULT_DIR, DEFAULT_COLOR);
    }


    /***************************************************************************
     * Přidá zadaného robota mezi spravované a zobrazované.
     *
     * @param robot Přidávaný robot
     * @param row   Vodorovná pozice přidávaného robota
     * @param col   Svislá    pozice přidávaného robota
     * @param dir   Směr natočení přidávanéhho robota
     */
    public
    void addRobot(IKarel robot, int row, int col, Direction dir)
    {
        addRobot(robot, row, col, dir, DEFAULT_COLOR);
    }


    /***************************************************************************
     * Přidá zadaného robota mezi spravované a zobrazované.
     *
     * @param robot Přidávaný robot
     * @param row   Vodorovná pozice přidávaného robota
     * @param col   Svislá    pozice přidávaného robota
     * @param dir   Směr natočení přidávanéhho robota
     * @param color Barva přidávanéhho robota
     */
    public
    void addRobot(IKarel robot, int row, int col, Direction dir, Color color)
    {
        checkWorldAlive();
        if (robots.containsKey(robot)) {
            throw new RobotException(robot,
                "Zadaný robot je již přítomen");
        }
        if (isFieldFree(row, col)) {
            var wrobot = new WRobot(robot, row, col, dir, color);
            robots.put(robot, wrobot);
//            System.out.println("Přidáno: robot=" + robot
//                             + ", wrobot=" + wrobot);
        }
        else {
            String block = (fields[row][col] == WALL)
                         ? "ZEĎ"
                         : activeWorld.wRobot(activeWorld.robotOn(row, col)).toString();
            System.out.println("Na poli [" + row + "," + col
                             + "] je " + block);
        }
        repaint();
    }


    /***************************************************************************
     * Odebere zadaného robota z množiny spravovaných a zobrazovaných.
     *
     * @param robot Odebíraný robot
     */
    public
    void removeRobot(IKarel robot)
    {
        checkWorldAlive();
        if (! robots.containsKey(robot)) {
            throw new RobotException(robot,
                "Zadaný robot není přítomen");
        }
        robots.remove(robot);
        repaint();
    }


    /***************************************************************************
     * Je-li daný svět aktuálním světem, umrtví jej.
     * V opačném případě neudělá nic, protože svět je již umrtven.
     */
    public void destroy()
    {
        if (activeWorld == this) {
            destroyWorld();
        }
    }


    /***********************************************************************
     * Vrátí informaci o tom, je-li na políčku pod robotem značka.
     *
     * @param robot Robot, jehož políčko se testuje
     * @return Je-li na poíčku značka, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public
    boolean isMarker(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        return (fields[wrobot.row][wrobot.col] > 0);
    }


    /***********************************************************************
     * Vrátí informaci o tom, je-li na políčku před robotem nějaký robot.
     *
     * @param robot Robot, před nímž se políčřko testuje
     * @return Je-li na poíčku robot, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public
    IKarel robotAhead(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        int c = wrobot.col + wrobot.dir.dx();
        int r = wrobot.row + wrobot.dir.dy();
        var result = robotOn(r, c);
        return result;
    }


//    /***********************************************************************
//     * Je-li na políčku před robotem nějaký robot, vrátí odkaz na něj.
//     *
//     * @param robot Robot, před nímž se políčřko testuje
//     * @return Je-li na poíčku robot, vrátí odkaz na něj,
//     *         jinak vrátí {@code null}
//     */
//    public
//    IKarel robotBefore(IKarel robot)
//    {
//        return robotAhead(robot);
//    }


    /***********************************************************************
     * Vrátí informaci o tom, je-li na políčku před robotem zeď
     * (byť okrajová ohraničující robotí dvorek).
     *
     * @param robot Robot, před nímž se políčřko testuje
     * @return Je-li na poíčku zeď, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public
    boolean isTurnedToEast(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        return (wrobot.dir == Direction.EAST);
    }


    /***********************************************************************
     * Vrátí informaci o tom, je-li na políčku před robotem zeď
     * (buť okrajová ohraničující robotí dvorek).
     *
     * @param robot Robot, před nímž se políčřko testuje
     * @return Je-li na poíčku zeď, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public
    boolean isWallBefore(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        int col = wrobot.col + wrobot.dir.dx();
        int row = wrobot.row + wrobot.dir.dy();
        return wallOn(row, col);
    }


    /***********************************************************************
     * Je-li na políčku robota alespoň jedna značka, zvedne ji,
     * není-li tam, vyuhodí výjimku.
     *
     * @param robot Robot zvedající značku
     * @throws RobotException nejsou-li pod robotem žádné značky
     */
    public
    void pick(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        int markers = fields[wrobot.row][wrobot.col];
        if (markers > 0) {
            fields[wrobot.row][wrobot.col] = markers - 1;
        }
        else {
            throw new RobotException(robot,
                    "\nNa poli nejsou žádné značky");
        }
    }


    /***********************************************************************
     * Položí na políčko pod zadaným robotem značku.
     * Je-li tam již plno, vyhodí výjimku.
     *
     * @param robot Robot pokládající značku
     * @throws RobotException na políčko pod robotem
     *                        se už žádná další značka nevejde
     */
    public
    void put(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        int markers = fields[wrobot.row][wrobot.col];
        if (markers < MAX_MARKERS) {
            fields[wrobot.row][wrobot.col] = markers + 1;
        }
        else {
            throw new RobotException(robot,
                    "\nNa pole již není možno přidat značku");
        }
    }


    /***************************************************************************
     * Přesune zadaného robota o políčko vpřed.
     *
     * @param robot Přesouvaný robot
     */
    public
    void step(IKarel robot)
    {
        moveBy(1, robot);
    }


    /***********************************************************************
     * Otočí zadaného robota o 90°vlevo.
     *
     * @param robot Otáčený robot
     */
    public
    void turnLeft(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        turnTo(wrobot.dir.turnLeft(), robot);
    }


    /***************************************************************************
     * Přestane robota zobrazovat, čímž jeho činnost výrazně zrychlí.
     *
     * @param robot Robot, který se má přestat zobrazovat
     * @return Odkaz na osloveného robota
     */
    public IKarel hide(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        wrobot.hidden++;
        return robot;
    }


    /***************************************************************************
     * Vrátí úroveň zobrazování do stavy před posledním skrytím.
     * Byl-li proto již tehdy skrytý (a tím pádem i zrychlený),
     * zůstane skrytý (a zrychlený) i nadále. Byl-li zobrazovaný,
     * začne se opět zobrazovat.
     *
     * @param robot Robot, jehož úroveň zobrazování se mění
     * @return Odkaz na osloveného robota
     */
    public IKarel unhide(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        if( --wrobot.hidden < 0 ) {
            throw new RobotException(robot,
                "\nZadanému odkrytí neodpovídá žádné zakrytí");
        }
        wrobot.show();
        return robot;
    }


    /***************************************************************************
     * Vrátí textový podpis světa zabrazující jeho rozměr.
     *
     * @return Textový podpis světa zabrazující jeho rozměr
     */
    public String toString(IKarel robot)
    {
        return wRobot(robot).toString();
    }


    /***************************************************************************
     * Oznámí všem přihlášeným posluchačům, že se svět změnil,
     * a je proto třeba jej překreslit.
     */
    public void repaint()
    {
        checkWorldAlive();
//        field2str("Repaint", fields, wrobots);
        for (IRobotWorldListener listener : listeners) {
            listener.repaint(fields(), wrobots);
        }
    }


    /***************************************************************************
     * Vrátí textový podpis světa zabrazující jeho rozměr.
     *
     * @return Textový podpis světa zabrazující jeho rozměr
     */
    @Override
    public String toString()
    {
        return "RobotWorld{rows=" + rows + ", cols=" + cols + '}';
    }


    /***************************************************************************
     * Je-li zadaný robot přihlášen, vrátí jeho zástupce,
     * který ví, jakou má barvu, kde je a kam je natočen.
     * Není-li přihlášen, vyhodí výjimku
     *
     * @param robot Robot, jehož přihlášení testujeme
     * @return Odkaz na zástupce zadaného robota
     * @throws IllegalArgumentException Není-li robot přihlášen
     */
    public
    WRobot wRobot(IKarel robot)
        throws IllegalArgumentException
    {
        checkWorldAlive();
        var wrobot = robots.get(robot);
        if (wrobot == null) {
            String msg = "\nZadaný robot není v aktuálním světě";
            IO.inform("Vyhozena výjimka\n" + msg);
            throw new IllegalArgumentException(msg);
        }
        return wrobot;
    }



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

    /***************************************************************************
     * Prověří, že daný svět je aktivní a že zadaná souřadnice je uvnitř něj.
     *
     * @param row   Vodorovná pozice testovaného robota
     * @param col   Svislá    pozice testovaného robota@t@
     * @throws IllegalArgumentException Zadaná pozice není uvnitř světa
     */
    private void checkInside(int row, int col)
    {
        checkWorldAlive();
        if (col<0  ||  row<0  ||  col>=cols  ||  row>=rows) {
            String msg = "\nPozice [" + row + "; " + col + "] není uvnitř světa robotů";
            IO.inform("Vyhozena výjimka" + msg);
            throw new IllegalArgumentException(msg);
        }
    }


    /***************************************************************************
     * Vrátí informaci o tom, je-li na zadaném poli zeď nebo nějaký robot.
     *
     * @param row   Vodorovná souřadnice testovaného pole
     * @param col   Svislá    souřadnice testovaného pole
     * @return {@code false} je-li na poli robot nebo zeď, jinak {@code false}
     */
    private //public
    boolean isFieldFree(int row, int col)
    {
        checkWorldAlive();
        var result = !((fields[row][col]  == WALL)  ||
                       (robotOn(row, col) != null)  );
        return result;
    }


    /***************************************************************************
     * Přesune zadaného robota o zadaný počet polí vpřed.
     *
     * @param robot Přesouvaný robot
     */
    private //public
    void moveBy(int distance, IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        int col = wrobot.col + distance*wrobot.dir.dx();
        int row = wrobot.row + distance*wrobot.dir.dy();
        moveTo(row, col, robot);
    }


    /***************************************************************************
     * Přesune zadaného robota na zadanou pozici.
     *
     * @param row   Cílový řádek
     * @param col   Cílový sloupec
     * @param robot Přesouvaný robot
     * @throws RobotException Nelze-li robota na zadanou pozici přesunout
     */
    private //public
    void moveTo(int row, int col, IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        if (col<0  ||  row<0  ||  col>=cols  ||  row>=rows  ||
           (fields[row][col] == WALL)  ||  (robotOn(row, col) != null))
        {
            throw new RobotException(robot,
                "\nNa pole [" + row + "; " + col + "] se nelze přesunout");
        }
        wrobot.col = col;
        wrobot.row = row;
        wrobot.show();
    }


    /***********************************************************************
     * Vrátí počet značek na políčku pod robotem.
     *
     * @param robot Robot, jehož pozici testujeme
     * @return Počet značek na políčku pod robotem
     */
    private //public
    int numMarkers(IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        return fields[wrobot.row][wrobot.col];
    }


    /***********************************************************************
     * Vrátí počet značek na zadaném políčku.
     *
     * @param row   Řádek   testovaného pole
     * @param col   Sloupec testovaného pole
     * @return Počet značek na testovaném poli
     * @throws IllegalArgumentException Zadaná pozice není uvnitř světa
     */
    private //public
    int markersOn(int row, int col)
    {
        checkInside(row, col);
        return fields[row][col];
    }


    /***************************************************************************
     * Vytiskne zadanou zprávu následovanou obsahem pole a informacemi
     * o zadaných robotech.
     *
     * @param message   Úvodní zpráva
     * @param fields    Pole s počty značek na jednotlivých políčcích
     * @param robots    Pole s odkazy na zástupce spravovaných robotů
     */
    private
    void field2str(String message, int[][] fields, Collection<WRobot> robots)
    {
        int rows = fields.length;
        System.out.println(message + ":\nFields.length = " + rows);
        for (int row=0;   row < rows;   row++) {
            int cols = fields[row].length;
            for (int col=0;   col < cols;   col++) {
                System.out.printf("%3s", fields[row][col]);
            }
            System.out.println();
        }
    }


    /***************************************************************************
     * Vrátí na robota na zadaném poli; není-li tam žádný, vrátí {@code null}.
     *
     * @param row   Vodorovná souřadnice testovaného pole
     * @param col   Svislá    souřadnice testovaného pole
     * @return {@code true} je-li na poli robot, jinak {@code false}
     * @throws IllegalArgumentException Zadaná pozice není uvnitř světa
     */
    private //public
    IKarel robotOn(int row, int col)
    {
        checkInside(row, col);
        for (var entry : robots.entrySet()) {
            var robot = entry.getValue();
            if (robot.row==row  &&  robot.col==col) {
                return entry.getKey();        //==========>
            }
        }
        return null;
    }


    /***********************************************************************
     * Otočí zadaného robota do zadaného směru.
     *
     * @param dir Cílový směr
     * @param robot Otáčený robot
     */
    private //public
    void turnTo(Direction dir, IKarel robot)
    {
        WRobot wrobot = wRobot(robot);
        wrobot.dir = dir;
        wrobot.show();
    }


    /***********************************************************************
     * Vrátí informaci, je-li na zadaném poli zeď.
     *
     * @param row   Vodorovná souřadnice testovaného pole
     * @param col   Svislá    souřadnice testovaného pole
     * @return Je-li na políčku zeď, vrátí {@code true},
     *         jinak vrátí {@code false}
     * @throws IllegalArgumentException Zadaná pozice není uvnitř světa
     */
    private //public
    boolean wallOn(int row, int col)
    {
        checkWorldAlive();
        if (col==-1  ||  row==-1  ||  col==cols  ||  row==rows) {
            return true;
        }
        checkInside(row, col);
        return fields[row][col] == WALL;
    }


    /***********************************************************************
     * Prověří, je-li zadaný svět ještě aktivní.
     *
     * @throws RuntimeException By-li daný svět již odstraněn
     */
    private void checkWorldAlive()
    {
        if (RobotWorld.activeWorld != this) {
            String msg = "\nDaný svět robotů byl již odstraněn";
            IO.inform("Vyhozena výjimka" + msg);
            throw new IllegalStateException(msg);
        }
    }



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

//    /***************************************************************************
//     * Značkovací interfejs, jehož implementací se třída hlásí k tomu,
//     * že její instance jsou schopny žít v robotím světě.
//     */
//    public interface IWRobot {}



//##############################################################################
//##############################################################################

    /***************************************************************************
     * Zástupce robota v robotím světě.
     */
    public class WRobot
    {
        private IKarel      robot;          //Zastupovaný robot
        private final Color color;
        private Direction   dir;            //Skutečné natočení
        private int         col, row;       //Zde je doopravdy
        private int         hidden;
        private int         vrow, vcol;     //Zda je zobrazován - visible
        private Direction   vdir;           //Zobrazované natočení
        private int         pause = PAUSE;    //Délka čekání po kroku a otočce
                                            //Zadává se v milisekundách



        /***********************************************************************
         * Vytvoří zástupce robota zadané barvy umístěného na zadaném místě
         * a otočeného do zadaného směru.
         *
         * @param col   Vodorovná souřadnice robota
         * @param row   Svislá souřadnice robota
         * @param dir   Směr natočení robota
         * @param color Barva robota
         */
        public WRobot(IKarel robot, int row, int col,
                      Direction dir, Color color)
        {
            this.robot = robot;
            this.row   = this.vrow = row;
            this.col   = this.vcol = col;
            this.dir   = this.vdir = (dir   == null ?  DEFAULT_DIR    :  dir);
            this.color = color     = (color == null ?  DEFAULT_COLOR  :  color);
        }


        /***********************************************************************
         * Vrátí odkaz na zastupovaného robota.
         *
         * @return Odkaz na zastupovaného robota
         */
        public IKarel robot()
        {
            return robot;
        }


        /***********************************************************************
         * Vrátí barvu daného robota.
         *
         * @return Barva daného robota
         */
        public Color color()
        {
            return color;
        }


        /***********************************************************************
         * Vrátí aktuální směr natočení daného robota.
         *
         * @return Aktuální směr natočení daného robota
         */
        public Direction dir()
        {
            return dir;
        }


        /***********************************************************************
         * Vrádí sloupec v němž se aktuálně daný robota nachází.
         *
         * @return Sloupec v němž se aktuálně daný robota nachází
         */
        public int col()
        {
            return col;
        }


        /***********************************************************************
         * Vrádí řádek v němž se aktuálně daný robota nachází.
         *
         * @return Řádek v němž se aktuálně daný robota nachází
         */
        public int row()
        {
            return row;
        }


        /***********************************************************************
         * Nastaví zobrazovací parametry zadaného robota,
         * a zobrazí aktuální stav světa robotů.
         */
        private void show()
        {
            if (hidden == 0) {
                vcol = col;
                vrow = row;
                vdir = dir;
                repaint();
                IO.pause(pause);
            }
        }


        /***********************************************************************
         * Vrátí textový podpis identifikující zastupovaného robota.
         *
         * @return Textový podpis identifikující zastupovaného robota
         */
        @Override
        public String toString()
        {
            var result = "{"
              + "row=" + row + ", col=" + col
              + ", dir=" + dir + ", color=" + color
              + (hidden == 0  ?  ""  :  ", h=" + hidden
                              +  ", vr=" + vrow + ", vc=" + vcol
                              +  ", vd=" + vdir)
              + '}';
            return result;
        }

    }



//##############################################################################
//##############################################################################

    /***************************************************************************
     *  Exceptions with default message containing information
     *  on robot's position and other characteristics.
     */
    @SuppressWarnings("serial")
    public static class RobotException extends RuntimeException
    {
        private RobotException(IKarel robot, String msg)
        {
            super("\n" + robot + "\n" + msg);
            String txt = "\n" + robot + "\n" + msg;
            IO.inform("Vyhozena výjimka" + txt);
        }
    }

}
