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

import shapes77.geom.IChangeable;
import shapes77.geom.Direction8;
import shapes77.geom.IDirectable;
import shapes77.util.ICopyable;

import java.util.ArrayList;
import java.util.List;



/*******************************************************************************
 * Instance třídy {@code Multishape} představují složitější geometrické tvary
 * určené pro práci na virtuálním plátně
 * při prvním seznámení s třídami a objekty.
 * Tyto tvary mohou být složeny z několika tvarů jednodušších,
 * které jsou instancemi rozhraní {@link ICanvasShape},
 * ve zvláštních případech interfejsu {@link IChangeable} &ndash; v takovém
 * případě pak ale není vytvořený mnohotvar kopírovatelný.
 * <p>
 * Mnohotvar je postupně skládán z řady jednodušších tvarů,
 * které musejí být instancemi rozhraní {@link ICanvasShape}
 * (případně. {@link IChangeable}). Jiné požadavky na ně kladeny nejsou.
 * Při sestavování mnohotvar automaticky upravuje
 * interní informaci o své pozici a rozměru tak,
 * aby pozice byla neustále v levém rohu opsaného obdélníku
 * a rozměr mnohotvaru odpovídal rozměru tohoto obdélníku.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2020-Winter
 */
public class Multishape
  implements ICanvasShape, IDirectable
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Směr, kam bude mnohotvar nasměrován v případě,
     *  když uživatel žádný preferovaný směr nezadá.    */
    public static final Direction8 DEFAULT_DIRECTION = Direction8.NORTH;

    /** Plátno, na které se bude instance kreslit. */
    private static final Canvas CANVAS;

    /** Kreslítko, jehož pomocí je možno na plátno kreslit. */
//    private static final Canvas.Painter PAINTER;



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

    /** Počet vytvořených instancí. */
    private static int count = 0;



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

    static {
        CANVAS  = Canvas.getInstance();
//        PAINTER = Canvas.Painter.getInstance();
    }



//\CF== CLASS (STATIC) FACTORY METHODS =========================================
//\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) ===============

    /** ID instance = pořadí vytvoření dané instance v rámci třídy. */
    private final int ID = ++count;

    /** Seznam součástí daného mnohotvaru. */
    private final List<Part> parts = new ArrayList<>();



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

    /** Výchozí název instance sestavený z názvu třídy
     *  následovaného znakem podtržení a ID instance. */
    private String name = getClass().getSimpleName() + "_" + ID;

    /** Dokud je atribut {@code false}, je možné do mnohotvaru
     *  přidávat další součásti. */
    private boolean creationDone = false;

    /** Vodorovná (x-ová) souřadnice instance. */
    private int xPos;

    /** Svislá (y-ová) souřadnice instance. */
    private int yPos;

    /** Šířka instance. */
    protected int width;

    /** Výška instance. */
    protected int height;

    /** Příznak kopírovatelnosti daného mnohotvaru. */
    private boolean copyable = true;

    /** Směr, do nějž je mnohotvar natočen. */
    private Direction8 direction;



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

    /***************************************************************************
     * Vytvoří prázdný mnohotvar očekávající, že jeho jednotlivé části
     * budou teprve dodány pomocí metody {@link #addShapes(ICanvasShape...)}.
     * <p>
     * Ukončení sestavování mnohotvaru je třeba oznámit zavoláním metody
     * {@link #creationDone()}.
     * Dokud není sestavování ukončeno,
     * není možno nastavovat pozici ani rozměr vznikajícího mnohotvaru.
     * Je však možno se na ně zeptat
     * a současně je možno rozdělaný mnohotvar nakreslit.
     */
    public Multishape()
    {
        this(null);
    }


    /***************************************************************************
     * Vytvoří mnohotvar skládající se z kopií zadaných tvarů
     * a otočený do implicitního směru;
     * do tohoto mnohotvaru již nebude možno přidávat další tvary.
     * Vkládané tvary jsou skládány na sebe, takže první vložený bude
     * zobrazen vespod a poslední vložený nahoře nade všemi.
     *
     * @param part1 Předloha první ze součástí mnohotvaru
     * @param parts Předlohy dalších součástí mnohotvaru
     */
    public Multishape(ICanvasShape part1, ICanvasShape... parts)
    {
        this(DEFAULT_DIRECTION, part1, parts);
    }


    /***************************************************************************
     * Vytvoří mnohotvar skládající se ze zadaných tvarů
     * a otočený do zadaného směru;
     * do tohoto mnohotvaru již nebude možno přidávat další tvary.
     * Tvary jsou skládány na sebe, takže první vložený bude zobrazen vespod
     * a poslední vložený nahoře nade všemi.
     *
     * @param direction Směr, do nějž bude mnohotvar natočen;
     *                  tento směr musí být jedním ze 4 hlavních směrů
     * @param part1     První ze součástí mnohotvaru
     * @param parts     Další součásti mnohotvaru
     */
    public Multishape(Direction8 direction, ICanvasShape part1,
                                            ICanvasShape... parts)
    {
        setDirectionInternal(direction);
        if (part1 != null) {
            addShapes(part1);
            addShapes(parts);
            //Jakmile jsou zadány vkládané tvary, konstruktor tvorbu ukončí
            creationDone = true;
        }
    }



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

    /***************************************************************************
     * Vrátí informaci o tom, je-li daný mnohotvar kopírovatelný.
     *
     * @return Je-li kopírovatelný, vrátí {@code true},
     *         jinak vrátí {@code false}
     */
    public boolean isCopyable()
    {
        return copyable;
    }


    /***************************************************************************
     * Vrátí směr, do nějž je instance otočena.
     *
     * @return  Instance třídy {@code Direction8} definující
     *          aktuálně nastavený směr
     */
    @Override
    public Direction8 getDirection()
    {
        return direction;
    }


    /***************************************************************************
     * Otočí instanci do zadaného směru.
     * Souřadnice instance se otočením nezmění.
     *
     * @param direction Směr, do nějž má být instance otočena
     */
    @Override
    public void setDirection(Direction8 direction)
    {
        verifyDone();
        if (getWidth() != getHeight()) {
            throw new IllegalStateException(
                "\nNastavovat směr je možno pouze pro čtvercový mnohotvar: "
                + this);
        }
        if (direction == this.direction) {
            return;
        }
        Direction8 oldDirection = this.direction;
        setDirectionInternal(direction);
        //Vím, že nevyhodil výjimku, a že proto nastavuji korektní směr
        turnTo(direction, oldDirection);
    }


    /***************************************************************************
     * Nastaví zadaný směr jako výchozí směr vytvářené instance.
     * Tato metoda instancí neotáčí, pouze nastavují výchozí směr.
     * Instance je implicitně považována za otočenou na sever.
     * Má-li mít instance jiný výchozí směr,
     * musí být nastaven před jejím dokončením.
     *
     * @param initialDirection Nastavovaný výchozí směr instance
     */
    public void setInitialDirection(Direction8 initialDirection)
    {
        try {
            verifyDone();
        }
        catch(IllegalStateException e) {
            //Vyhodil výjimku => ještě není ukončena tvorba =>
            //=> Smím nastavit počáteční směr
            setDirectionInternal(initialDirection);
            return;
        }
        //Nevyhodil výjimku => Tvoba je ukončena =>
        //=> Počáteční směr již nelze nastavit
        throw new IllegalStateException(
            "\nPočáteční směr mnohotvaru lze nastavit pouze" +
            "před jeho dokončením");
    }


    /***************************************************************************
     * Vrátí název instance.
     * Výchozí podoba názvu názvu sestává z názvu třídy
     * následovaného znakem podtržení a ID instance.
     * Název je ale možné kdykoliv změnit.
     *
     * @return  Název instance
     */
    public String getName()
    {
        return name;
    }


    /***************************************************************************
     * Nastaví nový název instance.
     *
     * @param name  Nový název instance
     */
    public void setName(String name)
    {
        this.name = name;
    }


    /***************************************************************************
     * Vrátí x-ovou (vodorovnou) souřadnici pozice instance,
     * tj. vodorovnou souřadnici levého horního rohu opsaného obdélníku.
     *
     * @return  Aktuální vodorovná (x-ová) souřadnice instance,
     *          x=0 má levý okraj plátna, souřadnice roste doprava
     */
    @Override
    public int getX()
    {
        return xPos;
    }


    /***************************************************************************
     * Vrátí y-ovou (svislou) souřadnici pozice instance,
     * tj. svislou souřadnici levého horního rohu opsaného obdélníku.
     *
     * @return  Aktuální svislá (y-ová) souřadnice instance,
     *          y=0 má horní okraj plátna, souřadnice roste dolů
     */
    @Override
    public int getY()
    {
        return yPos;
    }


    /***************************************************************************
     * Přemístí instanci na zadanou pozici.
     * Všechny její součásti jsou přesouvány současně jako jeden objekt.
     * Pozice instance je přitom definována jako pozice
     * levého horního rohu opsaného obdélníku.
     *
     * @param x  Nově nastavovaná vodorovná (x-ová) souřadnice instance,
     *           x=0 má levý okraj plátna, souřadnice roste doprava
     * @param y  Nově nastavovaná svislá (y-ová) souřadnice instance,
     *           y=0 má horní okraj plátna, souřadnice roste dolů
     */
    @Override
    public void setPosition(int x, int y)
    {
        verifyDone();
        int dx = x - getX();
        int dy = y - getY();
        {
            for (Part part : parts) {
                IChangeable shape = part.changeable;
                shape.setPosition(dx + shape.getX(), dy + shape.getY());
            }
        }
        this.xPos = x;    //Nastavuji hodnoty pro celý tvar
        this.yPos = y;
        paint();
    }


    /***************************************************************************
     * Vrátí šířku instance v bodech.
     * Šířka instance jsou přitom definována jako šířka
     * opsaného obdélníku.
     *
     * @return  Aktuální šířka instance v bodech
     */
    @Override
    public int getWidth()
    {
        return width;
    }


    /***************************************************************************
     * Vrátí výšku instance v bodech.
     * Výška instance jsou přitom definována jako výška
     * opsaného obdélníku.
     *
     * @return  Aktuální výška instance v bodech
     */
    @Override
    public int getHeight()
    {
        return height;
    }


    /***************************************************************************
     * Nastaví nové rozměry instance.
     * Upraví rozměry a pozice všech jeho součástí tak,
     * aby výsledný mnohotvar měl i při novém rozměru
     * stále stejný celkový vzhled.
     * Rozměry instance jsou přitom definovány jako rozměry
     * opsaného obdélníku.
     * Nastavované rozměry musí být nezáporné,
     * místo nulového rozměru se nastaví rozměr rovný jedné.
     *
     * @param width    Nově nastavovaná šířka; šířka &gt;= 0
     * @param height   Nově nastavovaná výška; výška &gt;= 0
     */
    @Override
    public void setSize(int width, int height)
    {
        verifyDone();
        if ((width < 0) || (height < 0)) {
            throw new IllegalArgumentException(
                            "\nRozměry nesmějí být záporné: width="
                          + width + ", height=" + height);
        }
        {
            //Correct the sizes and positions of particular parts
            for (Part part : parts) {
                part.afterResizing(width, height);
            }
            //Set attributes of the whole multishape
            this.width  = Math.max(1, width);
            this.height = Math.max(1, height);
        } paint();
    }


    /***************************************************************************
     * Vrátí počet tvarů tvořících daný mnohotvar.
     * Obsahuje-li daný mnohotvar nějaký jiný mnohotvar,
     * je tento mnohotvar považován za jediný objekt.
     *
     * @return  Počet tvarů tvořících daný mnohotvar
     */
    public int getNumberOfShapes()
    {
        return parts.size();
    }


    /***************************************************************************
     * Vrátí počet jednoduchých tvarů tvořících daný mnohotvar.
     * Obsahuje-li daný mnohotvar nějaký jiný mnohotvar,
     * jsou rekurzivně započteny všechnz jeho jednoduché součásti.
     *
     * @return  Počet tvarů tvořících daný mnohotvar
     */
    public int getNumberOfSimpleShapes()
    {
        int number = 0;
        for (Part part : parts) {
            if (part.changeable instanceof Multishape) {
                number += ((Multishape)(part.changeable))
                                            .getNumberOfSimpleShapes();
            }
            else {
                number++;
            }
        }
        return number;
    }



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

    /***************************************************************************
     * Přidá do mnohotvaru kopie zadaných tvarů
     * a příslušně upraví novou pozici a velikost mnohotvaru.
     *
     * @param shapes  Přidávané tvary
     */
    public final void addShapes(ICanvasShape... shapes)
    {
        if (creationDone) {
            throw new IllegalStateException("\nAttempt to add a shape after "
                  + "after finishing the creation of the mutlishape " + name);
        }
        for (ICanvasShape shape : shapes) {
            ICanvasShape ish = shape.copy();
            addTheShape(ish);
        }
    }


    /***************************************************************************
     * Přidá do mnohotvaru zadaný objekt (tj. ne jeho kopii)
     * a příslušně upraví novou pozici a velikost mnohotvaru.
     * Neimplementuje-li přidávaný tvar interfejs
     * {@link ICopyable},
     * bude celý mnohotvar označen za nekopírovatelný.
     *
     * @param <T>    Skutečný typ argumentu
     * @param shape  Přidávaný tvar
     * @return Instance daného mnohotvaru, aby bylo možno příkazy řetězit
     */
    public final <T extends IChangeable & ICanvasPaintable>
           Multishape addTheShape(T shape)
    {
        if (creationDone) {
            throw new IllegalStateException("\nAttempt to add a shape "
                    + "after finishing the creation of the mutlishape "
                    + getName());
        }
        //asx, asy, asw, ash = x, y, width height of the added shape
        int asx = shape.getX();
        int asy = shape.getY();
        int asw = shape.getWidth();
        int ash = shape.getHeight();

        if (! (shape instanceof ICopyable)) {
            copyable = false;
        }
        if (parts.isEmpty())  //The added shape is the first one
        {
            xPos   = asx;
            yPos   = asy;
            width  = asw;
            height = ash;
            parts.add(new Part(shape, asx, asy, asw, ash));
            return this;                            //==========>
        }

        //Přídávaný tvar není prvním
        //Zapamatuji si původní parametry, aby je pak bylo možno porovnávat
        //s upravenými po zahrnutí nového tvaru
        int oldX      = xPos;
        int oldY      = yPos;
        int oldWidth  = width;
        int oldHeight = height;
        boolean change = false;

        if (asx < xPos)
        {   //The added shape reach behind the lef border
            width += xPos - asx;
            xPos   = asx;
            change = true;
        }
        if (asy < yPos)
        {   //The added shape reach behind the upper border
            height += yPos - asy;
            yPos   = asy;
            change = true;
        }
        if ((xPos + width) < (asx + asw))
        {   //The added shape reach behind the right border
            width = asx + asw - xPos;
            change = true;
        }
        if ((yPos + height) < (asy + ash))
        {   //The added shape reach behind the bottom border
            height = asy + ash - yPos;
            change = true;
        }
        //Now the attributes xPos, yPos, width a height have values
        //corresponding to the multishape including the added shape

        //If something have changed, I have to recompute all the parts
        if (change) {
            for (Part part : parts) {
                part.afterAddition(oldX, oldY, oldWidth, oldHeight);
            }
        }
        parts.add(new Part(shape));
        return this;
    }


    /***************************************************************************
     * Vytvoří stejně velkou a stejně umístěnou
     * hlubokou kopii daného mnohotvaru.
     * Termín hluboká kopie označuje skutečnost,
     * že tvary, které budou součástí vytvořené kopie,
     * budou kopiemi odpovídajících součástí originálu.
     *
     * @return Požadovaná kopie
     */
    @Override
    public Multishape copy()
    {
        if (! copyable) {
            throw new IllegalStateException(
                    "\nDaný mnohotvar není kopírovatelný");
        }
        ICanvasShape[] shapeArray = new ICanvasShape[parts.size()-1];
        ICanvasShape   shape1     = (ICanvasShape)parts.get(0).changeable;
        for (int i = 0; i < shapeArray.length; i++) {
            Part part = parts.get(i+1);
            shapeArray[i] = (ICanvasShape)part.changeable;
        }
        Multishape copy = new Multishape(shape1, shapeArray);
        return copy;
    }


    /***************************************************************************
     * Ukončí tvorbu mnohotvaru;
     * od této chvíle již nebude možno přidat žádný další objekt.
     */
    public void creationDone()
    {
        if (parts.size() < 1) {
            throw new IllegalStateException(
                    "\nThe multishape has to have at least one part");
        }
        creationDone = true;
    }


    /***************************************************************************
     * Zobrazí svoji instanci, tj.vykreslí její obraz na plátno.
     */
    @Override
    public void paint()
    {
        for (Part part : parts) {
            part.paintable.paint();
        }
    }


    /***************************************************************************
     * Smaže obraz své instance z plátna (nakreslí ji barvou pozadí plátna).
     */
    @Override
    public void rubOut()
    {
        for (Part part : parts)
        {
            part.paintable.rubOut();
        }
    }


    /***************************************************************************
     * Vrátí textový podpis instance, tj. její řetězcovou reprezentaci.
     * Používá se především při ladění.
     *
     * @return Název instance následovaný jejími souřadnicemi,
     *         rozměry a barvou
     */
    @Override
    public String toString()
    {
        return name + "[x=" + xPos + ", y=" + yPos
             + ", width=" + width + ", height=" + height
             + ", direction=" + direction
             + ", ID=" + ID + "]";
    }



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

    /***************************************************************************
     * Otočí instanci do zadaného směru bez kontroly její dokončenosti.
     *
     * @param direction Směr, do nějž má být instance otočena
     */
    private void setDirectionInternal(Direction8 direction)
    {
        if (direction.isCardinal()) {
            this.direction = direction;
        }
        else {
            throw new IllegalArgumentException(
                "\nMnohotvar lze natočit pouze do jednoho ze čtyř hlavních " +
                "směrů, požadováno: " + direction);
        }
    }


    /***************************************************************************
     * Otočí instanci do zadaného směru bez kontroly její dokončenosti.
     *
     * @param toDirection   Směr, do nějž má být instance otočena
     * @param fromDirection Směr, do nějž je instance otočena
     */
    private void turnTo(Direction8 toDirection, Direction8 fromDirection)
    {
        if (toDirection == Direction8.NOWHERE) {
            return;
        }
        int module   = getWidth();
        int distance = fromDirection.ordinalDistanceTo(toDirection);
        for (Part part : parts) {
            if (part.changeable instanceof IDirectable) {
                Direction8 dirFrom = ((IDirectable)part.changeable)
                                                       .getDirection();
                Direction8 dirTo   = dirFrom.turnBy(distance);
                ((IDirectable)part.changeable).setDirection(dirTo);
            }
            double x, y, w, h;

            switch(distance) //Přepočet závisí na cílovém směru
            {
                case -6:
                case +2:
                    x = part.dy;
                    y = 1 -  part.dx - part.dw;
                    w = part.dh;
                    h = part.dw;
                    break;

                case -4:
                case +4:
                    x = 1  -  part.dx  -  part.dw;
                    y = 1  -  part.dy  -  part.dh;
                    w = part.dw;
                    h = part.dh;
                    break;

                case -2:
                case +6:
                    x = 1  -  part.dy  -  part.dh;
                    y = part.dx;
                    w = part.dh;
                    h = part.dw;
                    break;

                default:
                    throw new RuntimeException(
                            "\nNení možné otočit oblast ze směru " +
                            fromDirection + " do směru " + this);
            }
            part.dx = x;
            part.dy = y;
            part.dw = w;
            part.dh = h;
            part.afterResizing(module, module);
        }
    }


    /***************************************************************************
     * Zkontroluje dokončenost konstrukce objektu a není-li objekt dokončen,
     * vyhodí výjimku {@code IllegalStateException}.
     *
     * @throws IllegalStateException Objekt ještě není dokončen
     */
    private void verifyDone()
    {
        if (creationDone) {
            return;
        }
        Throwable ex = new Throwable();
        StackTraceElement[] aste = ex.getStackTrace();
        String method = aste[1].getMethodName();
        throw new IllegalStateException(
            "\nNedokončený tvar nemůže volat metodu: " + method);
    }



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

////////////////////////////////////////////////////////////////////////////////
//\NC1 /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    /***************************************************************************
     * Instance třídy {@code Part} slouží jako přepravky pro uchovávání
     * pomocných informací pro co nelepší změnu velikosti mnohotvaru.
     */
    private final class Part
    {
    //\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==========
    //\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==========



    //##########################################################################
    //\CI== CLASS (STATIC) INITIALIZER (CLASS CONSTRUCTOR) =====================
    //\CF== CLASS (STATIC) FACTORY METHODS =====================================
    //\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) ===========
    //\IV== INSTANCE VARIABLES (VARIABLE INSTANCE ATTRIBUTES/FIELDS) ===========

        /** Tvar tvořící příslušnou část mnohotvaru. */
        IChangeable changeable;

        /** Ta samá část prezentovaná jako paintable. */
        ICanvasPaintable paintable;

        /** Podíl odstupu od levého kraje mnohotvaru
         *  na jeho celkové šířce. */
        double dx;

        /** Podíl odstupu od horního kraje mnohotvaru
         *  na jeho celkové výšce. */
        double dy;

        /** Podíl šířky části k celkové šířce mnohotvaru. */
        double dw;

        /** Podíl výšky části k celkové výšce mnohotvaru. */
        double dh;



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

        /***********************************************************************
         * Vytvoří přepravku charakterizující zadanou součást a zapamatuje si
         * v ní relativní pozici a velikost této součásti
         * v rámci současné podoby mnohotvaru.
         *
         * @param part    Tvar, jehož podíl na mnohotvaru si chceme zapamatovat
         */
        <T extends IChangeable & ICanvasPaintable>
        Part(T part)
        {
            this(part, xPos, yPos, width, height);
        }


        /***********************************************************************
         * Vytvoří přepravku charakterizující zadanou součást a zapamatuje si
         * v ní relativní pozici a velikost této součásti
         * v rámci současné podoby mnohotvaru.
         *
         * @param part    Tvar, jehož podíl na mnohotvaru si chceme zapamatovat
         * @param x       Aktuální vodorovná souřadnice vytvářeného mnohotvaru
         * @param y       Aktuální svislá    souřadnice vytvářeného mnohotvaru
         * @param width   Aktuální šířka vytvářeného mnohotvaru
         * @param height  Aktuální výška vytvářeného mnohotvaru
         */
        <T extends IChangeable & ICanvasPaintable>
        Part(T part, int x, int y, int width, int height)
        {
            this.changeable = part;
            this.paintable  = part;

            int      partX      = part.getX();
            int      partY      = part.getY();
            int      partWidth  = part.getWidth();
            int      partHeight = part.getHeight();
            double   dblW       = width;
            double   dblH       = height;

            dx = (partX - x) / dblW;
            dy = (partY - y) / dblH;
            dw = partWidth   / dblW;
            dh = partHeight  / dblH;
        }



    //\IA== INSTANCE ABSTRACT METHODS ==========================================
    //\IG== INSTANCE GETTERS AND SETTERS =======================================
    //\IM== INSTANCE REMAINING NON-PRIVATE METHODS =============================

        /***********************************************************************
         * Aktualizuje uchovávanou relativní pozici a rozměry dané součásti
         * v rámci celého mnohotvaru  po přidání nové součásti
         * vedoucí ke změně pozice a/nebo rozměru mnohotvaru.
         *
         * @param ox  Původní vodorovná souřadnice
         * @param oy  Původní svislá    souřadnice
         * @param ow  Původní šířka
         * @param oh  Původní výška
         */
        void afterAddition(int ox, int oy, int ow, int oh)
        {
            //Souřadnice se mohou pouze zmenšovat
            dx = (ox - xPos + dx*ow) / width;
            dy = (oy - yPos + dy*oh) / height;

            dw = dw * ow / width;
            dh = dh * oh / height;
        }


        /***********************************************************************
         * Aktualizuje uchovávanou relativní pozici a rozměry dané součásti
         * v rámci celého mnohotvaru po změně jeho velikosti.
         *
         * @param width   Nastavovaná šířka celého mnohotvaru
         * @param height  Nastavovaná výška celého mnohotvaru
         */
        void afterResizing(int width, int height)
        {
            changeable.setPosition(
                  (int)Math.round(Multishape.this.xPos + dx*width),
                  (int)Math.round(Multishape.this.yPos + dy*height));
            changeable.setSize((int)Math.round(dw*width),
                          (int)Math.round(dh*height));
        }


        /***********************************************************************
         * Vrátí textovou reprezentaci všech atributů.
         *
         * @return Textová reprezentace všech atributů
         */
        @Override
        public String toString()
        {
            return "Part[shape=" + changeable + ", dx=" + dx + ", dy=" + dy +
                   ", dw=" + dw + ", dh=" + dh + "]";
        }



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



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

}
