package doprava;

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

import rup.česky.tvary.Barva;
import rup.česky.tvary.AktivníPlátno;
import rup.česky.tvary.Kreslítko;
import rup.česky.tvary.Pozice;
import rup.česky.tvary.Čára;
import rup.česky.tvary.Elipsa;


/*******************************************************************************
 * Instance třídy Linka představují linku, po které se budou pohybovat
 * kabinky, které budou jezdit pořád kolem dokola.
 *
 * @author Rudolf Pecinovský
 */
public class Linka
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    private static final java.util.Set<Barva> zprovozněné = 
                                            new java.util.HashSet<Barva>();
    
    private static final AktivníPlátno AP = AktivníPlátno.getPlátno();
    

//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================
    
    private final Barva barva;


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

    private Zastávka první;
    private int      rychlost = 4;
    private boolean  zrušená  = false;
    private int      čekání   = 500;


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


    /***************************************************************************
     * Vytvoří vnější, modrou linku, po níž se budoucí kabinky budou pohybovat
     * zadanou rychlostí; očekává, že plátno má rozměry alespoň 7×7 polí.
     */
    public static Linka vytvořVnější( int rychlost )
    {
        int šířka = Math.max( AP.getŠířka() * AP.getKrok(), 350 );
        int výška = Math.max( AP.getVýška() * AP.getKrok(), 350 );
        AP.setKrokRozměr( 1, šířka, výška );
        Linka vnější  = new Linka( Barva.MODRÁ, new int[][] 
                      { {25, 25}, {25,275}, {175,325}, {325,275}, {325, 25}});
        vnější.setRychlost( rychlost );
        return vnější;
    }
        

    /***************************************************************************
     * Vytvoří střední, zelenou linku, po níž se budoucí kabinky budou pohybovat
     * zadanou rychlostí; očekává, že plátno má rozměry 6×6 polí.
     */
    public static Linka vytvořStřední( int rychlost )
    {
        int šířka = Math.max( AP.getŠířka() * AP.getKrok(), 300 );
        int výška = Math.max( AP.getVýška() * AP.getKrok(), 300 );
        AP.setKrokRozměr( 1, šířka, výška );
        Linka střední = new Linka( Barva.ČERVENÁ, new int[][] 
                      { {75, 75}, {75,225}, {175,275}, {275,225}, {275, 75}});
        střední.setRychlost( rychlost );
        return střední;
    }
        
        
    /***************************************************************************
     * Vytvoří vnitřní, zelenou linku, po níž se budoucí kabinky budou pohybovat
     * zadanou rychlostí; očekává, že plátno má rozměry 7×7 polí.
     */
    public static Linka vytvořVnitřní( int rychlost )
    {
        int šířka = Math.max( AP.getŠířka() * AP.getKrok(), 250 );
        int výška = Math.max( AP.getVýška() * AP.getKrok(), 250 );
        AP.setKrokRozměr( 1, šířka, výška );
        Linka vnitřní = new Linka( Barva.ZELENÁ, new int[][] 
                      {{125,125},{125,175}, {175,225}, {225,175}, {225,125}});
        vnitřní.setRychlost( rychlost );
        return vnitřní;
    }
    
    
    
//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Konstruktor Linky definuje její barvu a předá ji vektor souřadnic
     * jejích jednotlivých zastávek.
     *
     * @param barva     Barva dané linky. Dvě linky nesmí mít stejnou barvu.
     * @param xyStanic  Souřadnice jednotlivých stanic na dané lince.
     */
    public Linka( Barva barva, int[][] xyStanic )
    {
        final String dvěSouřadnice = "Stanice musí mít právě dvě souřadnice";
        
        if( !zprovozněné.add( barva ) )
            throw new IllegalArgumentException(
                "Žádné dvě linky nesmějí mít stejnou barvu." );
        if( xyStanic.length < 2 )
            throw new IllegalArgumentException(
                "Linka musí mít nejméně dvě stanice." );
        if( xyStanic[0].length != 2 )
            throw new IllegalArgumentException( dvěSouřadnice );

        this.barva = barva;
        AP.nekresli();
            první = new Zastávka( this, xyStanic[0][0], xyStanic[0][1] );
            Zastávka další = první;
            for( int i=1;   i < xyStanic.length;   i++ )
            {
                if( xyStanic[i].length != 2 )
                    throw new IllegalArgumentException( dvěSouřadnice );
                další = new Zastávka( další, xyStanic[i][0], xyStanic[i][1] );
            }
        AP.vraťKresli();
    }


    /***************************************************************************
     * Konstruktor Linky definuje její barvu a předá ji vektor souřadnic
     * jejích jednotlivých zastávek.
     *
     * @param barva  Barva dané linky. Dvě linky nesmí mít stejnou barvu.
     * @param x1     Vodorovná souřadnice první zastávky
     * @param y1     Svislá souřadnice první zastávky
     * @param x2     Vodorovná souřadnice druhé zastávky
     * @param y2     Svislá souřadnice druhé zastávky
     */
    public Linka( Barva barva, int x1, int y1, int x2, int y2 )
    {
        this( barva, new int[][] { {x1,y1}, {x2,y2} } );
    }



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

    /***************************************************************************
     * Vrátí odkaz na první zastávku na lince.
     * 
     * @return První zastávka na lince.
     */
    public IZastávka getPrvní()
    {
        živá();
        return první;
    }


    /***************************************************************************
     * Vrátí odkaz na barvu linky.
     * 
     * @return Barva linky.
     */
    public Barva getBarva()
    {
        živá();
        return barva;
    }
    
    
    /***************************************************************************
     * Vrátí dobu v milisekundách, kterou je kabinkám doporučeno 
     * čekat ve stanici před tím, než vyjedou dál.
     * 
     * @return Doporučená doba čekání ve stanici.
     */
    public int getČekání()
    {
        živá();
        return čekání;
    }
    
    
    /***************************************************************************
     * Vrátí doporučenou dobu čekání kabin ve stanici než vyjednou dál;
     * doba je uváděna v milisekundách.
     * 
     * @return Doporučená doba čekání ve stanici.
     */
    public void setČekání( int milisekund )
    {
        živá();
        if( (milisekund < 0) || (1000 < milisekund) )
            throw new IllegalArgumentException(
                "Doporučená doba čekání smí být vozsahu 0-1000 milisekund " +
                "\nZadáno: " + milisekund + " milisekund" );
        this.čekání = milisekund;
    }
    
    
    /***************************************************************************
     * Vrátí doporučenou rychlost, kterou by se měly kabinky pohybovat 
     * po lince. Pojedou-li všechny kabinky stejnou rychlostí, nemohou se 
     * nabourat na trati. Budou-li dostatečně daleko za sebou a dodrží-li
     * doporučenou dobu čekání, nemohou se nabourat ani ve stanici.
     * 
     * @return Doporučená rychlost přesunu po lince.
     */
    public int getRychlost()
    {
        živá();
        return rychlost;
    }

    
    /***************************************************************************
     * Nastaví novou doporučenou rychlost pro danou linku.
     * 
     * @param rychlost Doporučená rychlost přesunu po lince.
     */
    public void setRychlost( int rychlost )
    {
        živá();
        if( rychlost < 1 )
            throw new IllegalArgumentException(
                "Rychlost kabinek na lince musí být kladná" );
        if( rychlost > 200 )
            throw new IllegalArgumentException(
                "Rychlost kabinek na lince smí být nejvýše 200" );
        this.rychlost = rychlost;
    }

    
    /***************************************************************************
     * Vrátí informaci o tom, jestli je linka již zrušená.
     * 
     * @return  Informace o zrušení linky: true=zrušená, false=živá.
     */
    public boolean isZrušená()
    {
        return zrušená;
    }
    
    

//== 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 P.názevTřídy( this ) + "_" + barva;
    }



//== NOVĚ ZAVEDENÉ METODY INSTANCÍ =============================================
    
    /***************************************************************************
     * @param zaMne
     * @param koho
     */
    public IZastávka přidejZa( IZastávka zaMne, int x, int y )
    {
        Zastávka z = (Zastávka)zaMne;
        moje( z );
        return z.napoj( x, y );
    }
    
    
    /***************************************************************************
     * @param koho
     */
    public void odstraň( IZastávka koho )
    {
        moje( koho );
        ((Zastávka)koho).zruš();
    }
    
    
    /***************************************************************************
     * 
     */
    public void zruš()
    {
        živá();
        while( první != null )
        {
            první = první.zruš();
        }
        zprovozněné.remove( barva );
        zrušená = true;
    }
    
    
    
//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================
    
    /***************************************************************************
     * 
     */
    private void živá()
    {
        if( zrušená )
            throw new IllegalStateException(
                "Linka je již zrušena a nelze ji tedy používat" );
    }
    
    
    /***************************************************************************
     * @param z
     */
    private void moje( IZastávka z )
    {
        živá();
        Zastávka další = první;
        boolean  našel = false;
        do{
            if( z == další )
            {
                našel = true;
                break;
            }
            další = (Zastávka)další.getNásledující();
        }while( další != první );
        if( ! našel )
            throw new IllegalArgumentException(
                "Zastávka " + z + " není na lince " + this );
    }
    
    
//== VNOŘENÉ A VNITŘNÍ TŘÍDY ===================================================

    private int zpočet = 0;
    /***************************************************************************
     * Instance třídy Zastávka představují zastávky na trati kabinové lanovky.
     * Při vytváření se musí zastávky budovat postupně od začátku do konce
     * a každé z nich je třeba dodat odkaz na jejího předchůdce. Nově vytvářená
     * zastávka se pak se svou předchůdkyné spojí čarou představující lano.
     * Zároven ji oznámí, že je jejím následovníkem.
     * Každá zastávka tak zná svého předchůdce a svého následovníka
     * a je na něj schopna dodat na požádání odkaz.
     */
    private class Zastávka implements IZastávka
    {
    //== KONSTANTNÍ ATRIBUTY TŘÍDY =============================================

        /** Průměr kruhu představujícího nástupiště */
        public static final int  PRŮMĚR = 20;

        /** Polovina průměru nástupiště. */
        private static final int  P2 = PRŮMĚR / 2;
        

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

        /** Pořadí vytvoření dané instance v rámci třídy. */
        private final int zpořadí = ++zpočet;

        /** Linka, na níž se zastávka nachází a která mimo jié definuje např.
         *  barvu nástupiště i příjezdové trati. */
        private final Linka linka;

        /** Souřadnice zastávky. */    
        private final int xPos, yPos;

        /** Obrazec symbolizující nástupiště na dané zastávce. */
        private Elipsa nástupiště;

        

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

        /** Předchozí zastávka na trati. */
        private Zastávka předchozí;

        /** Následující zastávka na trati. */
        private Zastávka následující;

        /** Trať vedoucí z předchozí zastávky do této. */
        private Čára trať;
        
        /** Indikuje, je-li zastávka součástí linky nebo je-li již vyjmuta. */
        private boolean funkční = true;


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

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

        /***********************************************************************
         * Tento konstruktor lze použít pouze pro linku bez zastávek - vytvoří 
         * počáteční zastávku linky. Všechny následující zastávky
         * jakož i spojovací tratě budou kresleny stejnou barvou.
         * Souřadnice zastávky jsou souřadnicemi středu kruhu, který představuje
         * její nástupiště.
         * 
         * @param linka   Linka, na které se bude zastávka nacházet.
         * @param x       x-ová souřadnice středu nástupiště.
         * @param y       y-ová souřadnice středu nástupiště.
         */
        public Zastávka( Linka linka, int x, int y )
        {
            this( null, linka, x, y );
        }


        /***********************************************************************
         * Vytvoří novou instanci třídy Zastávka. Její souřadnice jsou přitom
         * souřadnicemi středu kruhu, který představuje její nástupiště.
         * Zastávka převezme barvu svého předchůdce a spojí se s ním čárou
         * stejné barvy. Po vytvoření přihláší zastávku u AnimPlátna.
         * 
         * @param předchozí Předchozí zastávka, odkud přijíždějí kabiny
         *                  pohybující se ve směru jízdy.
         * @param x         x-ová souřadnice středu nástupiště.
         * @param y         y-ová souřadnice středu nástupiště.
         */
        public Zastávka( Zastávka předchozí, int x, int y )
        {
            this( předchozí, předchozí.getLinka(), x, y );
        }


        /***********************************************************************
         * Vytvoří novou instanci třídy Zastávka. Její souřadnice jsou přitom
         * souřadnicemi středu kruhu, který představuje její nástupiště.
         * Zastávka převezme barvu svého předchůdce a spojí se s ním čárou
         * stejné barvy. Po vytvoření přihláší zastávku u AnimPlátna.
         * 
         * @param předchozí Předchozí zastávka, odkud přijíždějí kabiny
         *                  pohybující se ve směru jízdy.
         * @param linka     Linka, na které se bude zastávka nacházet.
         * @param x         x-ová souřadnice středu nástupiště.
         * @param y         y-ová souřadnice středu nástupiště.
         */
        private Zastávka( Zastávka předchozí, Linka linka, int x, int y )
        {
            this.linka = linka;
            this.xPos = x;
            this.yPos = y;
            nástupiště = 
                new Elipsa( x-P2, y-P2, PRŮMĚR, PRŮMĚR, linka.getBarva() );
            if( předchozí == null ) //První zastávka na lince
            {
                this.předchozí = this.následující = this;
            }
            else
            {
                napojNa( předchozí );
            }
            AP.přidej( this );
        }



    //== ABSTRAKTNÍ METODY =====================================================
    //== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =================================

        /***********************************************************************
         * Vrátí barvu zastávky.
         * 
         * @return Barva zastávky.
         */
        public Linka getLinka()
        {
            naLince();
            return linka;
        }


        /***********************************************************************
         * Vrátí pozici zastávky, tj. pozici jejího středu.
         * 
         * @return Pozice středu zastávky.
         */
        public Pozice getPozice()
        {
            naLince();
            return new Pozice( xPos, yPos );
        }


        /***********************************************************************
         * Vrátí odkaz na předchozí zastávku na trati.
         * -
         * @return Odkaz na předchozí zastávku.
         */
        public IZastávka getPředchozí()
        {
            naLince();
            return předchozí;
        }


        /***********************************************************************
         * Vrátí odkaz na následující zastávku na trati.
         * 
         * @return Odkaz na následující zastávku.
         */
        public IZastávka getNásledující()
        {
            naLince();
            return následující;
        }



    //== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ =============================

        /***********************************************************************
         * Nakreslí se dodaným kreslítkem.
         * 
         * @param g Dodané kreslítko.
         */
        public void nakresli(Kreslítko g)
        {
            if( funkční )
            {
                trať.nakresli( g );
                nástupiště.nakresli( g );
            }
        }



    //== 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 P.názevTřídy( this ) + "_" + barva + "_" + zpořadí + 
                ", pozice=[" + xPos + ";" + yPos + "], linka=" + linka;
        }



    //== NOVĚ ZAVEDENÉ METODY INSTANCÍ =========================================
                
        /***********************************************************************
         * Na zastávku (tj. za ní) napojí zastávku na zadaných souřadnicích.
         * Nepovažuje-li ji předchozí zastávka za svého následníka, 
         * nechá původního následníka napojit na sebe. 
         * Považuje-li ji za následníka, tak již další akce nedělá.
         *
         * @param předchozí  Budoucí předchozí zastávka dané stanice;
         */
        public IZastávka napoj( int x, int y )
        {
            return new Zastávka( this, x, y );
        }

        
        /***********************************************************************
         * Napojí zastávku tratí na zastávku převzatou jako předchozí.
         * Nepovažuje-li ji předchozí zastávka za svého následníka, 
         * nechá původního následníka napojit na sebe. 
         * Považuje-li ji za následníka, tak již další akce nedělá.
         *
         * @param předchozí  Budoucí předchozí zastávka dané stanice;
         */
        void napojNa( Zastávka předchozí )
        {
            if( linka != předchozí.getLinka() )
                 throw new IllegalArgumentException(
                    "Zastávka musí mít být na stejné lince jako její budoucí" +
                    " předchůdce, na kterého se nyní napojuje: \nzastávka=" +
                    linka + ", předchůdce=" + předchozí.linka );
            this.předchozí = předchozí;
            AP.nekresli();
                if( předchozí.následující != this )
                {
                    this.následující = předchozí.následující;
                    //this.následující.předchozí = this;
                    this.následující.napojNa( this );
                    předchozí.následující = this;
                }
                trať = new Čára( předchozí.getPozice(), getPozice(),
                                 linka.getBarva() );
            AP.vraťKresli();
        }
        
        
        /***********************************************************************
         * Odstraní tuto zastávku z trati a svého bývalého předchůdce napojí 
         * na svého bývalého následníka, na nějž zároveň vrátí odkaz.
         *
         * @return   Odkaz na následující zastávku. Není-li taková, vrátí null.
         */
        public Zastávka zruš()
        {
            AP.nekresli();
                if( předchozí != this )
                {
                    předchozí.následující = následující;
                    následující.napojNa( předchozí );
                }
                AP.odstraň( this );
                nástupiště = null;
                trať = null;
            AP.vraťKresli();
            funkční = false;
            return (následující == this)  ?  null :  následující;
        }
        
        
        
    //== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ====================================
        
        /***********************************************************************
         * Ověří, že zastávka je součástí linky. Není-li, vyvolá výjimku.
         */
        private void naLince() 
        {
            if( funkční )   return;
            throw new IllegalStateException(
                "Zasávka je již zrušena a nelze ji tedy používat" );
        }
        
        
        
    //== VNOŘENÉ A VNITŘNÍ TŘÍDY ===============================================
    //== TESTY A METODA MAIN ===================================================
    }//public class Zastávka extends kniha.tvary.APosuvný
    
    
    
//== TESTY A METODA MAIN =======================================================
}//public class Linka

