import java.awt.geom.Ellipse2D;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import java.lang.reflect.Constructor;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;


/*******************************************************************************
 * Třída Dispečer má na starosti vedení hry,
 * při které má uživatel dopravit UFO ze startovní rampy do hangáru.
 * <p>
 * V první variantě uživatel zadává rychlost UFO přímým voláním metody
 * setRychlost(double,double).
 * <p>
 * Při druhé varianty hry, uživatel zadává tah motorů a tím i zrychlení UFO
 * prostřednictvím kurzorových kláves a mezerníku, kterým motory vypíná.
 * Stiskem číselné klávesy může přepínat které UFO zrovna ovládá.
 *
 * @author     Rudolf Pecinovský
 * @version    1.00, červen 2004
 */
public class Dispečer
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Frekvence snímkování = počet překreslení Vesmíru za vteřinu. */
    public static final int FREKVENCE = 8;

    /** Odkaz na okno, ve kterém se všechno kreslí. */
    protected static final Vesmír V = Vesmír.getVesmír();

    /** Náhrada za přepisování Talíř.VELIKOST pro lenochy. */
    protected static final int TV = Talíř.VELIKOST;

    /** Nejmenší povolená velikost překážející planety. */
    private static final int NEJMENŠÍ = TV;

    /** Největší povolená velikost překážející planety. */
    private static final int NEJVĚTŠÍ = NEJMENŠÍ * 4;

    /** Vektor s možnými barvami planet. První tři ale nebudeme používat.
     *  Černá [0] je barva vesmíru, takže by nebyla vidět,
     *  Modrá [1] bude barva startovní rampy a hangárů a
     *  Červená [2] bude barva létajících talířů. */
    private static final Barva[] barvyPlanet;
    public  static final Barva   barvaVesmíru;
    public  static final Barva   barvaRampy;
    public  static final Barva   barvaUFO;
            static
    {
        Barva[] b    = Barva.getZnáméBarvy();
        barvaVesmíru = b[0];
        barvaRampy   = b[1];
        barvaUFO     = b[2];
        barvyPlanet  = new Barva[ b.length-3 ];
        System.arraycopy( b, 3, barvyPlanet, 0, barvyPlanet.length );
        b = null;
    }



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

    private static Dispečer dispečer;



//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    private final int HANGÁRŮ;

    /** Startovací rampa (nultá položka) a přistávací rampy = hangáry. */
    private final Rampa[]   rampa;

    /** Planety, které mají za úkol překážet v rychlém dosažení cíle. */
    private final Planeta[] planeta;

    /** Seznam doposud vygenerovaných UFO. */
    private final List<UFO> ufoList = new ArrayList<UFO>();

    /** Časovač, který zabezpečí pravidelné, opakované vyvolání metody
     *  run() puštěného filmu. */
    private final Timer timer = new Timer("Dispečer UFO");

    /** Indikuje, zda je v provozu verze ovládané z klávesnice
     *  nebo verze ovládáná přímým voláním metody setRychlost(int,int) */
    private final boolean zKlávesnice;

    /** Svislá souřadnice hangárů. */
    private final int yHangáru;



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

    /** Aktuální počet vygenerovaných UFO. */
    protected int početUFO = 0;

    /** Objekt, který ma na starosti korektní zobrazování pohybujících se
     *  objektů. */
    private Film film = null;

    /** Proměnná registrující běh filmu -
     *  zabezpečuje, aby nebylo možno spustit právě běžící film. */
    private boolean stojí = true;

    /** UFO, které je v danou chvíli ovládané klávesnicí. */
    private UFO ovládané;

    /** Posluchač reagtující na stisky kláves. */
    private Posluchač posluchač;

    /** Třída, jejíž instance budou vytvářeny jako UFO. */
    private Class ufoClass = UFO.class;

    /** Konstruktor, kterým budou vytvářena nová UFO. */
    private Constructor ufoKonstruktor;
    {
        try
        {
            ufoKonstruktor = ufoClass.getConstructor(
                new Class[] { Talíř.class, int.class } );
        }
        catch( Exception e )
        {
            throw new RuntimeException(
                "Nepodařilo se vytvořit konsturktor třídy " + ufoClass, e );
        }
    }



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

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

    /***************************************************************************
     * Je-li již vytvořen vesmír s dispečerem, vyvolá výjimku,
     * není-li ještě vytvořen vesmír s dispečerem, vytvoří vesmír,
     * v němž se budou pohybovat UFO a u horního okraje plátna pro ně
     * připraví startovací rampu a u dolního okraje zadaný počet hangárů,
     * zároveň nastaví, bude-li hra ovládaná z klávesnice.
     *
     * @param hangárů      Požadovaný počet hangárů.
     * @param zKlávesnice  true = hra bude ovládána z klávesnice
     */
    static Dispečer getDispečer( int hangárů, boolean zKlávesnice )
    {
        if( dispečer != null )
            throw new IllegalStateException(
                "Může být vytvořen pouze jeden dispečer." );
        dispečer = new Dispečer( hangárů, 0, zKlávesnice );
        return dispečer;
    }


    /***************************************************************************
     * Je-li již vytvořen vesmír s dispečerem, vrátí odkaz na dispečera;
     * není-li ještě vytvořen vesmír s dispečerem, vytvoří vesmír,
     * v němž se budou pohybovat UFO, u horního okraje plátna pro ně
     * připraví startovací rampu a u dolního okraje 5 hangárů
     * a odstartuje hru řízenou pomocí klávesnice.
     *
     * @param hangárů  Požadovaný počet hangárů.
     */
    public static Dispečer getDispečer( )
    {
        if( dispečer != null )
            return dispečer;
        return getDispečer( 5, true );
    }


    /***************************************************************************
     * Vytvoří vesmír, v němž se budou pohybovat UFO a u horního okraje plátna
     * pro ně připraví zadaný počet hangárů.
     * Požaqdovaný počet překážejících planet i cílových hangárů
     * může být omezen.
     *
     * @param hangárů  Požadovaný počet hangárů.
     * @param překážek Počet náhodně rozmístěných planet.
     */
    private Dispečer(int hangárů, int planet, boolean zKlávesnice)
    {
        this.zKlávesnice = zKlávesnice;

        HANGÁRŮ = hangárů;
        int rozměr = 3*HANGÁRŮ*TV;
        V.setRozměr( rozměr, rozměr );

        rampa   = new Rampa[HANGÁRŮ+1];
        planeta = new Planeta[planet];

        /*# Vytvořit nový vesmír, v jehož levém horním rohu bude startovací
         *  rampa (čtverec), kam budou přistavována nová ufo, a podél dolního
         *  okraje bude sada očíslovaných hangárů (opět čtverce), kam budou
         *  ufo přistávat. */
        yHangáru = V.getVýška() - TV;
        rampa[0] = new Rampa( 0, TV, TV );
        for( int h=1,  hx=3*TV/2;
             h <= HANGÁRŮ;
             h++, hx+=3*TV )
        {
            rampa[h] = new Rampa( h, hx, yHangáru );
        }

        /*# Vytvořit požadovaný počet planet, kterým se budou muset ufo
         *  vyhýbat. Planety musí být dostatečně daleko od sebe,
         *  aby mezi nimi mohlo ufo s rezervou proletět (aby mezi planetami
         *  bylo místo alespoň pro 2 talíře (nezpomeňte vzít v úvahu velikost
         *  platnet). Nechete-li volit umístění plaent zcela náhodné,
         *  můžete je mít někde předdefinované.
         *  Planety nesmějí být ve spodním startovacím pásu ani v horním pásu
         *  s hangáry; mezi planetami a hangáry musí zůstat manévrovací prostor.
         */
        for(int i=0; i < planeta.length; i++) {
            planeta[i] = new Planeta(mezi(25, 275), mezi(25, 250));
            planeta[i].nakresli();
        }
        V.překresli();
        start();
    }


//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ =================================
//== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY ===============================
//== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================
//== NOVĚ ZAVEDENÉ METODY INSTANCÍ =============================================

    /***************************************************************************
     * Nastaví používání UFO, jež budou instancemi třídy UFO.
     */
    public void použijUFO()
    {
        použijTřídu( UFO.class );
    }


    /***************************************************************************
     * Nastaví používání UFO, jež budou instancemi třídy UFO.
     */
    public void použijUFO_3a()
    {
        použijTřídu( UFO_3a.class );
    }


    /***************************************************************************
     * Nastaví používání UFO, jež budou instacemi třídy zadané v parametru.

     *
     * @param ufoClass  Class objekt třídy, jajíž instance budou
     *                  použity jako UFO.
     */
    public void použijTřídu( Class ufoClass )
    {
        if( this.ufoClass == ufoClass )
            return;
        try
        {
            ufoKonstruktor = ufoClass.getConstructor(
                new Class[] { Talíř.class, int.class } );
        }
        catch( Exception e )
        {
            throw new RuntimeException(
                "Nepodařilo se vytvořit konsturktor třídy " + ufoClass, e );
        }
        if( ! UFO.class.isAssignableFrom( ufoClass ) )
            throw new RuntimeException(
                "Třída " + ufoClass + " není potomkem třídy UFO" );
        this.ufoClass = ufoClass;
    }


    /***************************************************************************
     * Vytvoří instanci UFO, umístí jí na startovní rampu
     * v levém dolním rohu plátna zobrazujícího vesmír a vrátí odkaz na ni.
     *
     * @return Odkaz na vytvořené UFO připravené na startovací rampě.
     */
    public UFO přistavUFO()
    {
        /*# Pro talíř je třeba spočítat správnou pozici na startovní rampě. */
        Talíř talíř = new Talíř( TV, TV );
        try
        {
            //ovládané = new UFO_3a( talíř, ++početUFO );
            ovládané = (UFO)(ufoKonstruktor.newInstance(
                new Object[] { talíř, new Integer( ++početUFO ) } ));
        }
        catch( Exception e )
        {
            throw new RuntimeException(
                "Nepodařilo se vytvořit instanci třídy " + ufoClass, e );
        }
        /* Zařazení do seznamu musí být synchronizováno, aby k němu nemohlo
         * dojít uprostřed procházení seznamem při překreslování vesmíru. */
        synchronized( ufoList )
        {
            ufoList.add( ovládané );
        }
        V.překresli();
        return ovládané;
    }


    /***************************************************************************
     * Ukončí multipřesun a tím uvolní procesor.
     */
    public void stop()
    {
        film.cancel();
        stojí = true;
        if( posluchač != null )
        {
            V.odhlašKlávesnici( posluchač );
            posluchač = null;
        }
    }


    /***************************************************************************
     * Spustí nový multipřesun.
     */
    public void start()
    {
        if( stojí )
        {
            //perioda uchováv počet milisekund mezi dvěma snímky
            int perioda = 1000 / FREKVENCE;
            film = new Film();
            //timer.scheduleAtFixedRate(film, perioda, perioda );
            timer.schedule(film, perioda, perioda);
            stojí = false;
            if( zKlávesnice )
            {
                posluchač = new Posluchač();
                V.přihlašKlávesnici( posluchač );
            }
        }
    }



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

    /** Generátor náhodných čísel slouží pouze následující metodě. */
    private static final java.util.Random rnd = new java.util.Random();

    /***************************************************************************
     * Vrátí náhodné číslo v zadaném rozsahu, tj. od spodní meze včetně
     * do horní meze nevčetně.
     *
     * @param od Dolní mez, tj. nejmenší generovatelné číslo.
     * @param k  Horní mez, tj. nejmenší už negenerovatelné číslo.
     */
    private static int mezi( int od, int k )
    {
        return od + rnd.nextInt( k-od );
    }


    /***************************************************************************
     * Pomocná metoda pro test chování klávesnice.
     */
    private static void vypiš( KeyEvent e )
    {
        char kch = e.getKeyChar();
        char ch = ((32<=kch)&&(kch<=0xff)) ? kch : '?';
        int kc=e.getKeyCode();
        System.out.println(": znak='" + ch +
          "', kódZnaku=" + (int)kch +
          ", kód=" + kc +
          ", loc=" + e.getKeyLocation() +
          ", txt=" + KeyEvent.getKeyText(kc) );
    }



//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Zkontroluje, je-li UFO v nějakém hangáru a je-li jeho rychlost menší
     * než 10, tak je v hangáru vystředí a nastaví jeho rychlost i tah
     * na nulu.
     *
     * @param ufo Kontrolované UFO.
     */
    private void zkontroluj( UFO ufo )
    {
        //Dostalo se UFO do oblasti hangárů?
        if( Math.abs(ufo.getY() - yHangáru) > TV/2 )
            return;
        //Letí dostatečně pomalu?
        if( (Math.abs(ufo.getXRychlost()) + Math.abs(ufo.getYRychlost())) > TV )
            return;
        //Nalétá-li do některého hangáru, požádej hangár, ať je zaparkuje
        double ux = ufo.getX();
        for( int ir=rampa.length;   --ir > 0;   )
        {
            if( Math.abs(rampa[ir].getX() - ux) <= TV/2 )
            {
                rampa[ir].zaparkuj( ufo );
                break;
            }
        }
    }



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

    //==========================================================================
    /***************************************************************************
     * Společný rodič všech čtverců a elips.
     */
    static abstract class Tvar
    {
        double xPos;    //Bodová x-ová souřadnice počátku
        double yPos;    //Bodová y-ová souřadnice počátku
        int    rozměr;  //velikost v bodech
        Barva  barva;   //Barva instance

        Tvar( double x, double y, int rozměr, Barva barva )
        {
            this.xPos   = x;
            this.yPos   = y;
            this.rozměr = rozměr;
            this.barva  = barva;
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         */
        public abstract void nakresli();


        /***********************************************************************
         * Vrátí x-ovou souřadnici pozice instance.
         *
         * @return  x-ová souřadnice.
         */
        public double getX()
        {
            return xPos;
        }


        /***********************************************************************
         * Vrátí y-ovou souřadnici pozice instance.
         *
         * @return  y-ová souřadnice.
         */
        public double getY()
        {
            return yPos;
        }


        /***********************************************************************
         * Nastaví novou pozici instance.
         *
         * @param x   Nová x-ová pozice instance
         * @param y   Nová y-ová pozice instance
         */
        public void setPozice( double x, double y )
        {
            xPos = x;
            yPos = y;
        }


        /***********************************************************************
         * Vrátí barvu instance.
         *
         * @return  Instance třídy Barva definující nastavenou barvu.
         */
        public Barva getBarva()
        {
            return barva;
        }


        /***********************************************************************
         * Nastaví novou barvu instance.
         *
         * @param nová  Požadovaná nová barva.
         */
        public void setBarva(Barva nová)
        {
            barva = nová;
            nakresli();
        }

    }


    //==========================================================================
    /***************************************************************************
     * Ekvivalent třídy Elipsa, pouze s nejnutnějším počtem metod.
     *
     * @return Název třídy následovnaý podtržítkem a "rodným číslem" instance.
     */
    static class Kruh extends Tvar
    {
        Ellipse2D.Double kruh;

        /***********************************************************************
         * Vytvoří instanci o zadaných parametrech.
         */
        Kruh( double x, double y, int rozměr, Barva barva )
        {
            super( x, y, rozměr, barva );
            int r2 = rozměr >> 1;   //Poloviční rozměr
            kruh = new Ellipse2D.Double(
                (int)xPos-r2, (int)yPos-r2, rozměr, rozměr );
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         * Při kreslení považuje za svoji pozici střed kruhu.
         */
        public void nakresli()
        {
            V.setBarvaPopředí( barva );
            V.zaplň( kruh );
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         * Při kreslení považuje za svoji pozici střed kruhu.
         */
        public void setPozice( double x, double y )
        {
            super.setPozice( x, y );
            int r2 = rozměr >> 1;   //Poloviční rozměr
            kruh.setFrame( (int)xPos-r2, (int)yPos-r2, rozměr,  rozměr );
        }

    }//static class Kruh extends Tvar



    //==========================================================================
    /***************************************************************************
     * Instance třídy představují planety, kterým se ufo musí vyhýbat.
     */
    private static class Planeta extends Kruh
    {
        /***********************************************************************
         * Vytvoří planetu na zadaných souřadnicích.
         */
        Planeta( int x, int y )
        {
            super( 0, 0, mezi( NEJMENŠÍ, NEJVĚTŠÍ ),
                   barvyPlanet[ mezi(0,barvyPlanet.length)]  );
            /*# Najít takovou pseudonáhodnout pozici,
             *  aby planeta byla od všech dříve vygenerovaných planet
             *  dostatečně daleko - tj. aby mezi planetami bylo místo alespoň
             *  pro 2 talíře (nezpomeňte vzít v úvahu velikost platnet).
             *  Nepodaří-li se takové místo najít, ohlásí chybu. */
            nakresli();
        }

    }//private static class Planeta extends Kruh



    //==========================================================================
    /***************************************************************************
     * Instance třídy Film jsou úlohy simulující život vesmíru.
     * Film je definván jako samostatná třída proto, aby jej bylo možno
     * snadno zapnout a vypnout.
     */
    private class Film 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()
        {
            V.smaž();
            for( int i=  rampa.length;  --i >= 0;    rampa[i].nakresli() );
            for( int i=planeta.length;  --i >= 0;  planeta[i].nakresli() );
            //Při překreslování se nesmí měnit počet objektů v seznamu
            synchronized( ufoList )
            {
                for( int i=ufoList.size();      --i >= 0;  )
                {
                    UFO u = (UFO)ufoList.get( i );
                    u.popojeď( FREKVENCE );
                    u.nakresli();
                    zkontroluj( u );
                }
            }//synchronized
            V.překresli();
       }//public void run()

    }//private class Film extends TimerTask



    //==========================================================================
    /***************************************************************************
     * Instance třídy Posluchač slouží jako posluchač klávesnice
     * řídící pohyb UFO v druhé variantě hry.
     */
    private class Posluchač extends KeyAdapter
    {
        /***********************************************************************
         * Ošetření klávesnice.
         */
        public void keyPressed(KeyEvent e)
        {
            //vypiš( e );
            int kc = e.getKeyChar();
            if( ('1' <= kc)  &&  (kc <= '9') )
            {
                int iu = kc - '1';  //1. ufo bude v 0. prvku
                if( iu < ufoList.size() )
                    ovládané = (UFO)ufoList.get( iu );
                return;
            }
            kc = e.getKeyCode();
            if( kc == KeyEvent.VK_ENTER )
            {
                přistavUFO();
                return;
            }
            if( ovládané == null )
                return;
            switch( kc )
            {
                case KeyEvent.VK_DOWN:
                    ovládané.dolů();
                    return;

                case KeyEvent.VK_LEFT:
                    ovládané.vlevo();
                    return;

                case KeyEvent.VK_RIGHT:
                    ovládané.vpravo();
                    return;

                case KeyEvent.VK_UP:
                    ovládané.vzhůru();
                    return;

                case KeyEvent.VK_SPACE:
                    ovládané.vypniMotory();
                    return;
            }//switch
        }

    }//private class Posluchač extends KeyAdapter



//== TESTY A METODA MAIN =======================================================
}//public class Dispečer



