package shapes77.canvas_1;
/* UTF-8 codepage: Příliš žluťoučký kůn úpěl ďábelské ó. ÷ × ¤  */

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;



/*******************************************************************************
 * Třída {@code Direction8} slouží jako výčtový typ pro 8 hlavních a vedlejších
 * světových stran spolu se směrem {@link NOWHERE} pro situace,
 * kdy není možno určit směr.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2018-Summer
 */
public enum Direction8
{
//\CE== VALUES OF THE ENUMERATION TYPE =========================================

    /** Východ       = doprava.        */    EAST      ("E" ,  1,  0),
    /** Severovýchod = doprava nahoru. */    NORTH_EAST("NE",  1, -1),
    /** Sever        = nahoru.         */    NORTH     ("N" ,  0, -1),
    /** Severozápad  = doleva nahoru.  */    NORTH_WEST("NW", -1, -1),
    /** Západ        = doleva.         */    WEST      ("W" , -1,  0),
    /** Jihozápad    = doleva nahoru.  */    SOUTH_WEST("SW", -1,  1),
    /** Jih          = dolu.           */    SOUTH     ("S" ,  0,  1),
    /** Jihovýchod   = doprava dolu.   */    SOUTH_EAST("SE",  1,  1),
    /** Žádný        = nikam.          */    NOWHERE   ("0" ,  0,  0),
    ;



//##############################################################################
//\CC== REMAINING CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ====

    /** Celkový počet definovaných směrů. */
    public static final int NUM_DIRS = values().length;

    /** Maska pro dělení modulo
     *  Výpočet masky předpokládá, že počet směrů je mocninou dvou
     *  plus jedna pro směr NOWHERE). */
    private static final int MASK = NUM_DIRS-2;

    /** Odmocnina z jedné poloviny pro výpočet šikmých vzdáleností. */
    private static final double SQR = Math.sqrt(0.5);

    /** Mapa konvertující názvy směrů (s diakritikou i bez ní) a jejich zkratky
     *  na příslušné směry. */
    private static final Map<String, Direction8> name2direction =
                                                 new HashMap<>(NUM_DIRS*3);

    /** Neměnný seznam hlavních světových stran. */
    private static final List<Direction8> cardinals =
            Collections.unmodifiableList(Arrays.asList(
                    new Direction8[]{EAST, NORTH, WEST, SOUTH}));


//\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Příznak povolení operací se směrem {@link #NOWHERE}.
     *  Implicitně jsou zakázány. */
    private static boolean nowhereProhibited = false;



//##############################################################################
//\CI== CLASS (STATIC) INITIALIZER (CLASS CONSTRUCTOR) =========================

    //Inicializace statických atributů je nerealizovatelná před
    //definicí jednotlivých hodnot ==> je ji proto potřeba realizovat dodatečně
    static
    {
        if (NUM_DIRS != 9) {
            throw new RuntimeException(
                "\nNabouraný zdrojový kód - špatný počet směrů");
        }
        for (Direction8 dir : values())  {
            name2direction.put(dir.name(),   dir);
            name2direction.put(dir.shortcut, dir);
        }
    }



//\CF== CLASS (STATIC) FACTORY METHODS =========================================

    /***************************************************************************
     * Vrátí směr se zadaným názvem nebo zkratkou.
     * Bohužel není možno pojmenovat tuto metodu {@code valueOf()},
     * protože takto nazvanou metodu definuje překladač v této třídě
     * takže ji není možno přebít vlastní verzí.
     *
     * @param name Název požadovaného směru nebo jeho zkratka;
     *              při zadávání nezáleží na velikosti písmen
     * @return Požadovaný směr
     * @throws IllegalArgumentException  Neexistuje-li směr se zadaným
     *                                   názvem nebo zkratkou
     */
    public static Direction8 get(String name)
    {
        Direction8 dir = name2direction.get(name.toUpperCase());
        if (dir == null) {
            throw new IllegalArgumentException(
                  "\nNeznámý směr s daným názvem či zkratkou: " + name);
        }
        return dir;
    }


    /***************************************************************************
     * Vrátí směr se zadaným indexem.
     * Směry mají indexy přiřazené postupně podle výskytu směru
     * při otáčení proti směru hodinových ručiček.
     * {@link #EAST} má index 0, {@link #NORTH_EAST} 1 a tak dále
     * až po {@link #SOUTH_EAST} s indexem 7.
     * Za index je však možno dosadit jakékoliv celé číslo,
     * které se přepočte jako počet otoček o 45°.
     * Směr {@link #NOWHERE} je při tomto přepočtu přeskakován.
     *
     * @param index Index požadovaného směru
     * @return Požadovaný směr
     */
    public static Direction8 get(int index)
    {
        if (index < 0) {
            index = 0x7FFFFFF8 + index;
        }
        index = index & 7;  //index % 8;
        Direction8 dir = values()[index];
        return dir;
    }



//\CG== CLASS (STATIC) GETTERS AND SETTERS =====================================
//\CM== CLASS (STATIC) REMAINING NON-PRIVATE METHODS ===========================


//\CP== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS ===========================



//##############################################################################
//\IC== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===============

    /** Velikost změny příslušné složky souřadnic po přesunu
     *  na sousední políčko v daném směru. */
    private final int dx, dy;

    /** Jedno- či dvoupísmenná zkratka úplného názvu daného směru. */
    private final String shortcut;



//\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===============



//##############################################################################
//\II== INSTANCE INITIALIZERS (CONSTRUCTORS) ===================================

    /***************************************************************************
     * Vytvoří nový směr a zapamatuje si zkratku jeho názvu
     * spolu se změnami souřadnic při pohybu v daném směru.
     *
     * @param shortcut  Jedno- či dvoj-písmenná zkratka označující daný směr
     * @param dx        Změna vodorovné souřadnice
     *                  při přesunu na sousední políčko v daném směru
     * @param dy        Změna svislé souřadnice
     *                  při přesunu na sousední políčko v daném směru
     */
    private Direction8(String shortcut, int dx, int dy)
    {
        this.shortcut = shortcut;
        this.dx = dx;
        this.dy = dy;
    }



//\IA== INSTANCE ABSTRACT METHODS ==============================================
//\IG== INSTANCE GETTERS AND SETTERS ===========================================

    /***************************************************************************
     * Vrátí zkratku názvu daného směru.
     *
     * @return  Požadovaná zkratka
     */
    public String getShortcut()
    {
        return shortcut;
    }


    /***************************************************************************
     * Vrátí informaci o tom, je-li daný směr jedním ze 4 hlavních směrů,
     * tj. jedná-li se o jeden ze směrů {@link #EAST}, {@link #NORTH},
     * {@link #WEST}, {@link #SOUTH}.
     *
     * @return Jedná-li se o hlavní směr, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    private
    boolean isCardinal()
    {
        return ((this.ordinal() & 1) == 0)  &&  (this != NOWHERE);
    }



//\IM== INSTANCE REMAINING NON-PRIVATE METHODS =================================

    /***************************************************************************
     * Obdrží x-vou souřadnici políčka a vrátí x-vou souřadnici
     * sousedního políčka v daném směru.
     *
     * @param x Obdržená x-ová souřadnice
     * @return x-ová souřadnice políčka po přesunu o jedno pole v daném směru
     */
    public int nextX(int x)
    {
        verifyAllowed();
        return x + dx;
    }


    /***************************************************************************
     * Obdrží x-vou souřadnici políčka a vrátí x-vou souřadnici políčka
     * vzdáleného v daném směru o zadanou vzdálenost.
     *
     * @param x        Obdržená x-ová souřadnice
     * @param distance Vzdálenost políčka v daném směru
     * @return x-ová souřadnice vzdáleného políčka
     */
    public double nextX(int x, int distance)
    {
        verifyAllowed();
        if ((dx != 0)  &&  (dy != 0)) {
            return x + SQR*dx*distance;
        } else {
            return x + dx*distance;
        }
    }


    /***************************************************************************
     * Obdrží y-vou souřadnici políčka a vrátí y-vou souřadnici
     * sousedního políčka v daném směru.
     *
     * @param y Obdržená y-ová souřadnice
     * @return y-ová souřadnice sousedního políčka v daném směru
     */
    public int nextY(int y)
    {
        verifyAllowed();
        return y + dy;
    }


    /***************************************************************************
     * Obdrží x-vou souřadnici políčka a vrátí x-vou souřadnici políčka
     * vzdáleného v daném směru o zadanou vzdálenost.
     *
     * @param y        Obdržená y-ová souřadnice
     * @param distance Vzdálenost políčka v daném směru
     * @return y-ová souřadnice vzdáleného políčka
     */
    public double nextY(int y, int distance)
    {
        verifyAllowed();
        if ((dx != 0)  &&  (dy != 0)) {
            return y + SQR*dy*distance;
        } else {
            return y + dy*distance;
        }
    }


    /***************************************************************************
     * Vrátí změnu vodorovné souřadnice při přesunu
     * na sousední pole v daném směru.
     *
     * @return Změna x-ové souřadnice při přesunu o jedno pole v daném směru
     */
    public int dx()
    {
        verifyAllowed();
        return dx;
    }


    /***************************************************************************
     * Vrátí změnu svislé souřadnice při přesunu
     * na sousední pole v daném směru.
     *
     * @return Změna y-ové souřadnice při přesunu o jedno pole v daném směru
     */
    public int dy()
    {
        verifyAllowed();
        return dy;
    }


    /***************************************************************************
     * Vrátí směr otočený o 90° vlevo.
     *
     * @return Směr objektu po vyplnění příkazu vlevo v bok
     */
    public Direction8 leftTurn()
    {
        return turnBy(2);
    }


    /***************************************************************************
     * Vrátí směr otočený o 90° vpravo.
     *
     * @return Směr objektu po vyplnění příkazu vpravo v bok
     */
    public Direction8 rightTurn()
    {
        return turnBy(-2);
    }


    /***************************************************************************
     * Vrátí směr otočený o 180°.
     *
     * @return Směr objektu po vyplnění příkazu čelem vzad.
     */
    public Direction8 aboutTurn()
    {
        return turnBy(4);
    }


    /***************************************************************************
     * Vrátí směr otočený o 45° vlevo.
     *
     * @return Směr objektu po vyplnění příkazu nalevo vpříč.
     */
    public Direction8 halfLeft()
    {
        return turnBy(1);
    }


    /***************************************************************************
     * Vrátí směr otočený o 45° vpravo.
     *
     * @return Směr objektu po vyplnění příkazu napravo vpříč.
     */
    public Direction8 halfRight()
    {
        return turnBy(-1);
    }



//\IP== INSTANCE PRIVATE AND AUXILIARY METHODS =================================

    /***************************************************************************
     * Vrátí směr otočený o zadaný počet osminek (45°) vlevo.
     *
     * @param eighths Počet osminek, o něž se má směr otočit,
     *                přičemž záporný počet označuje otočku vpravo
     * @return Směr objektu po vyplnění příkazu
     */
    public Direction8 turnBy(int eighths)
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (eighths + ordinal())];
    }


    /***************************************************************************
     * Ověří, že se nejedná o operaci zakázanou pro směr {@link #NOWHERE};
     * není-li tomu tak, vyhodí výjimku {@link IllegalArgumentException}.
     *
     * @throws IllegalStateException
     *         Pro směr {@link #NOWHERE} je tato operace zakázána
     */
    private void verifyAllowed()
    {
        if (nowhereProhibited  &&  (this == NOWHERE)) {
            Throwable t = new Throwable();
            StackTraceElement[] aste = t.getStackTrace();
            StackTraceElement   ste  = aste[1];
            String method = ste.getMethodName();

            throw new IllegalStateException("\nThis operation is prohibited "
                                   + "for the direction NOWHERE: " + method);
        }
    }


    /***************************************************************************
     * Ověří, že se daný směr je jedním ze čtyř hlavních směrů;
     * není-li, vyhodí výjimku {@link IllegalArgumentException}.
     *
     * @throws IllegalArgumentException
     *         Směr není jedním ze čtyř hlavních směrů
     */
    private void verifyCardinal()
            throws IllegalArgumentException
    {
        if (! isCardinal()) {
            String method = violatingMethod();
            throw new IllegalStateException(
                  "\nMetodu " + method + " nelze použít pro směr " + this +
                  "\nLze ji použít pouze pro čtyři hlavní světové strany");
        }
    }


    /***************************************************************************
     * Vrátí název metody, která nevyhověla testovaným podmínkám.
     *
     * @return Název metody
     */
    private String violatingMethod()
    {
        Throwable           t      = new Throwable();
        StackTraceElement[] aste   = t.getStackTrace();
        StackTraceElement   ste    = aste[2];
        String              method = ste.getMethodName();
        return method;
    }



//##############################################################################
//\NT== NESTED DATA TYPES ======================================================
}
