import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;


/*******************************************************************************
 * Třída Plátno slouží k jednoduchému kreslení na virtuální plátno.
 * <p>
 * Třída neposkytuje veřejný konstruktor, protože chce, aby její instance
 * byla jedináček, tj. aby se všechno kreslilo na jedno a to samé plátno.
 * Jediným způsobem, jak získat odkaz na instanci třídy Plátno,
 * je volaní statické metody <code>getPlátno</code>.</p>
 * <p>
 * Aby bylo možno na plátno obyčejné kreslit a nebylo nutno kreslené objekty
 * přihlašovat, odmazané časti obrazců se automaticky neobnovují.
 * Je-li proto při smazání některého obrazce odmazána část jiného obrazce,
 * je třeba příslušný obrazec explicitně překreslit.</p>
 *
 * @author     Rudolf Pecinovský
 * @version    2.01, duben 2004
 */

public class Plátno
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Počáteční šířka plátna v bodech. */
    public static final int ŠÍŘKA_0 = 300;

    /** Počáteční výška plátna v bodech. */
    public static final int VÝŠKA_0 = 300;

    /** Počáteční barva pozadí plátna. */
    public static final Barva POZADÍ_0 = Barva.KRÉMOVÁ;

    /** Titulek v záhlaví okna plátna. */
    private static final String TITULEK  = "Jednoduché plátno";



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** Jediná instance třídy Plátno. */
    private static Plátno jedináček;    //Jediná instance třídy plátno



//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    //Z venku neovlivnitelné Atributy pro zobrazeni plátna v aplikačním okně

        /** Aplikační okno animacniho plátna. */
        private JFrame okno;

        /** Instance lokální třídy, která je zřízena proto, aby odstínila
         *  metody svého rodiče JPanel. */
        private JPanel vlastníPlátno;

        /** Vše se kreslí na obraz - ten se snadněji překreslí. */
        private Image obrazPlátna;

        /** Kreslítko získané od obrazu plátna, na nějž se vlastně kreslí. */
        private Graphics2D kreslítko;


    //Přímo ovlivnitelné atributy
        private Barva barvaPozadí;
        private int šířka;
        private int výška;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ METODY TŘÍDY ======================================================

    /***************************************************************************
     * Smaže plátno, přesněji smaže všechny obrazce na plátně.
     * Tato metoda by měla býr definována jako metodoa instance,
     * protože je instance jedináček,
     * byla metoda pro snazší dostupnost definovaná jako metoda třídy.
     * Jinak by totiž bylo potřeba vytvořit před smazáním plátna jeho instanci.
     */
    public static void smaž()
    {
        if( jedináček == null ) return;
        jedináček.smaž(
            new Rectangle2D.Double(0, 0, jedináček.šířka, jedináček.výška) );
    }



//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Jediná metoda umožnující získat odkaz na instanci plátna.
     * Vrací vsak pokaždé odkaz na stejnou instanci, protože tato instance
     * je jedináček. Pokud instance při volaní metody ještě neexistuje,
     * metoda instanci vytvoří.
     *
     * @return Odkaz na instanci třídy Plátno.
     */
    public static Plátno getPlátno()
    {
        if(jedináček == null)
        {
            jedináček = new Plátno();
        }
        jedináček.okno.setVisible(true);
        return jedináček;
    }


    /***************************************************************************
     * Implicitní (a jediný) konstruktor - je volán pouze jednou.
     */
    private Plátno()
    {
        okno  = new JFrame();          //Vytvoří nové aplikační okno
        okno.setTitle(TITULEK);
        okno.setResizable( false );    //Není možné měnit rozměr myši
        okno.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        okno.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent evt) {
                System.exit(0);
            }
        });
        vlastníPlátno = new JPanel()
        {
            /** Povinně překrývaná abstraktní metoda třídy JPanel. */
            public void paint(Graphics g)
            {
                g.drawImage(obrazPlátna, 0, 0, null);
            }

        };
        okno.setContentPane(vlastníPlátno);
        barvaPozadí = POZADÍ_0;
        setRozměr(ŠÍŘKA_0, VÝŠKA_0);        //Připraví a vykreslí prázdné plátno
    }



//== PŘÍSTUPOVÉ METODY ATRIBUTU INSTANCÍ =======================================

    /***************************************************************************
     * Poskytuje informaci o aktuální viditelnosti okna.
     *
     * @return Je-li okno viditelné, vrací <b>true</b>, jinak vrací <b>false</b>
     */
    public boolean isViditelné()
    {
        return okno.isVisible();
    }


    /***************************************************************************
     * Nastaví viditelnost plátna.
     *
     * @param Viditelný logická hodnota požadované viditelnost (true=viditelné)
     */
    public void setViditelné(boolean viditelné)
    {
        boolean změna = (isViditelné() != viditelné);
        if( změna )
        {
            okno.setVisible(viditelné);
            if( viditelné )
            {
                okno.toFront();
                //překresli();
            }
        }
    }


    /***************************************************************************
     * Vrátí aktuální barvu pozadí.
     *
     * @return   Nastavena barva pozadí
     */
    public Barva getBarvaPozadí()
    {
        return barvaPozadí;
    }


    /***************************************************************************
     * Nastaví pro plátno barvu pozadí.
     *
     * @param barva  Nastavovaná barva pozadí
     */
    public void setBarvaPozadí(Barva barva)
    {
        barvaPozadí = barva;
        kreslítko.setBackground( barvaPozadí.getColor() );
        smaž();
    }


    /***************************************************************************
     * Nastaví pro plátno barvu popředí.
     *
     * @param  barva  Nastavovaná barva popředí
     */
    public void setBarvaPopředí(Barva barva)
    {
        kreslítko.setColor( barva.getColor() );
    }


    /***************************************************************************
     * Vrátí šířku plátna.
     *
     * @return  Aktuální šířka plátna v bodech
     */
    public int getŠířka()
    {
        return šířka;
    }


    /***************************************************************************
     * Vrátí výšku plátna.
     *
     * @return  Aktuální výška plátna v bodech
     */
    public int getVýška()
    {
        return výška;
    }


    /***************************************************************************
     * Nastaví nový rozměr plátna zadáním jeho výsky a šířky.
     *
     * @param  šířka  Nova šířka plátna v bodech
     * @param  výška  Nová výška plátna v bodech
     */
    public void setRozměr(int šířka, int výška)
    {
        boolean upravit;
        do
        {
            this.šířka = šířka;
            this.výška = výška;
            okno.setResizable(true);
            vlastníPlátno.setPreferredSize( new Dimension(šířka, výška) );
            okno.setMaximizedBounds( new Rectangle (šířka, výška) );
            okno.pack();
            java.awt.Dimension dim = okno.getSize( null );
            java.awt.Insets    ins = okno.getInsets();
//             P.zpráva("Nastavuju: šířka=" + šířka + ", výška=" + výška +
//                 "\nMám: width=" + dim.width + ", height=" + dim.height +
//                 "\nleft=" + ins.left + ", right=" + ins.right +
//                 "\n top=" + ins.top + ", bottom=" + ins.bottom );
            upravit = false;
            if( šířka < (dim.width - ins.left - ins.right) )
            {
                šířka = dim.width - ins.left - ins.right + 2;
                upravit = true;
            }
            if( výška < (dim.height - ins.top - ins.bottom) )
            {
                výška = dim.height - ins.top - ins.bottom;
                upravit = true;
            }
        }while( upravit );
        obrazPlátna = vlastníPlátno.createImage(šířka+2, výška+2);
        kreslítko = (Graphics2D)obrazPlátna.getGraphics();
        kreslítko.setBackground( barvaPozadí.getColor() );
        okno.setResizable(false);
        okno.setVisible(true);
        okno.toFront();
        připravObrázek();
        smaž();
    }



//== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ =================================
//== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY ===============================
//== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================

    /***************************************************************************
     * Převede instanci na řetězec. Používá se především při ladění.
     *
     * @return Řetězcová reprezentace dané instance.
     */
    public String toString()
    {
        return this.getClass().getName() +
            "(" + šířka + "×" + výška +
            " bodů, barvaPozadí=" + barvaPozadí + ")";
    }



//== NOVĚ ZAVEDENÉ METODY INSTANCÍ =============================================

    /***************************************************************************
     * Nakreslí zadaný obrazec a vybarví jej barvou popředí plátna.
     *
     * @param  obrazec  Kreslený obrazec
     */
    public void zaplň(Shape obrazec)
    {
        kreslítko.fill(obrazec);
        vlastníPlátno.repaint();
    }


    /***************************************************************************
     * Smaže zadaný obrazec na plátně; obrazec vsak stalé existuje,
     * jenom není vidět. Smaže se totiž tak, že se nakreslí barvou pozadí.
     *
     * @param  obrazec   Obrazec, který má byt smazán
     */
    public void smaž(Shape obrazec)
    {
        Color original = kreslítko.getColor();
        kreslítko.setColor(barvaPozadí.getColor());
        kreslítko.fill(obrazec);       //Smaže jej vyplněním barvou pozadí
        kreslítko.setColor(original);
        vlastníPlátno.repaint();
    }


    /***************************************************************************
     * Vypíše na plátno text aktuálním písmem a aktuální barvou popředí.
     *
     * @param  text   Zobrazovaný text
     * @param  x      x-ová souřadnice textu
     * @param  y      y-ová souřadnice textu
     * @param  c      Barva zadaná jako textový řetězec
     */
    public void kresliString(String text, int x, int y, Barva barva)
    {
        setBarvaPopředí(barva);
        kreslítko.drawString(text, x, y);
        vlastníPlátno.repaint();
    }


    /***************************************************************************
     * Nakresli na plátno úsečku se zadanými krajními body.
     * Usedku vykreslí aktuální barvou popředí.
     *
     * @param  x1    x-ová souřadnice počátku
     * @param  y1    y-ová souřadnice počátku
     * @param  x2    x-ová souřadnice konce
     * @param  y2    x-ová souřadnice konce
     * @param  barva Barva úsečky
     */
    public void kresliČáru(int x1, int y1, int x2, int y2, Barva barva)
    {
        setBarvaPopředí(barva);
        kreslítko.drawLine(x1, y1, x2, y2);
        vlastníPlátno.repaint();
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Nastaví viditelnost plátna.
     * Při nastavení viditelného plátna je přesune do popředí.
     * Lze ji proto použit i pro přesun již viditelného plátna do popředí.
     *
     * @param visible logická hodnota požadované viditelnost (true=viditelné)
     */
    private void připravObrázek()
    {
        obrazPlátna = vlastníPlátno.createImage(šířka, výška);
        kreslítko = (Graphics2D)obrazPlátna.getGraphics();
        kreslítko.setColor(barvaPozadí.getColor());
        kreslítko.fillRect(0, 0, šířka, výška);
        kreslítko.setColor(Color.black);
    }



//== VNOŘENÉ A VNITŘNÍ TŘÍDY ===================================================
//== TESTOVACÍ TŘÍDY A METODY ==================================================
}

