// Copyright 2012 Jeeeyul Lee, Seoul, Korea
// https://github.com/jeeeyul/pde-tools
//
// This module is multi-licensed and may be used under the terms
// of any of the following licenses:
//
// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
// GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
// AL, Apache License, V2.0 or later, http://www.apache.org/licenses
// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
// MIT, MIT License, http://www.opensource.org/licenses/MIT
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.
package net.jeeeyul.eclipse.themes.rendering;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.inject.Inject;

import net.jeeeyul.eclipse.themes.SharedImages;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolderRenderer;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Widget;

public class HackedCTabRendering extends CTabFolderRenderer {
	public static Field HACK_CTabItem_closeRect;
	public static Field HACK_CTabItem_shortenText;
	public static Field HACK_CTabItem_shortenTextWidth;
	public static Field HACK_CTabItem_closeImageState;
	public static Field HACK_CTabFolder_curveWidth;
	public static Field HACK_CTabFolder_curveIndent;

	public static Method HACK_CTabFolder_getRightItemEdge;
	public static Method HACK_CTabFolder_updateItems;

	static {
		try {
			HACK_CTabItem_closeRect = CTabItem.class
					.getDeclaredField("closeRect");
			HACK_CTabItem_closeRect.setAccessible(true);

			HACK_CTabItem_shortenText = CTabItem.class
					.getDeclaredField("shortenedText");
			HACK_CTabItem_shortenText.setAccessible(true);

			HACK_CTabItem_shortenTextWidth = CTabItem.class
					.getDeclaredField("shortenedTextWidth");
			HACK_CTabItem_shortenTextWidth.setAccessible(true);

			HACK_CTabItem_closeImageState = CTabItem.class
					.getDeclaredField("closeImageState");
			HACK_CTabItem_closeImageState.setAccessible(true);

			HACK_CTabFolder_curveWidth = CTabFolderRenderer.class
					.getDeclaredField("curveWidth");
			HACK_CTabFolder_curveWidth.setAccessible(true);

			HACK_CTabFolder_curveIndent = CTabFolderRenderer.class
					.getDeclaredField("curveIndent");
			HACK_CTabFolder_curveIndent.setAccessible(true);

			HACK_CTabFolder_getRightItemEdge = CTabFolder.class
					.getDeclaredMethod("getRightItemEdge", GC.class);
			HACK_CTabFolder_getRightItemEdge.setAccessible(true);

			HACK_CTabFolder_updateItems = CTabFolder.class
					.getDeclaredMethod("updateItems");
			HACK_CTabFolder_updateItems.setAccessible(true);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// Constants for circle drawing
	final static int LEFT_TOP = 0;

	final static int LEFT_BOTTOM = 1;

	final static int RIGHT_TOP = 2;

	final static int RIGHT_BOTTOM = 3;

	// drop shadow constants
	final static int SIDE_DROP_WIDTH = 3;

	final static int BOTTOM_DROP_WIDTH = 4;

	// keylines
	final static int OUTER_KEYLINE = 1;

	final static int INNER_KEYLINE = 0;

	final static int TOP_KEYLINE = 0;

	// Item Constants
	static final int ITEM_TOP_MARGIN = 2;

	static final int ITEM_BOTTOM_MARGIN = 6;
	static final int ITEM_LEFT_MARGIN = 4;
	static final int ITEM_RIGHT_MARGIN = 4;
	static final int INTERNAL_SPACING = 4;

	static final String E4_SHADOW_IMAGE = "org.eclipse.e4.renderer.shadow_image"; //$NON-NLS-1$
	static final String E4_TOOLBAR_ACTIVE_IMAGE = "org.eclipse.e4.renderer.toolbar_background_active_image"; //$NON-NLS-1$

	static final String E4_TOOLBAR_INACTIVE_IMAGE = "org.eclipse.e4.renderer.toolbar_background_inactive_image"; //$NON-NLS-1$

	static int blend(int v1, int v2, int ratio) {
		int b = (ratio * v1 + (100 - ratio) * v2) / 100;
		return Math.min(255, b);
	}

	static RGB blend(RGB c1, RGB c2, int ratio) {
		int r = blend(c1.red, c2.red, ratio);
		int g = blend(c1.green, c2.green, ratio);
		int b = blend(c1.blue, c2.blue, ratio);
		return new RGB(r, g, b);
	}

	static int[] drawCircle(int xC, int yC, int r, int circlePart) {
		int x = 0, y = r, u = 1, v = 2 * r - 1, e = 0;
		int[] points = new int[1024];
		int[] pointsMirror = new int[1024];
		int loop = 0;
		int loopMirror = 0;
		while (x < y) {
			if (circlePart == RIGHT_BOTTOM) {
				points[loop++] = xC + x;
				points[loop++] = yC + y;
			}
			if (circlePart == RIGHT_TOP) {
				points[loop++] = xC + y;
				points[loop++] = yC - x;
			}
			if (circlePart == LEFT_TOP) {
				points[loop++] = xC - x;
				points[loop++] = yC - y;
			}
			if (circlePart == LEFT_BOTTOM) {
				points[loop++] = xC - y;
				points[loop++] = yC + x;
			}
			x++;
			e += u;
			u += 2;
			if (v < 2 * e) {
				y--;
				e -= v;
				v -= 2;
			}
			if (x > y)
				break;
			if (circlePart == RIGHT_BOTTOM) {
				pointsMirror[loopMirror++] = xC + y;
				pointsMirror[loopMirror++] = yC + x;
			}
			if (circlePart == RIGHT_TOP) {
				pointsMirror[loopMirror++] = xC + x;
				pointsMirror[loopMirror++] = yC - y;
			}
			if (circlePart == LEFT_TOP) {
				pointsMirror[loopMirror++] = xC - y;
				pointsMirror[loopMirror++] = yC - x;
			}
			if (circlePart == LEFT_BOTTOM) {
				pointsMirror[loopMirror++] = xC - x;
				pointsMirror[loopMirror++] = yC + y;
			}
			// grow?
			if ((loop + 1) > points.length) {
				int length = points.length * 2;
				int[] newPointTable = new int[length];
				int[] newPointTableMirror = new int[length];
				System.arraycopy(points, 0, newPointTable, 0, points.length);
				points = newPointTable;
				System.arraycopy(pointsMirror, 0, newPointTableMirror, 0,
						pointsMirror.length);
				pointsMirror = newPointTableMirror;
			}
		}
		int[] finalArray = new int[loop + loopMirror];
		System.arraycopy(points, 0, finalArray, 0, loop);
		for (int i = loopMirror - 1, j = loop; i > 0; i = i - 2, j = j + 2) {
			int tempY = pointsMirror[i];
			int tempX = pointsMirror[i - 1];
			finalArray[j] = tempX;
			finalArray[j + 1] = tempY;
		}
		return finalArray;
	}

	int[] shape;
	Image shadowImage, toolbarActiveImage, toolbarInactiveImage;
	int cornerSize = 14;
	boolean shadowEnabled = true;

	Color shadowColor;
	Color outerKeyline, innerKeyline;
	Color[] activeToolbar;

	int[] activePercents;

	Color[] inactiveToolbar;

	int[] inactivePercents;

	boolean active;
	Color selectedTabFillColor;
	Color tabOutlineColor;
	int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;

	protected Color selectedTabItemColor;
	protected Color unselectedTabItemColor;
	protected Color selectedTabFillHighlightColor;

	@Inject
	public HackedCTabRendering(CTabFolder parent) {
		super(parent);
	}

	void _drawClose(GC gc, Rectangle closeRect, int closeImageState) {
		if (closeRect.width == 0 || closeRect.height == 0)
			return;

		// draw X 9x9
		int x = closeRect.x + Math.max(1, (closeRect.width - 9) / 2);
		int y = closeRect.y + Math.max(1, (closeRect.height - 9) / 2);
		y += parent.getTabPosition() == SWT.BOTTOM ? -1 : 1;

		switch (closeImageState & (SWT.HOT | SWT.SELECTED | SWT.BACKGROUND)) {
		case SWT.NONE: {
			gc.drawImage(SharedImages.getImage(SharedImages.CLOSE_NORMAL), x, y);
			break;
		}
		case SWT.HOT: {
			gc.drawImage(SharedImages.getImage(SharedImages.CLOSE_ACTIVE), x, y);
			break;
		}
		case SWT.SELECTED: {
			gc.drawImage(SharedImages.getImage(SharedImages.CLOSE_ACTIVE),
					x + 1, y + 1);
			break;
		}
		case SWT.BACKGROUND: {
			gc.drawImage(SharedImages.getImage(SharedImages.CLOSE_NORMAL), x, y);
			break;
		}
		}
	}

	String _shortenText(GC gc, String text, int width) {
		return parent.getSimple() ? _shortenText(gc, text, width, "...") : _shortenText(gc, text, width, ""); //$NON-NLS-1$
	}

	String _shortenText(GC gc, String text, int width, String ellipses) {
		if (gc.textExtent(text, SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC).x <= width)
			return text;
		int ellipseWidth = gc.textExtent(ellipses, SWT.DRAW_TRANSPARENT
				| SWT.DRAW_MNEMONIC).x;
		int length = text.length();
		TextLayout layout = new TextLayout(parent.getDisplay());
		layout.setText(text);
		int end = layout.getPreviousOffset(length, SWT.MOVEMENT_CLUSTER);
		while (end > 0) {
			text = text.substring(0, end);
			int l = gc.textExtent(text, SWT.DRAW_TRANSPARENT
					| SWT.DRAW_MNEMONIC).x;
			if (l + ellipseWidth <= width) {
				break;
			}
			end = layout.getPreviousOffset(end, SWT.MOVEMENT_CLUSTER);
		}
		layout.dispose();
		return end == 0 ? text.substring(0, 1) : text + ellipses;
	}

	public ImageData blur(Image src, int radius, int sigma) {
		float[] kernel = create1DKernel(radius, sigma);

		ImageData imgPixels = src.getImageData();
		int width = imgPixels.width;
		int height = imgPixels.height;

		int[] inPixels = new int[width * height];
		int[] outPixels = new int[width * height];
		int offset = 0;
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				RGB rgb = imgPixels.palette.getRGB(imgPixels.getPixel(x, y));
				if (rgb.red == 255 && rgb.green == 255 && rgb.blue == 255) {
					inPixels[offset] = (rgb.red << 16) | (rgb.green << 8)
							| rgb.blue;
				} else {
					inPixels[offset] = (imgPixels.getAlpha(x, y) << 24)
							| (rgb.red << 16) | (rgb.green << 8) | rgb.blue;
				}
				offset++;
			}
		}

		convolve(kernel, inPixels, outPixels, width, height, true);
		convolve(kernel, outPixels, inPixels, height, width, true);

		ImageData dst = new ImageData(imgPixels.width, imgPixels.height, 24,
				new PaletteData(0xff0000, 0xff00, 0xff));

		dst.setPixels(0, 0, inPixels.length, inPixels, 0);
		offset = 0;
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				if (inPixels[offset] == -1) {
					dst.setAlpha(x, y, 0);
				} else {
					int a = (inPixels[offset] >> 24) & 0xff;
					// if (a < 150) a = 0;
					dst.setAlpha(x, y, a);
				}
				offset++;
			}
		}
		return dst;
	}

	private int clamp(int value) {
		if (value > 255)
			return 255;
		if (value < 0)
			return 0;
		return value;
	}

	protected Point computeSize(int part, int state, GC gc, int wHint, int hHint) {
		wHint += paddingLeft + paddingRight;
		hHint += paddingTop + paddingBottom;
		if (0 <= part && part < parent.getItemCount()) {
			gc.setAdvanced(true);
			Point result = super.computeSize(part, state, gc, wHint, hHint);
			gc.setAdvanced(false);
			return result;
		}
		return super.computeSize(part, state, gc, wHint, hHint);
	}

	protected Rectangle computeTrim(int part, int state, int x, int y,
			int width, int height) {
		GC gc = new GC(parent);
		gc.dispose();
		int borderTop = TOP_KEYLINE + OUTER_KEYLINE;
		int borderBottom = INNER_KEYLINE + OUTER_KEYLINE;
		int marginHeight = parent.marginHeight;
		int sideDropWidth = shadowEnabled ? SIDE_DROP_WIDTH : 0;
		switch (part) {
		case PART_BODY:
			x = -1 - paddingLeft;
			int tabHeight = parent.getTabHeight() + 1;
			y = y - paddingTop - marginHeight - tabHeight - borderTop
					- (cornerSize / 4);
			width = 2 + paddingLeft + paddingRight;
			height += paddingTop + paddingBottom;
			height += tabHeight + (cornerSize / 4) + borderBottom + borderTop;
			break;
		case PART_HEADER:
			x = x - (INNER_KEYLINE + OUTER_KEYLINE) - sideDropWidth;
			width = width + 2 * (INNER_KEYLINE + OUTER_KEYLINE + sideDropWidth);
			break;
		case PART_BORDER:
			x = x - INNER_KEYLINE - OUTER_KEYLINE - sideDropWidth
					- (cornerSize / 4);
			width = width + 2 * (INNER_KEYLINE + OUTER_KEYLINE + sideDropWidth)
					+ cornerSize / 2;
			y = y - borderTop;
			height = height + borderTop + borderBottom;
			break;
		default:
			if (0 <= part && part < parent.getItemCount()) {
				x = x - ITEM_LEFT_MARGIN;// - (CORNER_SIZE/2);
				width = width + ITEM_LEFT_MARGIN + ITEM_RIGHT_MARGIN + 1;
				y = y - ITEM_TOP_MARGIN;
				height = height + ITEM_TOP_MARGIN + ITEM_BOTTOM_MARGIN;
			}
			break;
		}
		return new Rectangle(x, y, width, height);
	}

	private void convolve(float[] kernel, int[] inPixels, int[] outPixels,
			int width, int height, boolean alpha) {
		int kernelWidth = kernel.length;
		int kernelMid = kernelWidth / 2;
		for (int y = 0; y < height; y++) {
			int index = y;
			int currentLine = y * width;
			for (int x = 0; x < width; x++) {
				// do point
				float a = 0, r = 0, g = 0, b = 0;
				for (int k = -kernelMid; k <= kernelMid; k++) {
					float val = kernel[k + kernelMid];
					int xcoord = x + k;
					if (xcoord < 0)
						xcoord = 0;
					if (xcoord >= width)
						xcoord = width - 1;
					int pixel = inPixels[currentLine + xcoord];
					// float alp = ((pixel >> 24) & 0xff);
					a += val * ((pixel >> 24) & 0xff);
					r += val * (((pixel >> 16) & 0xff));
					g += val * (((pixel >> 8) & 0xff));
					b += val * (((pixel) & 0xff));
				}
				int ia = alpha ? clamp((int) (a + 0.5)) : 0xff;
				int ir = clamp((int) (r + 0.5));
				int ig = clamp((int) (g + 0.5));
				int ib = clamp((int) (b + 0.5));
				outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
				index += height;
			}
		}

	}

	private float[] create1DKernel(int radius, int sigma) {
		// guideline: 3*sigma should be the radius
		int size = radius * 2 + 1;
		float[] kernel = new float[size];
		int radiusSquare = radius * radius;
		float sigmaSquare = 2 * sigma * sigma;
		float piSigma = 2 * (float) Math.PI * sigma;
		float sqrtSigmaPi2 = (float) Math.sqrt(piSigma);
		int start = size / 2;
		int index = 0;
		float total = 0;
		for (int i = -start; i <= start; i++) {
			float d = i * i;
			if (d > radiusSquare) {
				kernel[index] = 0;
			} else {
				kernel[index] = (float) Math.exp(-(d) / sigmaSquare)
						/ sqrtSigmaPi2;
			}
			total += kernel[index];
			index++;
		}
		for (int i = 0; i < size; i++) {
			kernel[i] /= total;
		}
		return kernel;
	}

	void createShadow(final Display display, boolean recreate) {
		Object obj = display.getData(E4_SHADOW_IMAGE);
		if (obj != null && !recreate) {
			shadowImage = (Image) obj;
		} else {
			ImageData data = new ImageData(60, 60, 32, new PaletteData(
					0xFF0000, 0xFF00, 0xFF));
			Image tmpImage = shadowImage = new Image(display, data);
			GC gc = new GC(tmpImage);
			if (shadowColor == null)
				shadowColor = gc.getDevice().getSystemColor(SWT.COLOR_GRAY);
			gc.setBackground(shadowColor);
			drawTabBody(gc, new Rectangle(0, 0, 60, 60), SWT.None);
			ImageData blured = blur(tmpImage, 5, 25);
			shadowImage = new Image(display, blured);
			display.setData(E4_SHADOW_IMAGE, shadowImage);
			tmpImage.dispose();
			display.disposeExec(new Runnable() {
				public void run() {
					Object obj = display.getData(E4_SHADOW_IMAGE);
					if (obj != null) {
						Image tmp = (Image) obj;
						tmp.dispose();
						display.setData(E4_SHADOW_IMAGE, null);
					}
				}
			});
		}
	}

	protected void dispose() {
		super.dispose();
	}

	protected void draw(int part, int state, Rectangle bounds, GC gc) {
		switch (part) {

		case PART_BODY:
			this.drawTabBody(gc, bounds, state);
			return;
		case PART_HEADER:
			this.drawTabHeader(gc, bounds, state);
			return;
		default:
			if (0 <= part && part < parent.getItemCount()) {
				if (bounds.width == 0 || bounds.height == 0)
					return;
				gc.setAdvanced(true);

				if ((state & SWT.SELECTED) != 0) {
					drawSelectedTabItemBackground(part, gc, bounds, state);
					state &= ~SWT.BACKGROUND;
					drawSelectedTabItem(part, gc, bounds, state);
				} else {
					drawUnselectedTabItemBackground(part, gc, bounds, state);

					if ((state & SWT.HOT) == 0 && !active) {
						state &= ~SWT.BACKGROUND;
						drawUnselectedTabItem(part, gc, bounds, state);
					} else {
						state &= ~SWT.BACKGROUND;
						drawUnselectedTabItem(part, gc, bounds, state);
					}
				}

				gc.setAdvanced(false);
				return;
			}
		}
		super.draw(part, state, bounds, gc);
	}

	protected void drawSelectedTabItem(int itemIndex, GC gc, Rectangle bounds,
			int state) {
		CTabItem item = parent.getItem(itemIndex);
		int x = bounds.x;
		int y = bounds.y;
		int height = bounds.height;
		int width = bounds.width;
		if (!parent.getSimple() && !parent.getSingle())
			width -= (getCurveWidth() - getCurveIndent());

		int rightEdge = Math.min(x + width, getRightItemEdge(parent, gc));
		// Draw selection border across all tabs

		// draw Image
		Rectangle trim = computeTrim(itemIndex, SWT.NONE, 0, 0, 0, 0);
		int xDraw = x - trim.x;
		if (parent.getSingle()
				&& (hasStyle(parent, SWT.CLOSE) || hasStyle(item, SWT.CLOSE)))
			xDraw += getCloseRect(item).width;
		Image image = item.getImage();
		if (image != null && !image.isDisposed()) {
			Rectangle imageBounds = image.getBounds();
			// only draw image if it won't overlap with close button
			int maxImageWidth = rightEdge - xDraw - (trim.width + trim.x);
			if (!parent.getSingle() && getCloseRect(item).width > 0)
				maxImageWidth -= getCloseRect(item).width + INTERNAL_SPACING;
			if (imageBounds.width < maxImageWidth) {
				int imageX = xDraw;
				int imageY = y + (height - imageBounds.height) / 2;
				imageY += parent.getTabPosition() == SWT.BOTTOM ? -1 : 1;
				gc.drawImage(image, imageX, imageY);
				xDraw += imageBounds.width + INTERNAL_SPACING;
			}
		}

		// draw Text
		int textWidth = rightEdge - xDraw - (trim.width + trim.x);
		if (!parent.getSingle() && getCloseRect(item).width > 0)
			textWidth -= getCloseRect(item).width + INTERNAL_SPACING;
		if (textWidth > 0) {
			Font gcFont = gc.getFont();
			gc.setFont(item.getFont() == null ? parent.getFont() : item
					.getFont());

			if (getShortenText(item) == null
					|| getShortenTextWidth(item) != textWidth) {
				setShortenText(item,
						_shortenText(gc, item.getText(), textWidth));
				setShortenTextWidth(item, textWidth);
			}
			Point extent = gc.textExtent(getShortenText(item),
					SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC);
			int textY = y + (height - extent.y) / 2;
			textY += parent.getTabPosition() == SWT.BOTTOM ? -1 : 1;

			if (selectedTabItemColor != null) {
				gc.setForeground(selectedTabItemColor);
			} else {
				gc.setForeground(parent.getSelectionForeground());
			}

			gc.drawText(getShortenText(item), xDraw, textY,
					SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC);
			gc.setFont(gcFont);

			// draw a Focus rectangle
			if (parent.isFocusControl()) {
				Display display = parent.getDisplay();
				if (parent.getSimple() || parent.getSingle()) {
					gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
					gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
					gc.drawFocus(xDraw - 1, textY - 1, extent.x + 2,
							extent.y + 2);
				} else {
					gc.setForeground(display
							.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW));
					gc.drawLine(xDraw, textY + extent.y + 1, xDraw + extent.x
							+ 1, textY + extent.y + 1);
				}
			}
		}
		if (hasStyle(parent, SWT.CLOSE) || hasStyle(item, SWT.CLOSE))
			_drawClose(gc, getCloseRect(item), getCloseImageState(item));
	}

	protected void drawSelectedTabItemBackground(int itemIndex, GC gc,
			Rectangle bounds, int state) {
		if (parent.getSingle() && parent.getItem(itemIndex).isShowing())
			return;

		int width = bounds.width;
		int[] points = new int[1024];
		int index = 0;
		int radius = cornerSize / 2;
		int circX = bounds.x + radius;
		int circY = bounds.y - 1 + radius;
		int selectionX1, selectionY1, selectionX2, selectionY2;
		if ((itemIndex == 0 || bounds.x <= 5)
				&& bounds.x == -computeTrim(CTabFolderRenderer.PART_HEADER,
						SWT.NONE, 0, 0, 0, 0).x) {
			circX -= 1;
			points[index++] = circX - radius;
			points[index++] = bounds.y + bounds.height;

			points[index++] = selectionX1 = circX - radius;
			points[index++] = selectionY1 = bounds.y + bounds.height;
		} else {
			if (active) {
				points[index++] = shadowEnabled ? SIDE_DROP_WIDTH : 0
						+ INNER_KEYLINE + OUTER_KEYLINE;
				points[index++] = bounds.y + bounds.height;
			}
			points[index++] = selectionX1 = bounds.x;
			points[index++] = selectionY1 = bounds.y + bounds.height;
		}
		int[] ltt = drawCircle(circX, circY, radius, LEFT_TOP);
		int startX = ltt[6];
		for (int i = 0; i < ltt.length / 2; i += 2) {
			int tmp = ltt[i];
			ltt[i] = ltt[ltt.length - i - 2];
			ltt[ltt.length - i - 2] = tmp;
			tmp = ltt[i + 1];
			ltt[i + 1] = ltt[ltt.length - i - 1];
			ltt[ltt.length - i - 1] = tmp;
		}
		System.arraycopy(ltt, 0, points, index, ltt.length);
		index += ltt.length;

		int[] rt = drawCircle(circX + width - (radius * 2), circY, radius,
				RIGHT_TOP);
		int endX = rt[rt.length - 4];
		for (int i = 0; i < rt.length / 2; i += 2) {
			int tmp = rt[i];
			rt[i] = rt[rt.length - i - 2];
			rt[rt.length - i - 2] = tmp;
			tmp = rt[i + 1];
			rt[i + 1] = rt[rt.length - i - 1];
			rt[rt.length - i - 1] = tmp;
		}
		System.arraycopy(rt, 0, points, index, rt.length);
		index += rt.length;

		points[index++] = selectionX2 = bounds.width + circX - radius;
		points[index++] = selectionY2 = bounds.y + bounds.height;

		if (active) {
			points[index++] = parent.getSize().x
					- (shadowEnabled ? SIDE_DROP_WIDTH : 0 + INNER_KEYLINE
							+ OUTER_KEYLINE);
			points[index++] = bounds.y + bounds.height;
		}
		gc.setClipping(0, bounds.y, parent.getSize().x
				- (shadowEnabled ? SIDE_DROP_WIDTH : 0 + INNER_KEYLINE
						+ OUTER_KEYLINE), bounds.y + bounds.height);// bounds.height
		// +
		// 4);
		if (selectedTabFillColor == null)
			selectedTabFillColor = gc.getDevice().getSystemColor(
					SWT.COLOR_WHITE);
		gc.setBackground(selectedTabFillColor);
		gc.setForeground(selectedTabFillColor);
		Pattern backgroundPattern = null;

		if (selectedTabFillHighlightColor != null) {
			backgroundPattern = new Pattern(gc.getDevice(), 0, 0, 0,
					bounds.height + 1, selectedTabFillHighlightColor,
					selectedTabFillColor);
			gc.setBackgroundPattern(backgroundPattern);
		}

		int[] tmpPoints = new int[index];
		System.arraycopy(points, 0, tmpPoints, 0, index);

		gc.fillPolygon(translate(tmpPoints, 1, 1));
		gc.drawLine(selectionX1, selectionY1, selectionX2, selectionY2);
		if (tabOutlineColor == null)
			tabOutlineColor = gc.getDevice().getSystemColor(SWT.COLOR_BLACK);
		gc.setForeground(tabOutlineColor);
		Color gradientLineTop = null;
		Pattern foregroundPattern = null;
		if (!active) {
			RGB blendColor = gc.getDevice()
					.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW).getRGB();
			RGB topGradient = blend(blendColor, tabOutlineColor.getRGB(), 40);
			gradientLineTop = new Color(gc.getDevice(), topGradient);
			foregroundPattern = new Pattern(gc.getDevice(), 0, 0, 0,
					bounds.height + 1, gradientLineTop, gc.getDevice()
							.getSystemColor(SWT.COLOR_WHITE));
			gc.setForegroundPattern(foregroundPattern);
		}
		gc.drawPolyline(tmpPoints);
		Rectangle rect = null;
		gc.setClipping(rect);

		if (active) {
			if (outerKeyline == null)
				outerKeyline = gc.getDevice().getSystemColor(SWT.COLOR_RED);
			gc.setForeground(outerKeyline);
			gc.drawPolyline(shape);
		} else {
			gc.drawLine(startX, 0, endX, 0);
			if (backgroundPattern != null)
				backgroundPattern.dispose();
			if (gradientLineTop != null)
				gradientLineTop.dispose();
			if (foregroundPattern != null)
				foregroundPattern.dispose();

		}
	}

	void drawShadow(final Display display, Rectangle bounds, GC gc) {
		if (shadowImage == null) {
			createShadow(display, true);
		}

		int x = bounds.x;
		int y = bounds.y;
		int SIZE = shadowImage.getBounds().width / 3;

		int height = Math.max(bounds.height, SIZE * 2);
		int width = Math.max(bounds.width, SIZE * 2);
		// top left
		gc.drawImage(shadowImage, 0, 0, SIZE, SIZE, 2, 10, SIZE, 20);
		int fillHeight = height - SIZE * 2;
		int fillWidth = width + 5 - SIZE * 2;

		int xFill = 0;
		for (int i = SIZE; i < fillHeight; i += SIZE) {
			xFill = i;
			gc.drawImage(shadowImage, 0, SIZE, SIZE, SIZE, 2, i, SIZE, SIZE);
		}

		// Pad the rest of the shadow
		gc.drawImage(shadowImage, 0, SIZE, SIZE, fillHeight - xFill, 2, xFill
				+ SIZE, SIZE, fillHeight - xFill);

		// bl
		gc.drawImage(shadowImage, 0, 40, 20, 20, 2, y + height - SIZE, 20, 20);

		int yFill = 0;
		for (int i = SIZE; i <= fillWidth; i += SIZE) {
			yFill = i;
			gc.drawImage(shadowImage, SIZE, SIZE * 2, SIZE, SIZE, i, y + height
					- SIZE, SIZE, SIZE);
		}
		// Pad the rest of the shadow
		gc.drawImage(shadowImage, SIZE, SIZE * 2, fillWidth - yFill, SIZE,
				yFill + SIZE, y + height - SIZE, fillWidth - yFill, SIZE);

		// br
		gc.drawImage(shadowImage, SIZE * 2, SIZE * 2, SIZE, SIZE, x + width
				- SIZE - 1, y + height - SIZE, SIZE, SIZE);

		// tr
		gc.drawImage(shadowImage, (SIZE * 2), 0, SIZE, SIZE, x + width - SIZE
				- 1, 10, SIZE, SIZE);

		xFill = 0;
		for (int i = SIZE; i < fillHeight; i += SIZE) {
			xFill = i;
			gc.drawImage(shadowImage, SIZE * 2, SIZE, SIZE, SIZE, x + width
					- SIZE - 1, i, SIZE, SIZE);
		}

		// Pad the rest of the shadow
		gc.drawImage(shadowImage, SIZE * 2, SIZE, SIZE, fillHeight - xFill, x
				+ width - SIZE - 1, xFill + SIZE, SIZE, fillHeight - xFill);
	}

	void drawTabBody(GC gc, Rectangle bounds, int state) {
		int[] points = new int[1024];
		int index = 0;
		int radius = cornerSize / 2;
		int marginWidth = parent.marginWidth;
		int marginHeight = parent.marginHeight;
		int delta = INNER_KEYLINE + OUTER_KEYLINE + 2
				* (shadowEnabled ? SIDE_DROP_WIDTH : 0) + 2 * marginWidth;
		int width = bounds.width - delta;
		int height = Math.max(parent.getTabHeight() + INNER_KEYLINE
				+ OUTER_KEYLINE + (shadowEnabled ? BOTTOM_DROP_WIDTH : 0),
				bounds.height - INNER_KEYLINE - OUTER_KEYLINE - 2
						* marginHeight
						- (shadowEnabled ? BOTTOM_DROP_WIDTH : 0));

		int circX = bounds.x + delta / 2 + radius;
		int circY = bounds.y + radius;

		// Body
		index = 0;
		int[] ltt = drawCircle(circX, circY, radius, LEFT_TOP);
		System.arraycopy(ltt, 0, points, index, ltt.length);
		index += ltt.length;

		int[] lbb = drawCircle(circX, circY + height - (radius * 2), radius,
				LEFT_BOTTOM);
		System.arraycopy(lbb, 0, points, index, lbb.length);
		index += lbb.length;

		int[] rb = drawCircle(circX + width - (radius * 2), circY + height
				- (radius * 2), radius, RIGHT_BOTTOM);
		System.arraycopy(rb, 0, points, index, rb.length);
		index += rb.length;

		int[] rt = drawCircle(circX + width - (radius * 2), circY, radius,
				RIGHT_TOP);
		System.arraycopy(rt, 0, points, index, rt.length);
		index += rt.length;
		points[index++] = circX;
		points[index++] = circY - radius;

		int[] tempPoints = new int[index];
		System.arraycopy(points, 0, tempPoints, 0, index);
		gc.fillPolygon(tempPoints);

		// Fill in parent background for non-rectangular shape
		Region r = new Region();
		r.add(bounds);
		r.subtract(tempPoints);
		gc.setBackground(parent.getParent().getBackground());
		Display display = parent.getDisplay();
		Region clipping = new Region();
		gc.getClipping(clipping);
		r.intersect(clipping);
		gc.setClipping(r);
		Rectangle mappedBounds = display
				.map(parent, parent.getParent(), bounds);

		parent.getParent().drawBackground(gc, bounds.x, bounds.y, bounds.width,
				bounds.height, mappedBounds.x, mappedBounds.y);

		// Shadow
		if (shadowEnabled)
			drawShadow(display, bounds, gc);

		gc.setClipping(clipping);
		clipping.dispose();
		r.dispose();

		// Remember for use in header drawing
		shape = tempPoints;
	}

	void drawTabHeader(GC gc, Rectangle bounds, int state) {
		int[] points = new int[1024];
		int index = 0;
		int radius = cornerSize / 2;
		int marginWidth = parent.marginWidth;
		int marginHeight = parent.marginHeight;
		int delta = INNER_KEYLINE + OUTER_KEYLINE + 2
				* (shadowEnabled ? SIDE_DROP_WIDTH : 0) + 2 * marginWidth;
		int width = bounds.width - delta;
		int height = bounds.height - INNER_KEYLINE - OUTER_KEYLINE - 2
				* marginHeight - (shadowEnabled ? BOTTOM_DROP_WIDTH : 0);
		int circX = bounds.x + delta / 2 + radius;
		int circY = bounds.y + radius;

		// Fill in background
		Region clipping = new Region();
		gc.getClipping(clipping);
		Region region = new Region();
		region.add(shape);
		region.intersect(clipping);
		gc.setClipping(region);

		int header = 3; // TODO: this needs to be added to computeTrim for
		// HEADER
		Rectangle trim = computeTrim(PART_HEADER, state, 0, 0, 0, 0);
		trim.width = bounds.width - trim.width;
		trim.height = (parent.getTabHeight() + 1 + header) - trim.height;
		trim.x = -trim.x;
		trim.y = -trim.y;

		draw(PART_BACKGROUND, SWT.NONE, trim, gc);

		gc.setClipping(clipping);
		clipping.dispose();
		region.dispose();

		int[] ltt = drawCircle(circX + 1, circY + 1, radius, LEFT_TOP);
		System.arraycopy(ltt, 0, points, index, ltt.length);
		index += ltt.length;

		int[] lbb = drawCircle(circX + 1, circY + height - (radius * 2) - 2,
				radius, LEFT_BOTTOM);
		System.arraycopy(lbb, 0, points, index, lbb.length);
		index += lbb.length;

		int[] rb = drawCircle(circX + width - (radius * 2) - 2, circY + height
				- (radius * 2) - 2, radius, RIGHT_BOTTOM);
		System.arraycopy(rb, 0, points, index, rb.length);
		index += rb.length;

		int[] rt = drawCircle(circX + width - (radius * 2) - 2, circY + 1,
				radius, RIGHT_TOP);
		System.arraycopy(rt, 0, points, index, rt.length);
		index += rt.length;
		points[index++] = points[0];
		points[index++] = points[1];

		int[] tempPoints = new int[index];
		System.arraycopy(points, 0, tempPoints, 0, index);

		if (outerKeyline == null)
			outerKeyline = gc.getDevice().getSystemColor(SWT.COLOR_BLACK);
		gc.setForeground(outerKeyline);
		gc.drawPolyline(shape);
	}

	protected void drawUnselectedTabItem(int index, GC gc, Rectangle bounds,
			int state) {
		try {
			CTabItem item = parent.getItem(index);
			int x = bounds.x;
			int y = bounds.y;
			int height = bounds.height;
			int width = bounds.width;

			// Do not draw partial items
			if (!item.isShowing())
				return;

			Rectangle clipping = gc.getClipping();
			if (!clipping.intersects(bounds))
				return;

			if ((state & SWT.FOREGROUND) != 0) {
				// draw Image
				Rectangle trim = computeTrim(index, SWT.NONE, 0, 0, 0, 0);
				int xDraw = x - trim.x;
				Image image = item.getImage();
				if (image != null && !image.isDisposed()
						&& parent.getUnselectedImageVisible()) {
					Rectangle imageBounds = image.getBounds();
					// only draw image if it won't overlap with close button
					int maxImageWidth = x + width - xDraw
							- (trim.width + trim.x);
					if (parent.getUnselectedCloseVisible()
							&& ((parent.getStyle() & SWT.CLOSE) != 0 || item
									.getShowClose())) {
						maxImageWidth -= ((Rectangle) HACK_CTabItem_closeRect
								.get(item)).width + 4;
					}
					if (imageBounds.width < maxImageWidth) {
						int imageX = xDraw;
						int imageHeight = imageBounds.height;
						int imageY = y + (height - imageHeight) / 2;
						imageY += parent.getTabPosition() == SWT.BOTTOM ? -1
								: 1;
						int imageWidth = imageBounds.width * imageHeight
								/ imageBounds.height;
						gc.drawImage(image, imageBounds.x, imageBounds.y,
								imageBounds.width, imageBounds.height, imageX,
								imageY, imageWidth, imageHeight);
						xDraw += imageWidth + 4;
					}
				}

				// draw Text
				int textWidth = x + width - xDraw - (trim.width + trim.x);
				if (parent.getUnselectedCloseVisible()
						&& ((parent.getStyle() & SWT.CLOSE) != 0 || item
								.getShowClose())) {
					textWidth -= ((Rectangle) HACK_CTabItem_closeRect.get(item)).width + 4;
				}
				if (textWidth > 0) {
					Font gcFont = gc.getFont();
					gc.setFont(item.getFont() == null ? parent.getFont() : item
							.getFont());
					if (HACK_CTabItem_shortenText.get(item) == null
							|| HACK_CTabItem_shortenTextWidth.getInt(item) != textWidth) {
						HACK_CTabItem_shortenText.set(item,
								_shortenText(gc, item.getText(), textWidth));
						HACK_CTabItem_shortenTextWidth.setInt(item, textWidth);
					}
					Point extent = gc.textExtent(
							(String) HACK_CTabItem_shortenText.get(item),
							SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC);
					int textY = y + (height - extent.y) / 2;
					textY += parent.getTabPosition() == SWT.BOTTOM ? -1 : 1;

					if (showUnselectedTabItemShadow()) {
						gc.setAlpha(180);
						gc.setForeground(parent.getDisplay().getSystemColor(
								SWT.COLOR_WHITE));
						gc.drawText(
								(String) HACK_CTabItem_shortenText.get(item),
								xDraw, textY + 1, SWT.DRAW_TRANSPARENT
										| SWT.DRAW_MNEMONIC);
					}

					gc.setAlpha(255);

					if (unselectedTabItemColor != null) {
						gc.setForeground(unselectedTabItemColor);
					} else {
						gc.setForeground(parent.getForeground());
					}
					gc.drawText((String) HACK_CTabItem_shortenText.get(item),
							xDraw, textY, SWT.DRAW_TRANSPARENT
									| SWT.DRAW_MNEMONIC);
					gc.setFont(gcFont);
				}

				if ((state & SWT.HOT) != 0
						&& parent.getUnselectedCloseVisible()
						&& (hasStyle(parent, SWT.CLOSE) || hasStyle(item,
								SWT.CLOSE)))
					_drawClose(gc, getCloseRect(item), getCloseImageState(item));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected void drawUnselectedTabItemBackground(int itemIndex, GC gc,
			Rectangle bounds, int state) {
		if ((state & SWT.HOT) != 0) {
			int width = bounds.width;
			int[] points = new int[1024];
			int[] inactive = new int[8];
			int index = 0, inactive_index = 0;
			int radius = cornerSize / 2;
			int circX = bounds.x + radius;
			int circY = bounds.y - 1 + radius;

			int leftIndex = circX;
			if (itemIndex == 0 || bounds.x <= 5) {
				if (parent.getSelectionIndex() != 0)
					leftIndex -= 1;
				points[index++] = leftIndex - radius;
				points[index++] = bounds.y + bounds.height;
			} else {
				points[index++] = bounds.x;
				points[index++] = bounds.y + bounds.height;
			}

			if (!active) {
				System.arraycopy(points, 0, inactive, 0, index);
				inactive_index += 2;
			}

			int[] ltt = drawCircle(leftIndex, circY, radius, LEFT_TOP);
			for (int i = 0; i < ltt.length / 2; i += 2) {
				int tmp = ltt[i];
				ltt[i] = ltt[ltt.length - i - 2];
				ltt[ltt.length - i - 2] = tmp;
				tmp = ltt[i + 1];
				ltt[i + 1] = ltt[ltt.length - i - 1];
				ltt[ltt.length - i - 1] = tmp;
			}
			System.arraycopy(ltt, 0, points, index, ltt.length);
			index += ltt.length;

			if (!active) {
				System.arraycopy(ltt, 0, inactive, inactive_index, 2);
				inactive_index += 2;
			}

			int rightIndex = circX - 1;
			int[] rt = drawCircle(rightIndex + width - (radius * 2), circY,
					radius, RIGHT_TOP);
			for (int i = 0; i < rt.length / 2; i += 2) {
				int tmp = rt[i];
				rt[i] = rt[rt.length - i - 2];
				rt[rt.length - i - 2] = tmp;
				tmp = rt[i + 1];
				rt[i + 1] = rt[rt.length - i - 1];
				rt[rt.length - i - 1] = tmp;
			}
			System.arraycopy(rt, 0, points, index, rt.length);
			index += rt.length;
			if (!active) {
				System.arraycopy(rt, rt.length - 4, inactive, inactive_index, 2);
				inactive[inactive_index] -= 1;
				inactive_index += 2;
			}

			points[index++] = bounds.width + rightIndex - radius;
			points[index++] = bounds.y + bounds.height;

			if (!active) {
				System.arraycopy(points, index - 2, inactive, inactive_index, 2);
				inactive[inactive_index] -= 1;
				inactive_index += 2;
			}

			gc.setClipping(points[0], bounds.y, parent.getSize().x
					- (shadowEnabled ? SIDE_DROP_WIDTH : 0 + INNER_KEYLINE
							+ OUTER_KEYLINE), bounds.y + bounds.height);

			gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE));
			int[] tmpPoints = new int[index];
			System.arraycopy(points, 0, tmpPoints, 0, index);

			gc.setAlpha(120);
			gc.fillPolygon(translate(tmpPoints, 1, 1));
			Color tempBorder = new Color(gc.getDevice(), 182, 188, 204);
			gc.setForeground(tempBorder);
			gc.drawPolygon(tmpPoints);
			tempBorder.dispose();
			gc.setAlpha(255);
		}
	}

	private Integer getCloseImageState(CTabItem item) {
		try {
			return (Integer) HACK_CTabItem_closeImageState.get(item);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Rectangle getCloseRect(CTabItem item) {
		try {
			return (Rectangle) HACK_CTabItem_closeRect.get(item);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Integer getCurveIndent() {
		try {
			return (Integer) HACK_CTabFolder_curveIndent.get(this);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Integer getCurveWidth() {
		try {
			return (Integer) HACK_CTabFolder_curveWidth.get(this);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Rectangle getPadding() {
		return new Rectangle(paddingTop, paddingRight, paddingBottom,
				paddingLeft);
	}

	private Integer getRightItemEdge(CTabFolder folder, GC gc) {
		try {
			return (Integer) HACK_CTabFolder_getRightItemEdge
					.invoke(folder, gc);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Color getSelectedTabFillHighlightColor() {
		return selectedTabFillHighlightColor;
	}

	public Color getSelectedTabItemColor() {
		return selectedTabItemColor;
	}

	private String getShortenText(CTabItem item) {
		try {
			return (String) HACK_CTabItem_shortenText.get(item);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Integer getShortenTextWidth(CTabItem item) {
		try {
			return (Integer) HACK_CTabItem_shortenTextWidth.get(item);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Color getUnselectedTabItemColor() {
		return unselectedTabItemColor;
	}

	private boolean hasStyle(Widget w, int flag) {
		return (w.getStyle() & flag) != 0;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	public void setActiveToolbarGradient(Color[] color, int[] percents) {
		activeToolbar = color;
		activePercents = percents;
	}

	public void setCornerRadius(int radius) {
		cornerSize = radius;
		parent.redraw();
	}

	public void setInactiveToolbarGradient(Color[] color, int[] percents) {
		inactiveToolbar = color;
		inactivePercents = percents;
	}

	public void setInnerKeyline(Color color) {
		this.innerKeyline = color;
		parent.redraw();
	}

	public void setOuterKeyline(Color color) {
		this.outerKeyline = color;
		// TODO: HACK! Should be set based on pseudo-state.
		setActive(!(color.getRed() == 255 && color.getGreen() == 255 && color
				.getBlue() == 255));
		parent.redraw();
	}

	public void setPadding(int paddingLeft, int paddingRight, int paddingTop,
			int paddingBottom) {
		this.paddingLeft = paddingLeft;
		this.paddingRight = paddingRight;
		this.paddingTop = paddingTop;
		this.paddingBottom = paddingBottom;
		parent.redraw();
	}

	public void setSelectedTabFill(Color color) {
		this.selectedTabFillColor = color;
		parent.redraw();
	}

	public void setSelectedTabFillHighlightColor(
			Color selectedTabFillHighlightColor) {
		this.selectedTabFillHighlightColor = selectedTabFillHighlightColor;
	}

	public void setSelectedTabItemColor(Color selectedTabItemColor) {
		this.selectedTabItemColor = selectedTabItemColor;
	}

	public void setShadowColor(Color color) {
		this.shadowColor = color;
		createShadow(parent.getDisplay(), true);
		parent.redraw();
	}

	public void setShadowVisible(boolean visible) {
		this.shadowEnabled = visible;
		parent.redraw();
	}

	private void setShortenText(CTabItem item, String shortenText) {
		try {
			HACK_CTabItem_shortenText.set(item, shortenText);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void setShortenTextWidth(CTabItem item, int width) {
		try {
			HACK_CTabItem_shortenTextWidth.set(item, width);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void setTabOutline(Color color) {
		this.tabOutlineColor = color;
		parent.redraw();
	}

	public void setUnselectedTabItemColor(Color unselectedTabItemColor) {
		this.unselectedTabItemColor = unselectedTabItemColor;
	}

	protected boolean showUnselectedTabItemShadow() {
		return true;
	}

	private int[] translate(int[] pointArray, int dx, int dy) {
		int[] result = new int[pointArray.length];
		System.arraycopy(pointArray, 0, result, 0, pointArray.length);
		for (int i = 0; i < result.length; i += 2) {
			result[i] += dx;
			result[i + 1] += dy;
		}
		return result;
	}

	protected void updateItems() {
		try {
			HACK_CTabFolder_updateItems.invoke(parent);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}