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;

import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 * Trida AnimPlatno slouzi k jednoduchemu kresleni na virtualni platno
 * a pripadne nasledne animaci nakreslenych obrazku.
 * Trida neposkytuje verejny operator <b><code>new</code></b>, protoze chce,
 * aby jeji instance byla jedinacek, tj. aby se vsechny obrazce kreslily
 * na jedno a to same platno.
 *
 * <p>Jedinym zpusobem, jak ziskat odkaz na instanci tridy AnimPlatno,
 * je volani staticke metody <code>getPlatno</code>.</p>
 * 
 * <p>Tato trida jiz dohlizi na to, aby se posouvane obrazce navzajem
 * neodmazavaly. Aby to mohla ohlidat, musi se kazdy obrazec prihlasit
 * k tomu, aby byl na platne kreslen (sada metod <code>pridej...</code>). 
 * Neprihlaseny obrazec se nekresli.</p>
 * 
 * <p>Aby se obrazec mohl prihlasit k zobrazovani na platno, musi 
 * implementovat rozhrani <code>IKresleny</code>.</p>
 * 
 * <p>Obrazec, ktery jiz dale nema byt kreslen se muze odhlasit zavolanim
 * metody <code>odeber(IKresleny)</code>.</p>
 * 
 * @author     Rudolf Pecinovsky
 * @version    1.02, 16.1.2003
 */

public class AnimPlatno
{
//== VEREJNE KONSTANTY =========================================================

    public static final int SIRKA = 300;
    public static final int VYSKA = 300;



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

    private static final Barva POZADI_0 = new Barva( 0xff, 0xff, 0xcc );



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

    private static AnimPlatno jedinacek;    //Jedina instance tridy AnimPlatno



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

    //Z venku neovlivnitelne Atributy pro zobrazeni platna v aplikacnim okne
    
        /** Aplikacni okno animacniho platna. */
        private JFrame frame;                   
        
        /** Instance lokalni tridy, ktera je zrizena proto, aby odstinila
         *  metody sveho rodice JPanel. */ 
        private VlastniPlatno vlastniPlatno;    
        
        /** Vse se kresli na obraz - ten se snadneji prekresli. */
        private Image obrazPlatna;
    
        /** Kreslitko ziskane od obrazu platna, na nejz se vlastne kresli. */
        private Graphics2D kreslitko;
        
        /** Obdelnik kresleny na pozadi. */
        private Rectangle2D pozadi;
        
        /** Semafor branici prilis castemu prekreslovani. Prekresluje se pouze 
         *  je-li ==0. Nesmi byt <0. */
        private int nekreslit = 0;

        /** Priznak toho, ze v dobe zakazaneho kresleni prisel pozadavek
         *  na prekresleni platna */
        private boolean kreslit = false;
        
        
    //Primo ovlivnitelne atributy
        private Barva barvaPozadi;
        private int sirka;
        private int vyska;

    //Seznam zobrazovanych predmetu
        List predmety = new LinkedList();



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

    /**
     * Jedina metoda umoznujici ziskat odkaz na instanci platna.
     * Vraci vsak pokazde odkaz na stejnou instanci,
     * protoze instance platna je jedinacek.
     * <p>Pokud instance pri volani metody jeste neexistuje,
     * metoda instanci vytvori.</p>
     *
     * @return instance tridy AnimPlatno
     */
    public static AnimPlatno getPlatno()
    {
        if(jedinacek == null)
        {
            jedinacek = new AnimPlatno("Animacni platno",
                                       SIRKA, VYSKA, POZADI_0);
        }
        jedinacek.setViditelny(true);
        return jedinacek;
    }//public static AnimPlatno getPlatno()



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


//== 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 AnimPlatno(String titulek, int sirka, int vyska, Barva pozadi)
    {
        frame  = new JFrame();          //Vytvori nove aplikacni okno
        frame.setTitle(titulek);
        frame.setResizable( false );    //Neni mozne menit rozmer mysi
        vlastniPlatno = new VlastniPlatno();      //Hlavnim kontejnerem aplikacniho okna
        frame.setContentPane(vlastniPlatno);   //  bude platno
        barvaPozadi = pozadi;
        setRozmer(sirka, vyska);        //Pripravi a vykresli prazdne platno
    }//private AnimPlatno(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.
     *
     * @param viditelny logicka hodnota pozadovane viditelnost (true=viditelne)
     */
    public void setViditelny(boolean viditelny)
    {
        boolean zmena = (isViditelny() != viditelny);
        if( zmena )
        {
            frame.setVisible(viditelny);
            if( viditelny )  prekresli();
        }
    }//public void setViditelny(boolean viditelny)


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


    /**
     * Nastavi pro platno barvu pozadi.
     *
     * @param  nazevBarvy  nastavovana barva pozadi - 
     * lze zadat nektery z nazvu evidovanych ve tride Barva nebo
     * obecnou charakteristiku zadavanou retezci ve tvaru
     * "RGB:RRGGBB" nebo "ARGB:AARRGGBB" - podrobnosti
     * viz trida Barva.
     */
    public void setBarvaPozadi(String nazevBarvy)
    {
        barvaPozadi = Barva.getBarva( nazevBarvy );
        kreslitko.setBackground( barvaPozadi );
        prekresli();
    }//public void setBarvaPozadi(String nazevBarvy)


    /**
     * Nastavi pro platno barvu pozadi.
     *
     * @param barva  instance tridy Barva charakterizujici nastavovanou barvu
     */
    public void setBarvaPozadi(Barva barva)
    {
        barvaPozadi = barva;
        kreslitko.setBackground( barvaPozadi );
        prekresli();
    }//public void setBarvaPozadi(Color barva)


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


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


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


    /**
     * Nastavi rozmer platna zadanim jeho vysky a sirky.
     *
     * @param  sirka  nova sirka
     * @param  vyska  nova vyska
     */
    public void setRozmer(int sirka, int vyska)
    {
        setRozmer(new Dimension(sirka, vyska));
    }//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 = d.width;
        vyska = d.height;
        vlastniPlatno.setPreferredSize( d );
        frame.pack();
        obrazPlatna = vlastniPlatno.createImage(sirka, vyska);
        kreslitko = (Graphics2D)obrazPlatna.getGraphics();
        kreslitko.setBackground( barvaPozadi );
        pozadi = new Rectangle2D.Double(0, 0, sirka, vyska);
        prekresli();
    }//public void setRozmer( Dimension d )

    
    /**
     * Pomocna metoda pro ucely ladeni aby bylo mozno zkontrolovat,
     * ze na konci metody ma semafor stejnou hodnotu, jako mel na pocatku.
     *
     * @return  stav vnitrniho semaforu: >0  - nebude se kreslit,<br>
     *                                   ==0 - kresli se,<br>
     *                                   <0  - chyba
     */
    public int getNekresli()
    {
    /*+ Protoze se hodnota promenne snizuje pouze v metode vratKresli()
     *  a ta podteceni kontroluje, nemelo by nastat.
     *  Proto je zde hlidani nepripustnho stavu vymaskovano *
        if( nekresli < 0 )
            throw new IllegalStateException(
                "Vnitrni semafor nesmi mit nikdy zapornou hodnotu!" );
    /*-*/
        return nekreslit;
    }//public int getNekresli()



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

    /**
     * Vykresli vsechny elementy.
     */
    public void prekresli()
    {
        if( (nekreslit == 0)  &&  isViditelny() )
        {
            kreslit = false;    //Vse, co se ma prekreslit, bude prekresleno
            kreslitko.setColor(barvaPozadi);
            kreslitko.fill(pozadi);
            ListIterator it = predmety.listIterator();
            while(it.hasNext()) {
                ((IKresleny)it.next()).kresli( kreslitko );
            }
            vlastniPlatno.repaint();
        }
        else
            kreslit = true;     //Nevyhoveli jsme zadosti o prekresleni
    }//private void prekresli()
    
    
    /**
     *  Potlaci prekreslovani platna.
     */
    public void nekresli()
    {
        nekreslit++;
    }//public void nekresli()
    
    
    /**
     *  Vrati semafor do stavu pred poslednim potlacenim kresleni.
     */
    public void vratKresli()
    {
        if( nekreslit == 0 )
            throw new IllegalStateException(
                "Vraceni do stavu kresleni musi prechazet zakaz!" );
        nekreslit--;
        if( (nekreslit == 0)  &&  kreslit )
            prekresli();
    }//public void vratKresli()


    /**
     * Odstrani zadany obrazec ze seznamu malovanych.
     * Byl-li obrazec v seznamu, prekresli vlastniPlatno.
     *
     * @param  obrazec  odstranovany obrazec
     *
     * @return  true v pripade, kdyz obrazec v seznamu byl,
     *          false v pripade, kdyz nebylo co odstranovat
     */
    public boolean odstran(IKresleny obrazec)
    {
        boolean ret = predmety.remove(obrazec);
        if(ret) prekresli();
        return ret;
    }//public boolean odstran(IKresleny obrazec)


    /**
     * Prida zadany obrazec na konec seznamu malovanych.
     * Byl-li obrazec opravdu pridan, prekresli platno.
     *
     * @param  obrazec  pridavany obrazec
     *
     * @return  true v pripade, kdyz byl obrazec opravdu pridan,
     *          false v pripade, kdyz jiz mezi zobrazovanymi byl
     */
    public boolean pridej(IKresleny obrazec)
    {
        boolean ret = predmety.add(obrazec);
        if(ret) prekresli();
        return ret;
    }//public boolean pridej(IKresleny obrazec)


    /**
     * Prida obrazec do seznamu malovanych tak, aby byl kreslen
     * nad zadanym obrazcem.
     * Byl-li obrazec opravdu pridan, prekresli platno.
     *
     * @param  kresleny  obrazec, ktery ma byt pri kresleni pod
     *                   pridavanym obrazcem
     * @param  pridany   pridavany obrazec
     *
     * @return  true v pripade, kdyz byl obrazec opravdu pridan,
     *          false v pripade, kdyz jiz mezi zobrazovanymi byl
     *          a pouze se presunul do jine urovne
     */
    public boolean pridejNad(IKresleny soucasny, IKresleny pridany )
    {
        boolean nebyl = ! predmety.remove(pridany);
        int kam = predmety.indexOf( soucasny );
        if( kam < 0 )
        {
            throw new IllegalArgumentException(
                "Referencni objekt neni na platne zobrazovan!" );
        }
        predmety.add( kam+1, pridany );
        prekresli();
        return nebyl;
    }//public boolean pridej(IKresleny obrazec)


    /**
     * Prida obrazec do seznamu malovanych tak, aby byl kreslen
     * pod zadanym obrazcem.
     * Byl-li obrazec opravdu pridan, prekresli platno.
     *
     * @param  kresleny  obrazec, ktery ma byt pri kresleni nad
     *                   pridavanym obrazcem
     * @param  pridany   pridavany obrazec
     *
     * @return  true v pripade, kdyz byl obrazec opravdu pridan,
     *          false v pripade, kdyz jiz mezi zobrazovanymi byl
     *          a pouze se presunul do jine urovne
     */
    public boolean pridejPod(IKresleny soucasny, IKresleny pridany )
    {
        boolean nebyl = ! predmety.remove(pridany);
        int kam = predmety.indexOf( soucasny );
        if( kam < 0 )
        {
            throw new IllegalArgumentException(
                "Referencni objekt neni na platne zobrazovan!" );
        }
        predmety.add( kam, pridany );
        prekresli();
        return nebyl;
    }//public boolean pridej(IKresleny obrazec)

    /**
     * Vrati poradi zadaneho prvku v seznamu kreslenych prvku.
     * Prvky se pritom kresli v rostoucim poradi, takze obrazec
     * s vetsim poradim je kreslen nad obrazcem s mensim poradim.
     * Neni-li zadany obrazec mezi kreslenymi, vrati -1.
     *
     * @param  obrazec  objekt, na jehoz kreslici poradi se dotazujeme
     * @return  poradi obrazce; prvy kresleny obrazec ma poradi 0.
     *          Neni-li zadany obrazec mezi kreslenymi, vrati -1.
     */
    public int poradi(IKresleny obrazec) 
    {
        return predmety.indexOf( obrazec );
    }//public int poradi(IKresleny obrazec)

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


    /**
     * Pocka zadany pocet milisekund - ma usnadnit tvorbu animaci.
     *
     * @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 =================================================


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

    /************************************************************************
     * Vnitrni trida VlastniPlatno 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 VlastniPlatno 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 VlastniPlatno extends JPanel


//== TESTOVACI TRIDY A METODY ==================================================

    public static class Test
    {
        public static void test()
        {
            AnimPlatno animPlatno = getPlatno();

            animPlatno.setRozmer(400,400);
/*+*
            //Test prevodu barev
            String sb;
            Color  cb;

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

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

            cb = Color.gray;
            sb = animPlatno.prevedBarva(Color.gray);
            System.out.println("Barva " + cb + " = " + sb );
/*-*/
        }//public static void test()

    }//public static class Test


}//public class AnimPlatno