/* ******************************************************************************
 * Copyright (c) 2006-2012 XMind Ltd. and others.
 * 
 * This file is a part of XMind 3. XMind releases 3 and
 * above are dual-licensed under the Eclipse Public License (EPL),
 * which is available at http://www.eclipse.org/legal/epl-v10.html
 * and the GNU Lesser General Public License (LGPL), 
 * which is available at http://www.gnu.org/licenses/lgpl.html
 * See https://www.xmind.net/license.html for details.
 * 
 * Contributors:
 *     XMind Ltd. - initial API and implementation
 *******************************************************************************/
package org.xmind.gef.draw2d;

import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;

/**
 * @author Frank Shaka
 */
public class SizeableImageFigure extends ReferencedFigure implements IHasImage {

    private static final Rectangle IMAGE_CLIENT_AREA = new Rectangle();

    private static final int FLAG_STRETCHED = MAX_FLAG << 1;

    private static final int FLAG_CONSTRAINED = MAX_FLAG << 2;

    static {
        MAX_FLAG = FLAG_CONSTRAINED;
    }

    private Image img = null;

    private Dimension imgSize = null;

    private int alpha = -1;

    private Insets margins;

    /**
     * @param parent
     */
    public SizeableImageFigure() {
        this(null);
    }

    public SizeableImageFigure(Image image) {
        setImage(image);
    }

    /**
     * Sets the Image that this ImageFigure displays.
     * <p>
     * IMPORTANT: Note that it is the client's responsibility to dispose the
     * given image.
     * 
     * @param image
     *            The Image to be displayed. It can be <code>null</code>.
     */
    public void setImage(Image image) {
        if (img == image)
            return;
        img = image;
        imgSize = null;
        setMinimumSize(new Dimension(1, 1));
        revalidate();
        repaint();
    }

    /**
     * @return the initial size of the containing image or an empty size if no
     *         image is set
     */
    public Dimension getImageSize() {
        if (imgSize == null) {
            if (img != null) {
                imgSize = new Dimension(img);
            } else
                imgSize = new Dimension();
        }
        return imgSize;
    }

    public boolean isConstrained() {
        return getFlag(FLAG_CONSTRAINED);
    }

    public void setConstrained(boolean constrained) {
        if (constrained == isConstrained())
            return;
        setFlag(FLAG_CONSTRAINED, constrained);
        repaint();
    }

    public boolean isStretched() {
        return getFlag(FLAG_STRETCHED);
    }

    public void setStretched(boolean stretched) {
        if (stretched == isStretched())
            return;
        setFlag(FLAG_STRETCHED, stretched);
        repaint();
    }

    public void setMargins(Insets margins) {
        this.margins = margins;
    }

    /**
     * @return The Image that this Figure displays
     */
    public Image getImage() {
        return img;
    }

    /**
     * @return the alpha
     */
    public int getAlpha() {
        return alpha;
    }

    /**
     * @see IUseTransparency#DONT_USE_ALPHA
     * @param alpha
     *            the alpha to set
     */
    public void setAlpha(int alpha) {
        if (this.alpha == alpha)
            return;
        this.alpha = alpha;
        repaint();
    }

    @Override
    public Dimension getPreferredSize(int wHint, int hHint) {
        if (prefSize != null)
            return prefSize;
        if (getLayoutManager() != null) {
            Dimension d = getLayoutManager().getPreferredSize(this, wHint,
                    hHint);
            if (d != null)
                return d;
        }
        return calculatePreferredImageSize(wHint, hHint);
    }

    protected Dimension calculatePreferredImageSize(int wHint, int hHint) {
        Dimension size = getImageSize();
        if (wHint < 0 && hHint < 0)
            return size;
        boolean constrained = isConstrained();
        boolean stretched = isStretched();
        if (constrained || stretched) {
            if (size.width == 0 || size.height == 0)
                return new Dimension(Math.max(wHint, 0), Math.max(hHint, 0));
            if (wHint == 0 || hHint == 0)
                return new Dimension(0, 0);
            if (wHint > 0 && hHint > 0) {
                if (constrained && (stretched || size.width > wHint
                        || size.height > hHint)) {
                    int a = size.width * hHint;
                    int b = size.height * wHint;
                    if (stretched ? (a < b) : (a > b)) {
                        int h = wHint == 0 ? 0 : b / size.width;
                        return new Dimension(wHint, h);
                    } else if (stretched ? (a > b) : (a < b)) {
                        int w = hHint == 0 ? 0 : a / size.height;
                        return new Dimension(w, hHint);
                    } else {
                        return new Dimension(wHint, hHint);
                    }
                } else {
                    return new Dimension(size.width, size.height);
                }
            } else if (wHint > 0) {
                return new Dimension(wHint, wHint * size.height / size.width);
            } else { // if (hHint > 0) {
                return new Dimension(hHint * size.width / size.height, hHint);
            }
        }
        return size;
    }

    /**
     * @see org.eclipse.draw2d.Figure#paintFigure(Graphics)
     */
    protected void paintFigure(final Graphics graphics) {
        super.paintFigure(graphics);

        Image image = getImage();
        if (image != null) {
            graphics.setAntialias(SWT.ON);
            if (getAlpha() != IUseTransparency.DONT_USE_ALPHA)
                graphics.setAlpha(getAlpha());
            paintImage(graphics, image);
        }
    }

    protected void paintImage(Graphics graphics, Image image) {
        if (image != null) {
            Dimension imageSize;
            if (image == this.img)
                imageSize = getImageSize();
            else {
                imageSize = new Dimension(image);
            }
            paintImage(graphics, image, imageSize,
                    getImageClientArea(imageSize));
        }
    }

    protected Rectangle getImageClientArea(Dimension imageSize) {
        Rectangle area = getClientArea(IMAGE_CLIENT_AREA);
        boolean constrained = isConstrained();
        boolean stretched = isStretched();
        if (constrained && (stretched || imageSize.width > area.width
                || imageSize.height > area.height)) {
            adaptAreaToRatio(area, imageSize, stretched);
        } else if (!stretched) {
            adaptAreaToSize(area, imageSize);
        }
        if (margins != null) {
            return new Rectangle(area.x + margins.left, area.y + margins.top,
                    Math.max(0, area.width - margins.getWidth()),
                    Math.max(0, area.height - margins.getHeight()));
        } else {
            return area;
        }
    }

    protected void adaptAreaToSize(Rectangle area, Dimension size) {
        area.x += (area.width - size.width) / 2;
        area.width = size.width;
        area.y += (area.height - size.height) / 2;
        area.height = size.height;
    }

    protected void adaptAreaToRatio(Rectangle area, Dimension ratio,
            boolean bigger) {
        int a = ratio.width * area.height;
        int b = ratio.height * area.width;
        if (bigger ? (a < b) : (a > b)) {
            int h = area.width == 0 ? 0 : b / ratio.width;
            area.y += (area.height - h) / 2;
            area.height = h;
        } else if (bigger ? (a > b) : (a < b)) {
            int w = area.height == 0 ? 0 : a / ratio.height;
            area.x += (area.width - w) / 2;
            area.width = w;
        }
    }

    protected void paintImage(Graphics graphics, Image image,
            Dimension imageSize, Rectangle clientArea) {
        //TODO FIXME
        try {
            if (clientArea.width == imageSize.width
                    && clientArea.height == imageSize.height) {
                graphics.drawImage(image, clientArea.x, clientArea.y);
            } else {
                graphics.drawImage(image, 0, 0, imageSize.width,
                        imageSize.height, clientArea.x, clientArea.y,
                        clientArea.width, clientArea.height);
            }
        } catch (Exception e) {
            //TODO FIXME
        }
    }

}
