package io.github.andrewauclair.moderndocking.internal.util;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.Icon;

/**
 *  The RotatedIcon allows you to change the orientation of an Icon by
 *  rotating the Icon before it is painted. This class supports the following
 *  orientations:
 *
 * <ul>
 * <li>DOWN - rotated 90 degrees
 * <li>UP (default) - rotated -90 degrees
 * <li>UPSIDE_DOWN - rotated 180 degrees
 * <li>ABOUT_CENTER - the icon is rotated by the specified degrees about its center.
 * </ul>
 */
public class RotatedIcon implements Icon
{
	/**
	 * Rotation state
	 */
	public enum Rotate
	{
		/**
		 * Rotation down
		 */
		DOWN,
		/**
		 * Rotation up
		 */
		UP,
		/**
		 * Rotation upside down
		 */
		UPSIDE_DOWN,
		/**
		 * Rotation center
		 */
		ABOUT_CENTER
	}

	private final Icon icon;

	private final Rotate rotate;

	private double degrees;
	private boolean circularIcon;

	/**
	 *  Convenience constructor to create a RotatedIcon that is rotated DOWN.
	 *
	 *  @param icon  the Icon to rotate
	 */
	public RotatedIcon(Icon icon)
	{
		this(icon, Rotate.UP);
	}

	/**
	 *  Create a RotatedIcon
	 *
	 *  @param icon	the Icon to rotate
	 *  @param rotate  the direction of rotation
	 */
	public RotatedIcon(Icon icon, Rotate rotate)
	{
		this.icon = icon;
		this.rotate = rotate;
	}

	/**
	 *  Create a RotatedIcon. The icon will rotate about its center. This
	 *  constructor will automatically set Rotate enum to ABOUT_CENTER.
	 *
	 *  @param icon	the Icon to rotate
	 *  @param degrees   the degrees of rotation
	 */
	public RotatedIcon(Icon icon, double degrees)
	{
		this(icon, degrees, false);
	}

	/**
	 *  Create a RotatedIcon. The icon will rotate about its center. This
	 *  constructor will automatically set Rotate enum to ABOUT_CENTER.
	 *
	 *  @param icon	the Icon to rotate
	 *  @param degrees   the degrees of rotation
	 *  @param circularIcon treat the icon as circular so its size doesn't change
	 */
	public RotatedIcon(Icon icon, double degrees, boolean circularIcon)
	{
		this(icon, Rotate.ABOUT_CENTER);
		setDegrees( degrees );
		setCircularIcon( circularIcon );
	}

	/**
	 *  Gets the Icon to be rotated
	 *
	 *  @return the Icon to be rotated
	 */
	public Icon getIcon()
	{
		return icon;
	}

	/**
	 *  Gets Rotate enum which indicates the direction of rotation
	 *
	 *  @return Rotate enum
	 */
	public Rotate getRotate()
	{
		return rotate;
	}

	/**
	 *  Gets the degrees of rotation. Only used for Rotate.ABOUT_CENTER.
	 *
	 *  @return the degrees of rotation
	 */
	public double getDegrees()
	{
		return degrees;
	}

	/**
	 *  Set the degrees of rotation. Only used for Rotate.ABOUT_CENTER.
	 *  This method only sets the degress of rotation, it will not cause
	 *  the Icon to be repainted. You must invoke repaint() on any
	 *  component using this icon for it to be repainted.
	 *
	 *  @param degrees the degrees of rotation
	 */
	public void setDegrees(double degrees)
	{
		this.degrees = degrees;
	}

	/**
	 *  Is the image circular or rectangular? Only used for Rotate.ABOUT_CENTER.
	 *  When true, the icon width/height will not change as the Icon is rotated.
	 *
	 *  @return true for a circular Icon, false otherwise
	 */
	public boolean isCircularIcon()
	{
		return circularIcon;
	}

	/**
	 *  Set the Icon as circular or rectangular. Only used for Rotate.ABOUT_CENTER.
	 *  When true, the icon width/height will not change as the Icon is rotated.
	 *
	 *  @param circularIcon true for a circular Icon, false otherwise
	 */
	public void setCircularIcon(boolean circularIcon)
	{
		this.circularIcon = circularIcon;
	}

//
//  Implement the Icon Interface
//

	/**
	 *  Gets the width of this icon.
	 *
	 *  @return the width of the icon in pixels.
	 */
	@Override
	public int getIconWidth()
	{
		if (rotate == Rotate.ABOUT_CENTER)
		{
			if (circularIcon)
				return icon.getIconWidth();
			else
			{
				double radians = Math.toRadians( degrees );
				double sin = Math.abs( Math.sin( radians ) );
				double cos = Math.abs( Math.cos( radians ) );

				return (int)Math.floor(icon.getIconWidth() * cos + icon.getIconHeight() * sin);
			}
		}
		else if (rotate == Rotate.UPSIDE_DOWN)
			return icon.getIconWidth();
		else
			return icon.getIconHeight();
	}

	/**
	 *  Gets the height of this icon.
	 *
	 *  @return the height of the icon in pixels.
	 */
	@Override
	public int getIconHeight()
	{
		if (rotate == Rotate.ABOUT_CENTER)
		{
			if (circularIcon)
				return icon.getIconHeight();
			else
			{
				double radians = Math.toRadians( degrees );
				double sin = Math.abs( Math.sin( radians ) );
				double cos = Math.abs( Math.cos( radians ) );

				return (int)Math.floor(icon.getIconHeight() * cos + icon.getIconWidth() * sin);
			}
		}
		else if (rotate == Rotate.UPSIDE_DOWN)
			return icon.getIconHeight();
		else
			return icon.getIconWidth();
	}

	/**
	 *  Paint the icons of this compound icon at the specified location
	 *
	 *  @param c The component on which the icon is painted
	 *  @param g the graphics context
	 *  @param x the X coordinate of the icon's top-left corner
	 *  @param y the Y coordinate of the icon's top-left corner
	 */
	@Override
	public void paintIcon(Component c, Graphics g, int x, int y)
	{
		Graphics2D g2 = (Graphics2D)g.create();

		int cWidth = icon.getIconWidth() / 2;
		int cHeight = icon.getIconHeight() / 2;
		int xAdjustment = (icon.getIconWidth() % 2) == 0 ? 0 : -1;
		int yAdjustment = (icon.getIconHeight() % 2) == 0 ? 0 : -1;

		if (rotate == Rotate.DOWN)
		{
			g2.translate(x + cHeight, y + cWidth);
			g2.rotate( Math.toRadians( 90 ) );
			icon.paintIcon(c, g2,  -cWidth, yAdjustment - cHeight);
		}
		else if (rotate == Rotate.UP)
		{
			g2.translate(x + cHeight, y + cWidth);
			g2.rotate( Math.toRadians( -90 ) );
			icon.paintIcon(c, g2,  xAdjustment - cWidth, -cHeight);
		}
		else if (rotate == Rotate.UPSIDE_DOWN)
		{
			g2.translate(x + cWidth, y + cHeight);
			g2.rotate( Math.toRadians( 180 ) );
			icon.paintIcon(c, g2, xAdjustment - cWidth, yAdjustment - cHeight);
		}
		else if (rotate == Rotate.ABOUT_CENTER)
		{
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setClip(x, y, getIconWidth(), getIconHeight());
			g2.translate((getIconWidth() - icon.getIconWidth()) / 2, (getIconHeight() - icon.getIconHeight()) / 2);
			g2.rotate(Math.toRadians(degrees), x + cWidth, y + cHeight);
			icon.paintIcon(c, g2, x, y);
		}

		g2.dispose();
	}
}