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.awt.*;

import javax.swing.*;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Collection;

import robotcz.RobotWorld.WRobot;



/*******************************************************************************
 * Třída zodpovědná za zobrazení robotího dvorku a jednotlivých robotů.
 * Zobrazovaní roboti musejí být instancemi třídy {@link RobotWorld.WRobot}.
 * Při vytváření zobrazovací instance se nejprve vytvoří instance světa robotů,
 * u které se pak zobrazovací instance zaregistruje jako posluchač.
 *
 * The class responding for painting robots' world and its parts on the screen.
 * This class knows how many markers is there possible to put on the single
 * field, and which colors of robots are allowed.
 * The class acts as singleton - it allows only one robots' world to exist.
 *
 * @author   Rudolf PECINOVSKÝ
 * @version 1.05.9267_2023-03-11
 */
public  class       RobotWindow
        implements  IRobotWorldListener
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============

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

    /** The offset of area for painting from frame borders. */
    private static int xL=4, xR=4, yT=23, yB=4;

    /** The path to the files with look of the field
     *  with particular number of markers. */
    private static final String FIELDS_ICON_DIRECTORY = "IMGF/";

    /** The path to the files with look of the robot
     *  turned in particular direction. */
    private static final String ROBOTS_ICON_DIRECTORY = "IMGR/";

    /** Array of file names of icons representing particular numbers
     *  of markers on the field.  */
    private static final String [] MARKER_ICON_FILENAMES =
    {
        "0.gif",
        "1.gif",
        "2.gif",
        "3.gif",
        "4.gif",
        "5.gif",
        "6.gif",
        "7.gif",
        "8.gif",
        "9.gif",
        "C0.gif",
        "C1.gif",
        "C2.gif",
        "C3.gif",
        "C4.gif",
        "C5.gif",
        "C6.gif",
        "C7.gif",
        "C8.gif",
        "C9.gif",
        "BrickWall.gif"
    };//protected static final String [] markerIconFileName =

    /** Characters representing robot paint in particular color.
     *  Size of this array determines the posible amount of different robots. */
    private static final String[] ROBOTS_COLOR_CHARS =
    {
        "K", // 000 = Black
        "B", // 001 = Blue
        "R", // 010 = Red
        "M", // 011 = Magenta
        "G", // 100 = Green
        "C", // 101 = Cyan
        "Y", // 110 = Yellow
        "W"  // 111 = White
    };//protected static String[] robotsColorChar =

    /** The final parts of file name with look of the robot
     *  turned in particular direction. */
    private static final String [] DIRECTION_EXTENSIONS =
    {
        "_EAST.gif",
        "_NORTH.gif",
        "_WEST.gif",
        "_SOUTH.gif"
    };//protected static String [] directionFileName =

    /** The size of ikon representing single field. */
    private static final int ICON_SIZE   = 32;

    /** Number of possible robot colors. */
    private static final int COLORS;

    /** Number of possible robot directions. */
    private static final int DIRECTIONS;

    /** Max allowed number of markers on the single field. */
    private static final int MARKERS;

    /** The number representing presence of wall on the given field. */
    private static final int WALL_INDEX;

    /** Indikace zdi používaná ve světě robotů. */
    private static final int WORLD_WALL;

    /** Array with robots icon for every possible color
     * and every possible direction of reprezented robot. */
    private static final ImageIcon[] ROBOT_LOOKS;

    /** Icons with the looks of field with particular number of markers. */
    private static final ImageIcon[] FIELD_LOOKS;



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

    /** Jediná instance dané třidy. */
    private static RobotWindow window;



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

    static {
        COLORS      = ROBOTS_COLOR_CHARS.length;
        DIRECTIONS  = DIRECTION_EXTENSIONS.length;
        MARKERS     = MARKER_ICON_FILENAMES.length;
        WALL_INDEX  = MARKERS-1;
        WORLD_WALL  = RobotWorld.WALL;
        FIELD_LOOKS = prepareFields();
        ROBOT_LOOKS = prepareRobots();

        if (MARKERS < RobotWorld.MAX_MARKERS-1) {
            String msg = "\nProti požadavku světa robotů není definován "
                       + "dostatečný počet ikon"
                       + "\npro reprezentace položených značek.";
            IO.inform("Vyhozena výjimka" + msg);
            throw new RuntimeException(msg);
        }
    }



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

    /***************************************************************************
     * Vrátí aktuální instanci okna světa robotů;
     * pokud žádná neexistuje, vrátí {@code null}.
     *
     * @return Aktuální instance okna světa robotů nebo {@code null}
     */
    public static RobotWindow window()
    {
        return window;
    }


    /***************************************************************************
     * Pokud neexistuje aktivní instance světa robotů, vytvoří svět
     * s implicitním počtem řádků a sloupců s prázdnými poli a současně
     * otevře okno, které bude tento svět vizuálně reprezentovat.
     * Pokud aktivní svět existuje, vyhodí IllegalStateException.
     *
     * @return Svět s implicitním počtem řádků a sloupců s prázdnými poli
     * @throws IllegalStateException pokud existuje aktivní svět
     */
    public static RobotWorld newWorld()
    {
        return newWindowWorld(RobotWorld.createWorld());
    }


    /***************************************************************************
     * Pokud žádná aktivní instance světa robotů neexistuje, vytvoří svět
     * se zadaným počtem řádků a sloupců s prázdnými poli a současně
     * otevře okno, které bude tento svět vizuálně reprezentovat.
     * Pokud aktivní svět existuje, vyhodí IllegalStateException.
     *
     * @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ů s prázdnými poli
     * @throws IllegalStateException pokud existuje aktivní svět
     */
    public static RobotWorld newWorld(int rows, int cols)
    {
        return newWindowWorld(RobotWorld.createWorld(new int[rows][cols]));
    }


    /***************************************************************************
     * Pokud žádná aktivní instance světa robotů neexistuje, vytvoří svět
     * se zadaným počtem řádků a sloupců s definovaným obsahem a současně
     * otevře okno, které bude tento svět vizuálně reprezentovat.
     * Pokud aktivní svět existuje, vyhodí IllegalStateException.
     *
     * @param world Vektor stringů reprezentujících jednotlivé řádky.
     *              Jednotlivé znaky reprezentují dopovídající pole
     *              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
     * @throws IllegalStateException pokud existuje aktivní svět
     * @see robotcz.RobotWorld#WALL_CHAR
     */
    public static RobotWorld newWorld(String... world)
    {
        return newWindowWorld(RobotWorld.createWorld(world));
    }


    /***************************************************************************
     * Neexistuje-li žádná aktivní instance světa robotů,
     * vytvoří svět se zadaným počtem řádků a sloupců s definovaným obsahem
     * a současně otevře okno, které bude tento svět vizuálně reprezentovat.
     * Pokud aktivní svět existuje, vyhodí IllegalStateException.
     *
     * @param fields Pole polí čísel reprezentujících jednotlivá pole světa.
     *              jednotlivá pole reprezentují řádky, hodnoty prvků defují
     *              počet značek (kladná čísla) nebo zeď (říslo -1)
     *              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 newWorld(int[]... fields)
    {
        return newWindowWorld(RobotWorld.createWorld(fields));
    }



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

    /***************************************************************************
     * Vytvoří grafickou reprezentaci světa robotů zadaného jako argument.
     *
     *  @param world Svět robotů, jehož grafická reprezentace se má vytvořit
     *  @return Grafická reprezentace zadaného světa robotů
     *  @throws  IllegalStateException pokud již takové reprezentace existuje
     */
    private static RobotWorld newWindowWorld(RobotWorld world)
    {
        window = new RobotWindow(world);
        try {
            SwingUtilities.invokeAndWait(() -> {window.frame.repaint();} );
        } catch (InterruptedException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return world;
    }


    /***************************************************************************
     * Připraví ikony reprezentující pole se značkami
     * pro všechna následně vytvořená okna.
    */
    private static ImageIcon[] prepareFields()
    {
        ImageIcon[] result = new ImageIcon[MARKERS+2];
        ClassLoader cl     = RobotWindow.class.getClassLoader();
        URL url;
        for (int m=0;   m < MARKER_ICON_FILENAMES.length;   m++) {
            String name = FIELDS_ICON_DIRECTORY + MARKER_ICON_FILENAMES[m];
            url = cl.getResource(name);
            if (url == null) {
                String msg =  "\nFile \"" + name + "\" not found";
                IO.inform("Vyhozena výjimka" + msg);
                throw  new RuntimeException(msg);
            }
            result[m] = new ImageIcon(url, name);
        }
        return result;
    }//protected static void prepareFields()


    /***************************************************************************
     * Připraví ikony reprezentující vybarvené a natočené roboty
     * pro všechna následně vytvořená okna.
     * Index pozice obrazu robota se spočte podle vzorce:<br>
     * {@code barev*číslo_směru + číslo_barvy}.
     */
    private static ImageIcon[] prepareRobots()
    {
        String name0, name;
        //Předpokládá, že počet barev i počet směrů jsou mocniny dvou
        ImageIcon[] result = new ImageIcon[COLORS*DIRECTIONS];
        //System.out.println("robotLook.len = " + result.length);
        ClassLoader cl     = RobotWindow.class.getClassLoader();
        URL url;
        for (int color=0, cd=0;   color < COLORS;   color++) {
            name0 = ROBOTS_ICON_DIRECTORY + ROBOTS_COLOR_CHARS[color];
            for (int dir=0;  dir < DIRECTIONS;  dir++, cd++) {
                name = name0 + DIRECTION_EXTENSIONS[dir];
                url = cl.getResource(name);
                if (url == null) {
                    System.out.println("name=" + name + ";   Url=" + url);
                    String msg = "\nFile \"" + name + DIRECTION_EXTENSIONS[dir]
                               + "\" not found";
                    IO.inform("Vyhozena výjimka" + msg);
                    throw  new RuntimeException(msg);
                }
                result[cd] = new ImageIcon(url);
            }//for dir
        }//for color
        return result;
    }//protected static void prepareRobots()



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

    /** Odkaz na svět, u nějž je přihlášen jako posluchač. */
    private final RobotWorld world;

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

    /** Okno, v němž je svět vybudován a zobrazován. */
    private final RFrame frame;



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

    /** Every field is represented by one label.
     *  Neníá konstantní, aby se mohlo při překreslování nahradit
     *  celé pole a nemuselo se kopírovat po řádcích. */
    private int[][] fields;



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

    /***************************************************************************
     * Vytvoří grafickou reprezentaci zadaného světa.
     */
    private RobotWindow(RobotWorld world)
    {
        this.world  = world;
        this.rows   = world.rows();
        this.cols   = world.cols();
        this.fields = world.fields();
        frame = new RFrame();
        world.addListener(this);
    }



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

    /***************************************************************************
     *  Returns the title of the window with the robots' world.
     *
     *  @return The title of the window with the robots' world.
     */
    public String getTitle()
    {
        return frame.getTitle();
    }


    /***************************************************************************
     *  Sets the title of the window with the robots' world.
     *  @param title The set title of the window with the robots' world.
     */
    public void setTitle(String title)
    {
        frame.setTitle(title);
    }


    /***************************************************************************
     * Vrátí instanci světa robotů reprezentovanou daným oknem.
     *
     * @return Instance světa robotů reprezentovaná daným oknem
     */
    public RobotWorld getWorld()
    {
        return world;
    }



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

    /***************************************************************************
     * Hlášení o změně ve světě robota vyžadující jeho překreslení.
     *
     * @param fields    Počty značek na jednotlivých polích
     * @param robots    Roboti vyskytující se v daném světě
     */
    @Override
    public void repaint(int[][] fields, Collection<WRobot> robots)
    {
        if ((fields.length != rows)  ||  (fields[0].length != cols)) {
            String msg = "\nZadané pole nemá požadovaný rozměr ["
                       + rows + ", " + cols + "]";
            IO.inform("Vyhozena výjimka" + msg);
            throw new RuntimeException(msg);
        }

//        prInt2("repaint - previous", this.fields);
//        prInt2("repaint - input",    fields);

        //Roboti se zanesou do pole indexy svých ikon zvětšenými o 1,
        //aby nekolidovaly s prázdným polem
        for (WRobot robot : robots) {
            fields[robot.row()][robot.col()] =
                -( robot.color().ordinal() * DIRECTIONS
                 + robot.dir()  .ordinal() + 1);
        }

//        prInt2("repaint - po zanesení robotů",  fields);

//        System.out.println("Přepsaná pole");
//        for (int row=0;   row < rows;   row++) {
//            int cols = fields[row].length;
//            for (int col=0;   col < cols;   col++) {
//                System.out.printf("%4s", fields[row][col]);
//            }
//            System.out.println();
//        }

        //Ve změněných polích se vymění ikony
        for (int row=0;   row < rows;   row++) {
            for (int col=0;   col < cols;   col++) {
                if (fields[row][col] != this.fields[row][col]) {
//                    System.out.println("Vyměňuji ikonu na pozici ["
//                                     + row + ", " + col + "]");
                    int f = fields[row][col];
                    frame.labels[row][col].setIcon(
                                f >= 0  ?  FIELD_LOOKS[ f]
                                        :  ROBOT_LOOKS[-f-1]
                    );
                }
            }
        }
        this.fields = fields;   //Zapamatuje si novou podobu pro příště
        try {
            SwingUtilities.invokeAndWait(() -> {window.frame.repaint();} );
        }
        catch (InterruptedException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }


    /***************************************************************************
     * Oznámení o konci světa robota a z toho plynoucí povinnosti
     * uvolnit blokované zdroje.
     */
    @Override
    public void release()
    {
        frame.dispose();
    }


    /***************************************************************************
     * 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 "RobotWindow{rows=" + rows + ", cols=" + cols + '}';
    }



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

    /***************************************************************************
     *  Class responding for creating of window for the robot world.
     */
    @SuppressWarnings("serial")
    private class   RFrame
            extends JFrame
    {
        /** Every field is represented by one label. */
        private final JLabel[][] labels;

        /***********************************************************************
         *  Constructor creating the empty window.
         */
        private RFrame()
        {
            super("Svět robotů");

            labels = new JLabel[rows][cols];
            for (int row=0;   row<rows;   row++) {
                for (int col=0;   col<cols;   col++) {
                    add(labels[row][col] = new JLabel(
                        FIELD_LOOKS[fields[row][col]]) );
                }
            }

            setLayout(new GridLayout(rows, cols));
            setSize(cols*ICON_SIZE + xL + xR,
                    rows*ICON_SIZE + yT + yB);
            setLocation(windowLocation);
            setVisible(true);
            setAlwaysOnTop(true);
            checkInsets();
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            addWindowListener(new WindowAdapter() {
                @Override
                public void windowDeactivated(WindowEvent e) {
                    windowLocation = getLocation();
                }
            });
        }//public RFrame()


        /***********************************************************************
         * Úprava používaná u systémů s tlustšími rámy oken.
         */
        private void checkInsets()
        {
            /* Test of the thickness of the border.
            * If the thickness is not equal to the expected one,
            * the field can not precisely fit into prepared area.
            */
            Insets insets = getInsets();
            if (insets.left != xL  || insets.right  != xR  ||
                insets.top  != yT  || insets.bottom != yB  )
            {
                xL = insets.left;
                xR = insets.right;
                yT = insets.top;
                yB = insets.bottom;
                Dimension d = new Dimension(cols*ICON_SIZE + xR + xL,
                    rows*ICON_SIZE + yT + yB);
                /*+TEST+*
                int xSize = xMax*ICON_SIZE + xL + xR;
                int ySize = yMax*ICON_SIZE + yT + yB;
                System.err.println(
                "New size: xSize=" + xSize + ", ySize=" + ySize +
                ", xL=" + xL + ", xR=" + xR + ", yT=" + yT + ", yB=" + yB);
                /*-*/
                setSize(d);
            }//if other insets
        }

    }//protected class RFrame extends JFrame



}//public class RWindow
