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

import shapes77.geom.Direction8;
import shapes77.util.IO;



/*******************************************************************************
 * Instance třídy {@code Resizer} představují objekty typu služebník,
 * které jsou schopny plynule zvětšit nebo zmenšit velikost zadaného objektu.
 * Třída NENÍ vláknově bezpečná (thread-safe). Nepředpokládá,
 * že její instance boudou volány simultánně z různých vláken.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2021-Winter
 */
public class Resizer
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Doba mezi jednotlivými "šťouchy".*/
    private final static int PERIOD = 30;



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

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



//##############################################################################
//\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) ===============

    /** Identifikační kód (rodné číslo) instance. */
    private final int ID = ++instances;

    /** Název sestávající z názvu třídy a pořadí instance */
    private final String name;



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

    /** Specifikuje sílu "nafukování" objektu danou instanci kompresoru,
     *  tj. míru jeho přifouknutí. */
    private int power;



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

    /***************************************************************************
     * Konstruktor kompresorů se silou nafukování 1 (síla nafukování definuje
     * počet bodů, o něž se zvětší rozměr objektu po jednom "šťouchu").
     */
    public Resizer()
    {
        this(1);
    }


    /***************************************************************************
     * Vytvoří kompresor se zadanou silou nafukování (síla nafukování
     * definuje počet bodů, o něž se zvětší rozměr objektu po jednom "šťouchu").
     *
     * @param power  Síla nafukování vytvářeného kompresoru
     */
    public Resizer(int power)
    {
        this.power = power;
        this.name  = getClass().getSimpleName() +
                "[ID=" + ID + ", power=" + power + "]";
    }



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

    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na zadaný násobek jeho velikosti.
     *
     * @param multiple  Kolikrát se má zvětšit velikost daného objektu
     * @param object    Objekt, jehož velikost je upravována
     */
    public void resizeByMultipleOf(double multiple, IModular object)
    {
        resizeByMultipleOf(multiple, object, Direction8.NORTH_WEST);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na zadaný násobek jeho velikosti,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     *
     * @param multiple Kolikrát se má zvětšit velikost daného objektu
     * @param object   Objekt, jehož velikost je upravována
     * @param fixed    Směr od středu obrazce, v němž se nachází pevný bod,
     *                 který při změně rozměru nemění svoji pozici.
     *                 Směry {@link Direction8#NOWHERE} a {@code null} označují
     *                 symetrickou změnu vůči středu obrazce.
     */
    public void resizeByMultipleOf(double multiple, IModular object,
                                   Direction8 fixed)
    {
        int module = (int)(object.getModule() * multiple);
        resize(object, module, fixed);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na zadaný násobek jeho velikosti.
     * Při změně velikosti se zachovává poměr stran.
     *
     * @param multiple  Kolikrát se má zvětšit velikost daného objektu
     * @param object    Objekt, jehož velikost je upravována
     */
    public void resizeByMultipleOf(double multiple, IResizable object)
    {
        int width  = (int)Math.round(object.getWidth()  * multiple);
        int height = (int)Math.round(object.getHeight() * multiple);
        resize(object, width, height, Direction8.NORTH_WEST);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na zadaný násobek jeho velikosti,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     * Při změně velikosti se zachovává poměr stran.
     *
     * @param multiple Kolikrát se má zvětšit velikost daného objektu
     * @param object   Objekt, jehož velikost je upravována
     * @param fixed    Směr od středu obrazce, v němž se nachází pevný bod,
     *                 který při změně rozměru nemění svoji pozici.
     *                 Směry {@link Direction8#NOWHERE} a {@code null} označují
     *                 symetrickou změnu vůči středu obrazce.
     */
    public void resizeByMultipleOf(double multiple, IChangeable object,
                                   Direction8 fixed)
    {
        int width = (int)Math.round(object.getWidth()  * multiple);
        int height = (int)Math.round(object.getHeight() * multiple);
        resize(object, width, height, fixed);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na požadovanou velikost.
     *
     * @param module Požadovaný výsledný rozměr objektu
     * @param object Objekt, jehož velikost je upravována
     */
    public void resizeTo(int module, IModular object)
    {
        resize(object, module, Direction8.NORTH_WEST);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na požadovanou velikost,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     *
     * @param module Požadovaný výsledný rozměr objektu
     * @param object Objekt, jehož velikost je upravována
     * @param fixed  Směr od středu obrazce, v němž se nachází pevný bod,
     *               který při změně rozměru nemění svoji pozici.
     *               Směry {@link Direction8#NOWHERE} a {@code null} označují
     *               symetrickou změnu vůči středu obrazce.
     */
    public void resizeTo(int module, IModular object, Direction8 fixed)
    {
        resize(object, module, fixed);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na požadovanou velikost.
     *
     * @param width  Nastavovaná šířka objektu
     * @param height Nastavovaná výška objektu
     * @param object Objekt, jehož velikost je upravována
     */
    public void resizeTo(int width, int height, IResizable object)
    {
        resize(object, width, height, Direction8.NORTH_WEST);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt na požadovanou velikost,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     *
     * @param width  Nastavovaná šířka objektu
     * @param height Nastavovaná výška objektu
     * @param object Objekt, jehož velikost je upravována
     * @param fixed  Směr od středu obrazce, v němž se nachází pevný bod,
     *               který při změně rozměru nemění svoji pozici.
     *               Směry {@link Direction8#NOWHERE} a {@code null} označují
     *               symetrickou změnu vůči středu obrazce.
     */
    public void resizeTo(int width, int height, IChangeable object,
                         Direction8 fixed)
    {
        resize(object, width, height, fixed);
    }


     /***************************************************************************
     * Zvětší/zmenší zadaný objekt o zadanou velikost.
     *
     * @param resizing Požadovaná změna rozměru
     * @param object   Objekt, jehož velikost je upravována
     */
    public void resizeBy(int resizing, IModular object)
    {
        resizeBy(resizing, object, Direction8.NORTH_WEST);
    }


     /***************************************************************************
     * Zvětší/zmenší zadaný objekt o zadanou velikost,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     *
     * @param resizing Požadovaná změna rozměru
     * @param object   Objekt, jehož velikost je upravována
     * @param fixed    Směr od středu obrazce, v němž se nachází pevný bod,
     *                 který při změně rozměru nemění svoji pozici.
     *                 Směry {@link Direction8#NOWHERE} a {@code null} označují
     *                 symetrickou změnu vůči středu obrazce.
     */
    public void resizeBy(int resizing, IModular object, Direction8 fixed)
    {
        resize(object, object.getModule() + resizing, fixed);
    }


    /***************************************************************************
     * Zvětší/zmenší zadaný objekt o zadanou velikost.
     *
     * @param dx     Změna rozměru ve vodorovném směru
     * @param dy     Změna rozměru ve svislém směru
     * @param object Objekt, jehož velikost je upravována
     */
    public void resizeBy(int dx, int dy, IResizable object)
    {
        int width  = object.getWidth()  + dx;
        int height = object.getHeight() + dy;
        resize(object, width, height, Direction8.NORTH_WEST);
    }


     /***************************************************************************
     * Zvětší/zmenší zadaný objekt o zadanou velikost,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     * Při změně velikosti se zachovává poměr stran.
     *
     * @param dx     Změna rozměru ve vodorovném směru
     * @param dy     Změna rozměru ve svislém směru
     * @param object Objekt, jehož velikost je upravována
     * @param fixed  Směr od středu obrazce, v němž se nachází pevný bod,
     *               který při změně rozměru nemění svoji pozici.
     *               Směry {@link Direction8#NOWHERE} a {@code null} označují
     *               symetrickou změnu vůči středu obrazce.
     */
    public void resizeBy(int dx, int dy, IChangeable object, Direction8 fixed)
    {
        int width  = object.getWidth()  + dx;
        int height = object.getHeight() + dy;
        resize(object, width, height, fixed);
    }


    /***************************************************************************
     * Převádí instanci na řetězec - používá se pro účely ladění.
     * Vrácený řetězec obsahuje název třídy následovaný identifikačním číslem
     * dané instance, které určuje pořadí dané instance mezi instancemi této
     * třídy, a nastavenou rychlostí změny.
     *
     * @return  Řetězec charakterizující instanci
     */
    @Override
    public String toString()
    {
        return name;
    }



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

     /***************************************************************************
     * Zvětší/zmenší zadaný objekt na požadovanou velikost,
     * přičemž během změny velikosti nebude zadaný bod
     * na hraně opsaného obdélníku měnit svoji polohu.
     *
     * @param object Objekt, jehož velikost je upravována
     * @param module Požadovaný výsledný modul objektu
     * @param fixed  Směr od středu obrazce, v němž se nachází pevný bod,
     *               který při změně rozměru nemění svoji pozici.
     *               Směry {@link Direction8#NOWHERE} a {@code null} označují
     *               symetrickou změnu vůči středu obrazce.
     */
    private void resize(IModular object, int module, Direction8 fixed)
    {
        resize(new Modular2Changeable(object), module, module, fixed);
    }


    /***************************************************************************
     * Nafoukne či vyfoukne zadaný objekt na požadovanou velikost.
     * Nejprve ale zabezpečí, aby byl objekt zobrazen na plátně.
     *
     * @param object Objekt, jehož velikost je upravována
     * @param width  Požadovaná výsledná šířka objektu
     * @param height Požadovaná výsledná výška objektu
     * @param fixed  Směr od středu obrazce, v němž se nachází pevný bod,
     *               který při změně rozměru nemění svoji pozici.
     *               Směry {@link Direction8#NOWHERE} a {@code null} označují
     *               symetrickou změnu vůči středu obrazce.
     */
    private void resize(IResizable object, int width, int height,
                        Direction8 fixed)
    {
        if ((width <0)  ||  (height < 0)) {
            throw new RuntimeException(
                "\nObjekt není možno zmenšit na zápornou velikost");
        }
        if (width ==0)  { width  = 1; }
        if (height==0)  { height = 1; }
        int    ow = object.getWidth();
        int    oh = object.getHeight();
        int    horizontal = width  - ow;
        int    vertical   = height - oh;
        int    numSteps   = Math.max(1,
                            (int)(Math.hypot(horizontal, vertical) / power));
        double dx = (double)horizontal / numSteps;
        double dy = (double)vertical   / numSteps;
        execute(object, numSteps, dx, dy, fixed);
    }


    /***************************************************************************
     * Výkonná metoda, která zařídí vlastní nafouknutí, resp. vyfouknutí
     * zadaného objektu na základě přípravených parametrů.
     *
     * @param object    Objekt, jehož velikost měníme.
     * @param numSteps  Počet kroků, v nichž velikost objektu změníme.
     * @param dx        Zvětšení šířky objektu v jednom kroku.
     * @param dy        Zvětšení výšky objektu v jednom kroku.
     * @param fixed     Směr, kterým leží pevný bod.
     */
    private void execute(IResizable object, int numSteps,
                         double dx, double dy, Direction8 fixed)
    {
        if (fixed == null) {
            fixed = Direction8.NOWHERE;
        }
        IChangeable icho = null;    //Object casted to IChangeable
        double dxx = 0, dyy = 0;    //Position change in each jerk
        double x   = 0, y   = 0;    //Coordinates after jerk
        if (fixed != Direction8.NORTH_WEST)
        {
            if (! (object instanceof IChangeable)) {
                throw new IllegalArgumentException(
                      "\nOnly the instances of IChangeable can be resized " +
                        "with the fixed point in the direction " + fixed);
            }
            icho = (IChangeable)object;
            x  = icho.getX() + .4;
            y  = icho.getY() + .4;
            if (     (fixed == Direction8.SOUTH_EAST) ||
                     (fixed == Direction8.EAST)       ||
                     (fixed == Direction8.NORTH_EAST))
            {
                dxx = -dx;
            }
            else if ((fixed == Direction8.NORTH)  ||
                     (fixed == Direction8.SOUTH)  ||
                     (fixed == Direction8.NOWHERE))
            {
                dxx = -dx/2;
            }
            if (     (fixed == Direction8.SOUTH_WEST)  ||
                     (fixed == Direction8.SOUTH)       ||
                     (fixed == Direction8.SOUTH_EAST))
            {
                dyy = -dy;
            }
            else if ((fixed == Direction8.EAST)  ||
                     (fixed == Direction8.WEST)  ||
                     (fixed == Direction8.NOWHERE))
            {
                dyy = -dy/2;
            }
        }
        //Konstantu připočítáváme proto, aby skoky byly vyrovnanější
        int    oldWidth  = object.getWidth();
        int    oldHeight = object.getHeight();
        double width     = oldWidth  + .4;
        double height    = oldHeight + .4;

        while (numSteps-- > 0) {
            IO.pause(PERIOD);
            width  += dx;
            height += dy;
            object.setSize((int)width, (int)height);
            if (fixed != Direction8.NORTH_WEST) {
                x += dxx;
                y += dyy;
                icho.setPosition((int)x, (int)y);
            }
        }
    }



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

     /***************************************************************************
     * Instance třídy{@code Modular2Changeable} adaptuje instance interfejsu
     * {@link IModular} na instance interfejsu {@link IChangeable}.
     */
    static
    public class Modular2Changeable
      implements IChangeable
    {
        private final IModular delegate;

        /***********************************************************************
         * Konstruktor si pouze zapamatuje převáděný objekt.
         * @param delegate Převáděný objekt
         */
        public Modular2Changeable(IModular delegate) {
            this.delegate = delegate;
        }
        @Override public int getWidth() {
            return delegate.getModule();
        }
        @Override public int getHeight() {
            return delegate.getModule();
        }
        @Override public void setSize(int width, int height) {
            if (width != height) {
                throw new IllegalArgumentException(
                      "\nThe width and height of a modular object should " +
                        "be the same: width=" + width + ", height=" + height);
            }
            delegate.setModule(width);
        }
        @Override public int getX() {
            return delegate.getX();
        }
        @Override public int getY() {
            return delegate.getY();
        }
        @Override public void setPosition(int x, int y) {
            delegate.setPosition(x, y);
        }
        @Override
        public void paint() {
            delegate.paint();
        }
        @Override
        public void rubOut() {
            delegate.rubOut();
        }
    }

}
