import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Trida Platno slouzi k jednoduchemu kresleni na virtualni platno.
 * Je soucasti projektu Tvary, jenz je startovacim projektem 
 * kurzu objektove orientovaneho programovani.
 *
 * <p>Trida neposkytuje verejny konstruktor, protoze chce, aby jeji
 * instance byla jedinacek, tj. aby se vsechny obrazce kreslily
 * na jedno a to same platno. Jedinym zpusobem, jak ziskat odkaz na instanci
 * tridy AnimPlatno, je volani staticke metody <code>getPlatno</code>.</p>
 *
 * <p>Aby bylo mozno na platno obycejne kreslit a nebylo nutno kreslene
 * tvary prihlasovat, byla ponechana jednoducha verze programu, 
 * ktera neobnovuje odmazane casti obrazcu. Je-li proto pri smazani
 * nektereho obrazce odmazana cast obrazce na nim ci pod nim, je treba
 * prislusny obrazec explicinte prekreslit.
 * 
 * @author     Rudolf Pecinovsky
 * @version    1.03, 3.1.2003
 */

public class Platno
{
//== VEREJNE KONSTANTY =========================================================

    public static final int SIRKA_0 = 300;
    public static final int VYSKA_0 = 300;


//== SOUKROME KONSTANTY ========================================================

    private static final Color POZADI_0 = new Color( 0xffffcc );


//== ATRIBUTY TRIDY ============================================================

    private static Platno jedinacek;    //Jedina instance tridy platno
    

//== ATRIBUTY INSTANCI =========================================================

    //Z venku neovlivnitelne Atributy pro zobrazeni platna v aplikacnim okne
    private JFrame frame;               //Aplikacni okno
    private PlatnoPane platno;          //Instance lokalni tridy
    private Graphics2D graphic;         //Kreslitko
    private Image obrazPlatna;
    
    //Atributy s pristupovymi metodami
    private Color barvaPozadi;
    private int sirka;
    private int vyska;


//== PRISTUPOVE METODY ATRIBUTU TRIDY ==========================================

    /**
     * Jedina metoda umoznujici ziskat odkaz na instanci platna.
     * Vraci vsak pokazde odkaz na stejnou instanci, protoze tato instance
     * je jedinacek. Pokud instance pri volani metody jeste neexistuje,
     * metoda instanci vytvori.
     *
     * @return instance tridy Platno
     */
    public static Platno getPlatno()
    {
        if(jedinacek == null)
        {
            jedinacek = new Platno("Platno", SIRKA_0, VYSKA_0, POZADI_0);
        }
        jedinacek.setViditelny(true);
        return jedinacek;
    }//public static Platno getPlatno()



//== OSTATNI METODY TRIDY ======================================================

    /**
     * Smaze platno, presneji smaze vsechny obrazce na platne.
     * Tato metoda je vlastne metodou instance, ale vzhledem k tomu,
     * ze instance je jedinacek, byla pro snazsi dostupnost
     * definovana jako metoda tridy.
     */
    public static void smaz()
    {
        if( jedinacek == null ) return;
        jedinacek.smaz(new Rectangle2D.Double(0, 0, 
            jedinacek.sirka, jedinacek.vyska));
    }//public static void smaz()
    

    /**
     * Prevede nazev barvy na prislusny objekt typu Color. Timto nazvem
     * muze byt jeden z nasledujicich nazvu barev:            <br>
     *      "cerna",   "modra",      "cervena",  "fialova",   <br>
     *      "zelena",  "blankytna",  "zluta",    "bila".      <br>
     *Vedle ceskych nazvu je metoda schopna rozpoznat i odpovidajici anglicke.
     *
     * <p>Dalsi moznosti je zadat barvu primo prostrednictvim hexadecimalni
     * (tj. v setnactkove soustave zapsane) hodnoty jednotlivych slozek - napr.:
     * cervena = "RGB:FF0000", zelena= "RGB:00FF00", modra = "RGB:0000FF",
     * cerna   = "RGB:000000", bila  = "RGB:FFFFFF", zluta = "RGB:FFFF00".</p>
     *
     * <p>Krome barev je mozne zadat i pruhlednost. Standardne zadavane barvy
     * jsou nepruhledne. Chceme-li spolu s intenzitou barevnych slozek zadat
     * i stupen pruhledosti, pouzejeme retezec ve tvaru "ARGB:AArrggbb",
     * kde AA je hexadecimalni hodnota stupne pruhlednosti:
     *     FF=pruhledny,  00=nepruhledny,  80=polopruhledny atd.
     *
     * @param  nazevBarvy  nova barva popredi zadana vyse popsanym zpusobem
     *                    
     * @return instance tridy Color reprezentujici zadanou barvu
     */
    public static Color prevedBarva(String nazevBarvy)
    {
        //Nejprve cesky bez hacku a carek
        if     (nazevBarvy.equals("cerna"))    return Color.black;
        else if(nazevBarvy.equals("modra"))    return Color.blue;
        else if(nazevBarvy.equals("cervena"))  return Color.red;
        else if(nazevBarvy.equals("fialova"))  return Color.magenta;
        else if(nazevBarvy.equals("zelena"))   return Color.green;
        else if(nazevBarvy.equals("blankytna"))return Color.cyan;
        else if(nazevBarvy.equals("zluta"))    return Color.yellow;
        else if(nazevBarvy.equals("bila"))     return Color.white;
        //A pak jeste jednou anglicky
        else if(nazevBarvy.equals("black"))    return Color.black;
        else if(nazevBarvy.equals("blue"))     return Color.blue;
        else if(nazevBarvy.equals("red"))      return Color.red;
        else if(nazevBarvy.equals("magenta"))  return Color.magenta;
        else if(nazevBarvy.equals("green"))    return Color.green;
        else if(nazevBarvy.equals("cyan"))     return Color.cyan;
        else if(nazevBarvy.equals("yellow"))   return Color.yellow;
        else if(nazevBarvy.equals("white"))    return Color.white;
        else if( nazevBarvy.startsWith("RGB:") )
            return new Color( 
                Integer.parseInt( nazevBarvy.substring( 4 ), 16 ) );
        else if( nazevBarvy.startsWith("ARGB:") )
            return new Color( 
                (int)Long.parseLong( nazevBarvy.substring( 5 ), 16 ), true );
        else
            throw new IllegalArgumentException(
                "Nepovolena barva: nazevBarvy=\"" + nazevBarvy + "\"" );
    }//public static Color prevedBarva(String nazevBarvy)


    /**
     * Prevede objekt typu Color na prislusny nazev barvy.
     * Neni-li pro tuto barvu k dipozici retezec s jejim nazvem (napr. "cerna"),
     * vrati retezec ve tvaru "ARGB:AARRGGBB", kde
     *    AA je hexadecimalni hodnota pruhlednosti (00=napruhledna),
     *    RR je hexadecimalni hodnota cervene,
     *    GG je hexadecimalni hodnota zelene a
     *    BB je hexadecimalni hodnota modre.
     *
     * @param  barva prevadena barva
     * 
     * @return retezec definujici zadanou barvu. 
     */
    public static String prevedBarva(Color barva)
    {
        if     (barva == Color.black)  return "cerna";
        else if(barva == Color.blue)   return "modra";
        else if(barva == Color.red)    return "cervena";
        else if(barva == Color.magenta)return "fialova";
        else if(barva == Color.green)  return "zelena";
        else if(barva == Color.cyan)   return "blankytna";
        else if(barva == Color.yellow) return "zluta";
        else if(barva == Color.white)  return "bila";
        else
            return "ARGB:" + Integer.toHexString(barva.getRGB());
    }//public static String prevedBarva(Color barva)



//== KONSTRUKTORY ==============================================================

    /**
     * Vytvori instanci tridy - je volana pouze jednou.
     *
     * @param title  nazev, ktery se objevi v titulkove liste okna
     * @param sirka  pozadovana sirka platna
     * @param vyska  pozadovana vyska platna
     * @param pozadi pozadovana barva pozadi
     */
    private Platno(String titulek, int sirka, int vyska, Color pozadi)
    {
        frame  = new JFrame();
        frame.setResizable( false );
        platno = new PlatnoPane();
        frame.setContentPane(platno);
        frame.setTitle(titulek);
        this.sirka = sirka;
        this.vyska = vyska;
        barvaPozadi = pozadi;
        platno.setPreferredSize(new Dimension(sirka, vyska));
        frame.pack();
    }//private Platno(String titulek, int sirka, int vyska, Color bgColour)



//== PRISTUPOVE METODY ATRIBUTU INSTANCI =======================================

    /**
     * Poskytuje informaci o aktualni viditelnosti okna.
     *
     * @return  je-li okno viditelne, vraci <b>true</b>, 
     *                          jinak vraci <b>false</b>
     */
    public boolean isViditelny()
    {
        return frame.isVisible();
    }//public boolean isViditelny()


    /**
     * Nastavi viditelnost platna. 
     * Pri nastaveni viditelneho platna je presune do popredi. 
     * Lze ji proto pouzit i pro presun jiz viditelneho platna do popredi.
     *
     * @param visible logicka hodnota pozadovane viditelnost (true=viditelne)
     */
    public void setViditelny(boolean viditelny)
    {
        if(graphic == null)
        {
            //Napoprve je treba vytvorit obrazek s barvou pozadi
            pripravObrazek();
        }
        //Zde je vlastni nastaveni viditelnosti okna
        frame.setVisible(viditelny);
        if( viditelny )   frame.toFront();
    }//public void setViditelny(boolean viditelny)
 

    /**
     * Vrati aktualni barvu pozadi.
     * 
     * @return   nastavena barva pozadi
     */
    public String getBarvaPozadi()
    {
        return prevedBarva( barvaPozadi );
    }//public Color getBarvaPozadi()


    /**
     * Nastavi pro platno barvu pozadi.
     * 
     * @param barva  nastavovana barva pozadi
     */
    public void setBarvaPozadi(String barva)
    {
        barvaPozadi = prevedBarva(barva);
        graphic.setBackground( barvaPozadi );
    }//public void setBarvaPozadi(Color barva)


    /**
     * Nastavi pro platno barvu popredi zadanim prislusneho retezce.
     * 
     * @param  nazevBarvy  nova barva popredi - lze si vybrat z hodnot:
     *                    "cerna",  "modra",     "cervena", "fialova",
     *                    "zelena", "blankytna", "zluta",   "bila".
     */
    public void setBarvaPopredi(String nazevBarvy)
    {
        graphic.setColor( prevedBarva(nazevBarvy) );
    }//public void setBarvaPopredi(String nazevBarvy)


    /**
     * Nastavi pro platno barvu popredi pomoci instance tridy Color.
     * 
     * @param  barva instance tridy Color charakterizujici nastavovanou barvu
     */
    public void setBarvaPopredi(Color barva)
    {
        graphic.setColor( barva );
    }//public void setBarvaPopredi(Color barva)


    /**
     * Vrati rozmer platna jako instanci tridy Dimension.
     * 
     * @return  aktualni instance tridy Dimension obsahujici rozmer platna
     */
    public Dimension getRozmer()
    {
        return platno.getSize();
    }//public Dimension getRozmer()


    /**
     * Vrati sirku platna.
     * 
     * @return  aktualni sirka platna v bodech
     */
    public int getSirka()
    {
        return sirka;
    }//public int getSirka()


    /**
     * Vrati vysku platna.
     * 
     * @return  aktualni vyska platna v bodech
     */
    public int getVyska()
    {
        return vyska;
    }//public int getVyska()


    /**
     * Nastavi novy rozmer platna zadanim jeho vysky a sirky.
     * 
     * @param  sirka  nova sirka platna v bodech
     * @param  vyska  nova vyska platna v bodech
     */
    public void setRozmer(int sirka, int vyska)
    {
        this.sirka = sirka;
        this.vyska = vyska;
        platno.setPreferredSize(new Dimension(sirka, vyska));
        frame.pack();
        pripravObrazek();
        smaz();
    }//public void setRozmer(int sirka, int vyska)


    /**
     * Nastavi rozmer platna prostrednictvim objektu typu Dimension.
     * 
     * @param  d instance tridy Dimension obsahujici rozmer platna 
     */
    public void setRozmer( Dimension d )
    {
        sirka = (int)d.getWidth();
        vyska = (int)d.getHeight();
        platno.setPreferredSize( d );
        frame.pack();
        pripravObrazek();
        smaz();
    }//public void setRozmer( Dimension d )
    
    

//== OSTATNI METODY INSTANCI ===================================================

    /**
     * Vybarvi zadany obrazec barvou popredi platna.
     * 
     * @param  obrazec  prebarvovana instance tridy Shape
     */
    public void zapln(Shape obrazec)
    {
        graphic.fill(obrazec);
        platno.repaint();
    }//public void zapln(Shape obrazec)


    /**
     * Smaze zadany obrazec na platne; obrazec vsak stale existuje.
     * Obrazec smaze tak, ze jej nakresli barvou pozadi.
     * 
     * @param  obrazec   instance tridy Shape, jejiz obraz ma byt smazan
     */
    public void smaz(Shape obrazec)
    {
        Color original = graphic.getColor();
        graphic.setColor(barvaPozadi);
        graphic.fill(obrazec);       //Smaze jej vyplnenim barvou pozadi
        graphic.setColor(original);
        platno.repaint();
    }//public void smaz(Shape obrazec)


    /**
     * Vypise na platno text aktualnim pismem a aktualni barvou popredi.
     *
     * @param  text   zobrazovany text
     * @param  x      x-ova souradnice textu.
     * @param  y      y-ova souradnice textu.
     * @param  c      barva zadana jako textovy retezec
     */
    public void kresliString(String text, int x, int y, String barva)
    {
        setBarvaPopredi(barva);
        graphic.drawString(text, x, y);
        platno.repaint();
    }//public void kresliString(String text, int x, int y)


    /**
     * Nakresli na platno usecku se zadanymi krajnimi body.
     * Usedku vykresli aktualni barvou popredi.
     * 
     * @param  x1   x-ova souradnice pocatku.
     * @param  y1   y-ova souradnice pocatku.
     * @param  x2   x-ova souradnice konce.
     * @param  y2   x-ova souradnice konce.
     * @param  c      barva zadana jako textovy retezec
     */
    public void kresliCaru(int x1, int y1, int x2, int y2, String barva)
    {
        setBarvaPopredi(barva);
        graphic.drawLine(x1, y1, x2, y2);
        platno.repaint();
    }//public void kresliCaru(int x1, int y1, int x2, int y2)


    /**
     * Pocka zadany pocet milisekund - pouziva se pri animacich.
     * 
     * @param  milliseconds  pocet milisekund, ktere je treba pockat
     */
    public void cekej(int milisekund)
    {
        try { Thread.sleep(milisekund); }
        catch (Exception e) {} //Prozatim mozne vyvolani vyjimky ignorujeme
    }//public void cekej(int milisekund)

    

//== SOUKROME A POMOCNE METODY =================================================
    
    /**
     * Nastavi viditelnost platna. 
     * Pri nastaveni viditelneho platna je presune do popredi. 
     * Lze ji proto pouzit i pro presun jiz viditelneho platna do popredi.
     *
     * @param visible logicka hodnota pozadovane viditelnost (true=viditelne)
     */
    private void pripravObrazek()
    {
        Dimension size = platno.getSize();
        obrazPlatna = platno.createImage(size.width, size.height);
        graphic = (Graphics2D)obrazPlatna.getGraphics();
        graphic.setColor(barvaPozadi);
        graphic.fillRect(0, 0, size.width, size.height);
        graphic.setColor(Color.black);
    }//public void setViditelny(boolean viditelny)
    

    
//== VNORENE A VNITRNI TRIDY ===================================================

    /************************************************************************
     * Vnitrni trida PlatnoPane predstavuje skutecne Platno v danem okne.
     * Je to vlastne JPanel s pridanou schopnosti obnovy obrazku, ktery je
     * na nem nakreslen.
     * <p>Trida je definovana jako vnitrni, aby uzivatel nebyl zbytecne
     * rozptylovan zaplavou metod zdedenych od predku z knihovny swing.
     */
    private class PlatnoPane extends JPanel
    {
        /**
         * Povinne prekrekryvana abstraktni metoda tridy JPanel.
         */
        public void paint(Graphics g)
        {
            g.drawImage(obrazPlatna, 0, 0, null);
        }//public void paint(Graphics g)

    }//private class PlatnoPane extends JPanel


//== TESTOVACI TRIDY A METODY ==================================================
    
    public static class Test
    {
        public static void test()
        {
            Platno platno = getPlatno();

            platno.setRozmer(400,400);

            //Test prevodu barev
            String sb;
            Color  cb;

            sb = "RGB:0000FF";
            cb = platno.prevedBarva(sb);
            System.out.println(sb+"=" + cb + ", alpha=" + cb.getAlpha());

            sb = "ARGB:800000FF";
            cb = platno.prevedBarva(sb);
            System.out.println(sb+"=" + cb + ", alpha=" + cb.getAlpha());

            cb = Color.gray;
            sb = platno.prevedBarva(Color.gray);
            System.out.println("Barva " + cb + " = " + sb );

        }//public static void test()
        
    }//public static class Test


}//public class Platno