/*
 * @(#)ImageFigure.java 5.1
 *
 */

package CH.ifa.draw.figures;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.net.URI;
import java.util.Vector;

import CH.ifa.draw.standard.BoxHandleKit;
import CH.ifa.draw.util.GUIProperties;
import CH.ifa.draw.util.Iconkit;
import de.renew.draw.storables.api.StorableApi;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;
import de.renew.draw.ui.ontology.FigureHandle;
import de.renew.util.StringUtil;

/**
 * A Figure that shows an Image.
 * Images shown by an image figure are shared by using the Iconkit.
 * <p>
 * {@link Iconkit}
 */
public class ImageFigure extends AttributeFigure implements ImageObserver {
    /** Logger used for the class */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ImageFigure.class);

    /*
     * Serialization support.
     */
    @Serial
    private static final long serialVersionUID = 148012030121282439L;

    /**
     * The name of the file which contains the image.
     * <p>
     * On serialization, the file path will be stored
     * completely, but when written as <code>Storable</code>,
     * it will be stored as a relative path.
     * </p>
     *
     * @serial
     **/
    private String _fileName;

    /**
     * The image to be shown.
     * <p>
     * This field is transient because the field
     * <code>fFileName</code> should be sufficient
     * to regain this information.
     * </p>
     **/
    private transient Image _image;

    /**
     * Determines position and size of the image by
     * specifying position and size of its bounding box.
     *
     * @serial
     **/
    private Rectangle _displayBox;

    /**
     * unused serial version number
     */
    @SuppressWarnings("unused")
    private final int _imageFigureSerializedDataVersion = 1;

    /**
     * Public constructor of the class. Creates a new ImageFigure.
     */
    public ImageFigure() {
        _fileName = null;
        _image = null;
        _displayBox = null;
    }

    /**
     * Constructor of the class. Creates a new ImageFigure with the given
     * image, filename and a point of origin.
     *
     * @param image The image object that will be displayed
     * @param fileName The filename of the File of the supplied image
     * @param origin Point of origin of the image
     */
    public ImageFigure(Image image, String fileName, Point origin) {
        if (fileName != null) {
            try {
                _fileName = new File(fileName).getCanonicalPath();
            } catch (IOException e) {
                LOGGER.error("Could not find file " + fileName + ": " + e);
            }
        } else {
            _fileName = null;
        }
        _image = image;
        _displayBox = new Rectangle(origin.x, origin.y, 0, 0);
        _displayBox.width = _image.getWidth(this);
        _displayBox.height = _image.getHeight(this);
    }

    @Override
    public void basicDisplayBox(Point origin, Point corner) {
        _displayBox = new Rectangle(origin);
        _displayBox.add(corner);
    }

    @Override
    public Vector<FigureHandle> handles() {
        Vector<FigureHandle> handles = new Vector<>();
        BoxHandleKit.addHandles(this, handles);
        return handles;
    }

    @Override
    public Rectangle displayBox() {
        return new Rectangle(_displayBox.x, _displayBox.y, _displayBox.width, _displayBox.height);
    }

    @Override
    protected void basicMoveBy(int x, int y) {
        _displayBox.translate(x, y);
    }

    @Override
    public void internalDraw(Graphics g) {
        if (_image == null && _fileName != null) {
            _image = Iconkit.instance().getImage(_fileName);
        }
        if (_image != null && new File(_fileName).exists()) {
            g.drawImage(
                _image, _displayBox.x, _displayBox.y, _displayBox.width, _displayBox.height, this);
        } else {
            drawGhost(g);
        }
    }

    private void drawGhost(Graphics g) {
        g.setColor(Color.gray);
        g.fillRect(_displayBox.x, _displayBox.y, _displayBox.width, _displayBox.height);
    }

    /**
     * Handles asynchronous image updates.
     */
    @Override
    public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
        if ((flags & (FRAMEBITS | ALLBITS)) != 0) {
            invalidate();
            if (listener() != null) {
                listener().figureRequestUpdate(StorableApi.createFigureChangeEvent(this));
            }
        }
        return (flags & (ALLBITS | ABORT)) == 0;
    }

    /**
     * Writes the ImageFigure to a StorableOutput. Only a reference to the
     * image, that is its pathname is saved.
     */
    @Override
    public void write(StorableOutput dw) {
        super.write(dw);
        dw.writeInt(_displayBox.x);
        dw.writeInt(_displayBox.y);
        dw.writeInt(_displayBox.width);
        dw.writeInt(_displayBox.height);
        URI imageURI = new File(_fileName).toURI();
        imageURI = StringUtil.makeRelative(dw.getURI(), imageURI);
        String relativePath = imageURI.getPath();
        dw.writeString(relativePath);
        LOGGER.debug(relativePath);
    }

    /**
     * Reads the ImageFigure from a StorableInput. It registers the
     * referenced figure to be loaded from the Iconkit.
     * <p>
     * {@link Iconkit#registerImage}
     */
    @Override
    public void read(StorableInput dr) throws IOException {
        super.read(dr);
        _displayBox = new Rectangle(dr.readInt(), dr.readInt(), dr.readInt(), dr.readInt());
        _fileName = dr.readString();
        URI uri = null;
        try {
            uri = dr.getURI().resolve(_fileName);
            _fileName = new File(uri).getCanonicalPath();
            LOGGER.debug("Including image from location: " + _fileName);
        } catch (IllegalArgumentException e) {
            LOGGER.error("Problem while resolving image location: " + e);
        } catch (IOException e) {
            LOGGER.error("Problem while resolving image location: " + e);
            _fileName = uri.getPath();
        } catch (NullPointerException e) {
            LOGGER.debug("StorableInput has no URI!");
        }

        if (!GUIProperties.noGraphics()) {
            Iconkit.instance().registerImage(_fileName);
        }
    }

    /**
     * Standard method for deserialization of an object.
     *
     * @param s input stream to read object data
     * @throws ClassNotFoundException when no class can be found
     * @throws IOException when an I/O Exception occurs
     */
    @Serial
    private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
        s.defaultReadObject();
        Iconkit.instance().registerImage(_fileName);
        _image = null;
    }
}