package rup.česky.tvary;

import rup.česky.společně.P;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;


/*******************************************************************************
 * Instance třídy Multipřesouvač je jedináček sloužící 
 * k přesunu několika grafických objektů zároveň.
 * Obdobně jako AktivníPlátno přijímá prostřednictvím metody <code>přidej</code>
 * do správy objekty, kterými pak po plátně pohybuje.
 * <p>
 * Tyto objekty musí být typu IPosuvny. Je-li objekt dokonce typu IMultiposuvný,
 * tak poté, co objekt přesune do zadané cílové pozice, zavolá jeho metodu
 * <code>přesunuto</code>, která může provést libovolnou akci (většinou předá
 * znovu objekt multipřesouvači, aby jej přesunul do další pozice).
 *
 * @author     Rudolf Pecinovský
 * @version    2.01, duben 2004
 */
public class Multipřesouvač
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================
    
    /** Implicitní hodnota periody = počet milisekund, které uplynou mezi
     *  dvěma následnými překresleními přesouvaných objektů. */    
    private static final int PERIODA_0 = 50;

    /** Tento atribut je tu pouze pro zjednodušení psaní. */
    private static final AktivníPlátno AP = AktivníPlátno.getPlátno();
    
    /** Jediná existující instance multipřesouvače. */
    private static final Multipřesouvač jedináček = new Multipřesouvač();

    

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

    /** Počet milisekund, které uplynou mezi dvěma následnými překresleními 
     *  přesouvaných objektů. */
    private final int perioda = PERIODA_0;

    /** Seznam posouvaných objektů. Je deklarovaný jako mapa,
     *  aby v něm bylo možno rychle testovat přítomnost prvku.
     *  Linkovanou mapou je proto, protože bude při kreslení velice často
     *  porcházen s vysokými požadavky na rychlost. */
    private final Map<IPosuvný,Animace> přesouvané = 
                                        new LinkedHashMap<IPosuvný,Animace>();

    /** Tabulka dvojic [IPosuvný;Animace] reprezentovaná jako množina. */
    private final Set<Map.Entry<IPosuvný,Animace>> dvojice = 
                                                        přesouvané.entrySet();

    /** Časovač, který se postará o opakované vykreslování svěřených objektů.*/
    private final Timer timer = new Timer();
    
    
    
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================
    
    /** Proměnná ovládající běh multipřesouvače. */
    private Multipřesun multipřesun = null;



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

    /***************************************************************************
     * Vrátí hodnotu periody =  počet milisekund, které uplynou mezi dvěma
     * následnými překresleními přesouvaných objektů. 
     * 
     * @return Perioda překreslování v milisekundách.
     */   
    public int getPerioda()
    {
        return perioda;
    }
    

    /***************************************************************************
     * Vrátí frekvenci překreslování, tj. počet překreslení za sekundu.
     * 
     * @return Frekvence překreslování.
     */   
    public int getFrekvence()
    {
        return 1000 / perioda;
    }
    
    
    
//== OSTATNÍ METODY TŘÍDY ======================================================

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

    /***************************************************************************
     * Vytvoří nový multipřesouvač s implicitní periodou (50 ms).
     */
    public static Multipřesouvač getInstance()
    {
        return jedináček;
    }


    /***************************************************************************
     * Vytvoří nový multipřesouvač - všechna potřebná nastavení
     * jsou součástí deklarací atributů.
     */
    private Multipřesouvač()
    {
    }



//== PŘÍSTUPOVÉ METODY ATRIBUTU INSTANCÍ =======================================
    
    /***************************************************************************
     * Vrátí pole se všemi právě přesouvanými objekty. Vrácený seznam je však
     * platný pouze v době volání. Vzápětí na to mohou být některé objekty
     * dovezeny do svých cílových pozic a mohou seznam "opustit".
     * 
     * @return Pole právě přesouvaných objektů.
     */
    public IPosuvný[] getPřesouvané()
    {
        synchronized( přesouvané )
        {
            return (IPosuvný[])(přesouvané.keySet().toArray(new IPosuvný[0]));
        }
    }
    
    
    
//== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ =================================
//== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY ===============================
//== OSTATNÍ PŘEKRYTÉ METODY RODIČOVSKÉ TŘÍDY ==================================
//== OSTATNÍ METODY INSTANCÍ ===================================================

    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice 
     * za zadanou dobu.
     *       
     * @param ip        Přesouvaný objekt
     * @param xn        Vodorovná souřadnice cílové pozice           
     * @param yn        Svislá souřadnice cílové pozice           
     * @param sekund    Doba, kterou bude přesun trvat, v sekundách 
     */    
    public void přesuň( IPosuvný ip, int xn, int yn, double sekund )
    {
        if( sekund <= 0 )
            throw new IllegalArgumentException(
                "Doba přesunu musí být kladná!");
        přesuň( ip, xn, yn, 0, sekund*1000 );
    }
    

    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice 
     * za zadanou dobu.
     *       
     * @param ip        Přesouvaný objekt
     * @param pozice    Požadovaná cílové pozice           
     * @param sekund    Doba, kterou bude přesun trvat, v sekundách 
     */    
    public void přesuň( IPosuvný ip, Pozice pozice, double sekund )
    {
        přesuň( ip, pozice.x, pozice.y, sekund );
    }
    

    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice 
     * za zadanou dobu.
     *       
     * @param ip        Přesouvaný objekt
     * @param pozice    Požadovaná cílové pozice           
     * @param rychlost  Počet bodů, o které se objekt přesune za sekundu
     */    
    public void přesuň( IPosuvný ip, Pozice pozice, int rychlost )
    {
        přesuň( ip, pozice.x, pozice.y, rychlost, 0 );
    }


    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice 
     * za zadanou dobu.
     *       
     * @param ip        Přesouvaný objekt
     * @param xn        Vodorovná souřadnice cílové pozice           
     * @param yn        Svislá souřadnice cílové pozice           
     * @param rychlost  Počet bodů, o které se objekt přesune za sekundu
     */    
    public void přesuň( IPosuvný ip, int xn, int yn, int rychlost )
    {
        if( rychlost <= 0 )
            throw new IllegalArgumentException(
                "Rychlost přesunu musí být kladná!");
        přesuň( ip, xn, yn, rychlost, 0 );
    }


    /***************************************************************************
     * Zastaví požadovaný objekt, tj. vyjme jej ze seznamu objektů, 
     * s nimiž pohybuje. Pro jeho příští rozpohybování je potřeba
     * znovu požádat multipřesouvač o jeho přesunutí.     
     *       
     * @param ip   Zastavovaný objekt         
     *       
     * @return     Informace o tom, byl-li objekt mezi přesouvanými     
     */ 
    public boolean zastav( IPosuvný ip )
    {
        synchronized( přesouvané )
        {
            if( přesouvané.containsKey(ip) )
            {
                přesouvané.remove( ip );
                return true;
            }
            return false;
        }
    }    


   /***************************************************************************
    * Zastaví zastaví přesouvače a zruší všechny plány přesunů. 
    */ 
    public void zastavVše()
    {
        stop();
        synchronized( přesouvané )
        {
            přesouvané.clear();
        }
    }    


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

    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice 
     * za zadanou dobu nebo zadanou rychlostí.
     *       
     * @param ip            Přesouvaný objekt
     * @param xn            Vodorovná souřadnice cílové pozice           
     * @param yn            Svislá souřadnice cílové pozice           
     * @param rychlost      Počet bodů, o které se objekt přesune za sekundu
     * @param milisekund    Doba, kterou bude přesun trvat, v sekundách 
     */    
    private void přesuň( IPosuvný ip, int xn, int yn, 
                         int rychlost, double milisekund )
    {
        if( ip == null )
            throw new NullPointerException(
                "Přesouvaný objekt nesmí být null!");
        Animace a = new Animace( ip, xn, yn, rychlost, milisekund  );
        synchronized(přesouvané)
        {
            if( přesouvané.get(ip) != null )
            {
                a = null;
                throw new IllegalStateException(
                    "Přidávaný objekt již je mezi přesouvanými!");
            }
            AP.přidej( ip );
            přesouvané.put( ip, a );
            start();
        }//synchronized(přesouvané)
    }
        
    
    /***************************************************************************
     * Ukončí multipřesun a tím uvolní procesor.    
     */     
    private synchronized void stop()
    {
        if( multipřesun == null )
            return;
        multipřesun.cancel();
        multipřesun = null;
    }
    
    
    /***************************************************************************
     * Spustí nový multipřesun. 
     */     
    private synchronized void start()
    {
        if( multipřesun == null )
        {
            multipřesun = new Multipřesun();
            timer.scheduleAtFixedRate( multipřesun, perioda, perioda );
            //timer.schedule(multipřesun, perioda, perioda);
        }
    }
    

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

    //==========================================================================
    /***************************************************************************
     * Soukromá vnitřní třída, jejíž instance sdružují všechny potřebné 
     * informace o přesouvaném objektu. Funguje pouze jako přepravka.
     */          
    private class Animace
    {
        double   x,  y;     //Aktuální souřadnice objektu
        double   dx, dy;    //Přírustek souřadnic v jednom kroku
        IPosuvný objekt;    //Animovaný objekt
        int      kroků;     //Počet kroků potřebných pro přesun.


        /***********************************************************************
         * Vytvoří animaci na základě zadaného objektu, jeho cílových 
         * souřadnic a doby, během níž má těchto souřadnic dosáhnout.
         * nebo rychlosti, s níž se má k tomuto cíli přesouvat.
         * Volající metoda musí zabezpečit, aby doba nebo rychlost byla nulová.
         *           
         * @param ip        Přesouvaný objekt         
         * @param xn        Vodorovná souřadnice cíle         
         * @param yn        Svislá souřadnice cíle
         * @param rychlost  Počet bodů "zdolaných" za sekundu
         * @param doba      Počet milisekund         
         */                          
        Animace( IPosuvný ip, int xn, int yn, int rychlost, double doba )
        {
            objekt = ip;
            x = ip.getX();
            y = ip.getY();
            dx = (xn - x);
            dy = (yn - y);
            if( rychlost > 0 )
            {
            	double vzdálenost = P.přepona( dx, dy );
                kroků = (int)(1000 * vzdálenost / (rychlost * perioda));
            }
            else
            {
            	kroků = (int)(doba / perioda); 
            }
            if( kroků < 1 )
                kroků = 1;
            //Posunutí zlepšuje rozložení přesunu při malém počtu kroků
            x  += 0.4;
            y  += 0.4;
            dx /= kroků;
            dy /= kroků;
        }
        

    }//private class Animace


    
    //==========================================================================
    /***************************************************************************
     * Instance třídy jsou úlohy ralizující vlastní multipřesun.
     * Multipřesun je definván jako samostatná třída proto, aby jej bylo možno
     * při ukončení všech zadaných přesunů vypnout a při vzniku nových
     * požadavků na přesun zase zapnout. 
     */    
    private class Multipřesun extends TimerTask
    {
        /***********************************************************************
         * Metoda vyžadovaná rozhraním Runable implementovaným rodičovskou
         * třídou TimerTask - tuto metodu zavolá Timer pokaždé, 
         * když se rozhodne spustit další provedení opakovaného úkolu 
         * (Timertask) = multipřesunu.              
         */     
        public void run()
        {
            //Při překreslování se nesmí měnit počet objektů v seznamu
            synchronized( přesouvané )
            {
                Iterator it = dvojice.iterator();
                AP.nekresli();
                while( it.hasNext() )
                {
                    Animace a = (Animace)(((Map.Entry)it.next()).getValue());
                    a.x += a.dx;
                    a.y += a.dy;
                    a.objekt.setPozice((int)a.x, (int)a.y);
                    if( --a.kroků == 0 )
                    {
                        it.remove();
                        if( a.objekt instanceof IMultiposuvný )
                        {
                            final IMultiposuvný aa = (IMultiposuvný) (a.objekt);
                            Thread t = new Thread(a.toString()) {
                                public void run() {
                                    aa.přesunuto();
                                }//public void run()
                            };//Thread t = new Thread()
                            t.start();
                        }
                        //Při vyprázdnění seznamu zruš úlohu
                        if( přesouvané.size() <= 0)
                        {
                            stop();
                            break;
                        }
                    }
                }//while
                AP.vraťKresli();
            }//synchronized( přesouvané )
       }//public void run()
        
    }//private class Multipřesun extends TimerTask



//== TESTY A METODA MAIN =======================================================
}


