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

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

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;

import java.net.MalformedURLException;
import java.net.URL;



/*******************************************************************************
 * Instance třídy {@code Picture} představují obrázky,
 * které je možné načíst ze souborů nebo vytvořit z oblasti plátna.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 2020-Winter
 */
public class Picture
  implements ICMShape, IDirectable, ICopyable
{
//\CC== CLASS CONSTANTS (CONSTANT CLASS/STATIC ATTRIBUTES/FIELDS) ==============
//\CV== CLASS VARIABLES (VARIABLE CLASS/STATIC ATTRIBUTES/FIELDS) ==============

    /** Počet vytvořených instancí. */
    private static int countCreated = 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) ===============

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

    /** Obalený obrázek. */
    private final Image image;

    /** Rozměry originálního obrázku. */
    private final int origWidth, origHeight;



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

    /** Afinní transformace zodpovědná za změny velikosti a pozice obrázku.
     *  Nastavuje se při změnách pozice, rozměru a natočení
     *  a používá se při překreslování objektu. */
    AffineTransform aft;

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

    /** Směr, do nějž je daný mnohotvar natočen. */
    private Direction8 initialDirection = Direction8.NORTH;

    /** Směr, do nějž je obrázek natočen. */
    private Direction8 direction = initialDirection;

    /** Příznak toho, že je již výchozí směr nastaven či použit. */
    private boolean directionSet;

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

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

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

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



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

    /***************************************************************************
     * Přečte ze zadaného souboru obrázek, který bude kreslit
     * na zadaných souřadnicích; pomocí úvodních hvězdiček
     * je možno zadat, zda se daný soubor bude hledat ve složce
     * balíčku třídy volající metody (pak musí jméno předcházet *&#47;),
     * nebo ve složce kořenového balíčku (pak musí předcházet **&#47;);
     *
     * @param x    Vodorovná souřadnice levého horního rohu obrázku
     * @param y    Svislá souřadnice levého horního rohu obrázku
     * @param file Název souboru, v němž je obrázek uložen
     */
    public Picture(int x, int y, String file)
    {
        this(x, y, file, null);
    }


    /***************************************************************************
     * Přečte ze zadané URL adresy obrázek, který bude kreslit
     * na zadaných souřadnicích; pomocí úvodních hvězdiček
     * je možno zadat, zda se daný soubor bude hledat ve složce
     * balíčku třídy volající metody (pak musí jméno předcházet *&#47;),
     * nebo ve složce kořenového balíčku (pak musí předcházet **&#47;);
     *
     * @param x    Vodorovná souřadnice levého horního rohu obrázku
     * @param y    Svislá souřadnice levého horního rohu obrázku
     * @param url  URL adresa obrázku
     */
    public Picture(int x, int y, URL url)
    {
        this(x, y, null, Toolkit.getDefaultToolkit().getImage(url));
    }


    /***************************************************************************
     * Vytvoří nový obrázek ze zadaného obrázku - instance třídy
     * {@code java.awt.Image} a umístí jej do počátku.
     *
     * @param x      Vodorovná souřadnice levého horního rohu obrázku
     * @param y      Svislá souřadnice levého horního rohu obrázku
     * @param image  Instance třídy {@code java.awt.Image},
     *               která bude základem obrázku.
     */
    public Picture(int x, int y, Image image)
    {
        this(x, y, null, image);
    }


    /***************************************************************************
     * Přečte ze zadaného souboru obrázek, který bude kreslit
     * na zadaných souřadnicích; pomocí úvodních hvězdiček
     * je možno zadat, zda se daný soubor bude hledat ve složce balíčku třídy
     * volající metody (pak musí jméno předcházet *&#47;),
     * nebo ve složce kořenového balíčku (pak musí předcházet **&#47;);
     *
     * @param x         Vodorovná souřadnice levého horního rohu obrázku
     * @param y         Svislá souřadnice levého horního rohu obrázku
     * @param fileName  Název souboru, v němž je obrázek uložen
     * @param image     Instance třídy {@code java.awt.Image},
     *                  která bude základem obrázku.
     */
    private Picture(int x, int y, String fileName, Image image)
    {
        //Test platnosti parametru
        if ((x<0) || (y<0)) {
            throw new IllegalArgumentException(
                "\nParametry nemají povolené hodnoty: x=" + x + ", y=" + y);
        }
        //Parametry akceptovány --> můžeme tvořit
        this.xPos  = x;
        this.yPos  = y;
        if (image != null) {
            this.image = image;
        } else {
            URL url;
            if (fileName.charAt(0) == '*') {
                if (fileName.charAt(1) == '*') {
                    String jméno = fileName.substring(3);
                    url = getClass().getClassLoader().getResource(jméno);
                } else {
                    Throwable t = new Throwable();
                    StackTraceElement ste = t.getStackTrace()[1];
                    String clsn = ste.getClassName();
                    Class<?> clss;
                    try{ clss = Class.forName(clsn);
                    } catch(ClassNotFoundException exc) {
                        throw new RuntimeException(
                            "\nNěco se podělalo - nenašel existující třídu " +
                            clsn, exc);
                    }
                    String fileNameString = fileName.substring(2);
                    url = clss.getResource(fileNameString);
                }
            }else {
                try {
                    url = new URL(fileName);
                } catch(MalformedURLException exc) {
                    throw new RuntimeException(
                            "\nNepodařilo se načíst obrázek v souboru " +
                            fileName, exc);
                }
            }
            this.image = Toolkit.getDefaultToolkit().getImage(url);
        }
        origWidth   = this.image.getWidth (Painter.IMG_OBSERVER);
        origHeight  = this.image.getHeight(Painter.IMG_OBSERVER);
        this.width  = origWidth;
        this.height = origHeight;
    }



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

    /***************************************************************************
     * 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()
    {
        directionSet = true;
        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)
    {
        directionSet = true;
        if (this.direction == direction) {
            return;
        }
        else {
            verifyRequest(direction);
            setDirSize(direction, getWidth(), getHeight());
            CM.repaint();
        }
    }


    /***************************************************************************
     * 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 jeho prvním použitím, tj. před tím,
     * než bude poprvé zavolána metoda {@link #setDirection(Direction8)},
     * resp. {@link #getDirection()}. Smí se zavolat pouze jednou.
     *
     * @param initialDirection Nastavovaný výchozí směr instance
     * @return Odkaz na instanci, aby bylo možno metodu zavolat
     *         jakou součást volání konstruktoru, což je doporučovaný postup
     * @throws IllegalStateException Pokud byl počáteční směr již nastaven
     *                               nebo použit
     */
    public synchronized Picture setInitialDirection(Direction8 initialDirection)
    {
        if (directionSet) {
            throw new IllegalStateException(
                      "\nPočáteční směr už byl nastaven nebo použit");
        }
        verifyRequest(initialDirection);
        this.direction = this.initialDirection = initialDirection;
        return this;
    }


    /***************************************************************************
     * 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)
    {
        if (aft != null) {
            aft = new AffineTransform(aft.getScaleX(), aft.getShearY(),
                                      aft.getShearX(), aft.getScaleY(), x, y);
        }
        xPos = x;
        yPos = y;
        CM.repaint();
    }


    /***************************************************************************
     * 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.
     * 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)
    {
        setDirSize(getDirection(), width, height);
        this.width = width;
        this.height = height;
        CM.repaint();
    }




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

    /***************************************************************************
     * Vrátí kopii daného tvaru,
     * tj. stejný obrázek, stejně velký a stejně umístěný.
     *
     * @return Požadovaná kopie
     */
    @Override
    public Picture copy()
    {
        Picture picture = new Picture(getX(), getY(), image);
        picture.setInitialDirection(initialDirection);
        picture.setSize(getWidth(), getHeight());
        picture.setDirection(direction);
        return picture;
    }


    /***************************************************************************
     * Prostřednictvím dodaného kreslítka vykreslí obraz své instance.
     *
     * @param painter Kreslítko schopné kreslit na plátno ovládané správcem
     */
    @Override
    public void paint(Painter painter)
    {
        if (aft == null) {
            painter.drawPicture(xPos, yPos, image, null);
        }
        else {
            painter.drawPicture(0, 0, image, aft);
        }
    }


    /***************************************************************************
     * 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 this.getClass().getSimpleName() + "#" + (100 + hashCode() % 900);
    }



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

    /***************************************************************************
     * Otočí instanci do zadaného směru
     * a přitom jí nastaví požadovanou výšku a šířku.
     *
     * @param direction Nově nastavovaný směr
     * @param width     Nově nastavovaná šířka; šířka &gt;= 0
     * @param height    Nově nastavovaná výška; výška &gt;= 0
     */
    public void setDirSize(Direction8 direction, int width, int height)
    {
        this.direction  = direction;
        int dirDistance = direction.ordinalDistanceTo(initialDirection);
        double xx, xy, xd, yx, yy, yd;
        switch(dirDistance)
        {
            case 0:
                if ((width == origWidth)  &&  (height == origHeight)) {
                    aft = null;
                    return;
                }
                xx = +((double)width / origWidth);
                xy = 0;
                xd = getX();
                yx = 0;
                yy = +((double)height / origHeight);
                yd = getY();
                break;

            case -6:    //O 90°doprava
            case +2:
                xx = 0;
                xy = -((double)height / origHeight);
                xd = getX() + height;
                yx = +((double)width / origWidth);
                yy = 0;
                yd = getY();
                break;

            case -4:    //0 180°
            case +4:
                xx = -((double)width / origWidth);
                xy = 0;
                xd = getX() + width;
                yx = 0;
                yy = -((double)height / origHeight);
                yd = getY() + height;
                break;

            case -2:    //O 90°doleva
            case +6:
                xx = 0;
                xy = +((double)height / origHeight);
                xd = getX();
                yx = -((double)width / origWidth);
                yy = 0;
                yd = getY() + width;
                break;

            default:
                throw new RuntimeException("\nNepovolené otočení");
        }
        this.aft       = new AffineTransform(xx, yx, xy, yy, xd, yd);
    }


    /***************************************************************************
     * Zkontroluje, že volající metoda otáčí obrázek do některého
     * z hlavních směrů. Obrázek nesmí být natáčen šikmo.
     *
     * @param direction Směr, do nějž je obrázek otáčen
     */
    private void verifyRequest(Direction8 direction)
    {
        if (! direction.isCardinal()) {
            throw new IllegalArgumentException(
                "\nObrázek lze natočit pouze do jednoho ze čtyř hlavních " +
                "směrů, požadováno: " + direction);
        }
//        if (getWidth() != getHeight()) {
//            throw new IllegalStateException(
//                "\nNastavovat směr je možno pouze pro čtvercový obrázek: "
//                + this);
//        }
    }



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