/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Original authors and others - initial API and implementation
 *     Loris Securo <lorissek@gmail.com> - Bug 499701, 500800
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.painter.cell;

import org.eclipse.nebula.widgets.nattable.style.BorderStyle;
import org.eclipse.nebula.widgets.nattable.style.BorderStyle.BorderModeEnum;
import org.eclipse.nebula.widgets.nattable.style.BorderStyle.LineStyleEnum;
import org.eclipse.swt.SWT;
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.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;

/**
 * This class contains utility methods for drawing graphics
 *
 * @see <a href=
 *      "http://java-gui.info/Apress-The.Definitive.Guide.to.SWT.and.JFace/8886final/LiB0095.html">GC
 *      snippets</a>
 */
public final class GraphicsUtils {

    private GraphicsUtils() {
        // private default constructor for helper class
    }

    /**
     * Draws text vertically (rotates plus or minus 90 degrees). Uses the
     * current font, color, and background.
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param string
     *            the text to draw
     * @param x
     *            the x coordinate of the top left corner of the drawing
     *            rectangle
     * @param y
     *            the y coordinate of the top left corner of the drawing
     *            rectangle
     * @param gc
     *            the GC on which to draw the text
     * @param style
     *            the style (SWT.UP or SWT.DOWN)
     *            <p>
     *            Note: Only one of the style UP or DOWN may be specified.
     *            </p>
     */
    public static void drawVerticalText(String string, int x, int y, GC gc, int style) {
        drawVerticalText(string, x, y, false, false, true, gc, style);
    }

    /**
     * Draws text vertically (rotates plus or minus 90 degrees). Uses the
     * current font, color, and background.
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param string
     *            the text to draw
     * @param x
     *            the x coordinate of the top left corner of the drawing
     *            rectangle
     * @param y
     *            the y coordinate of the top left corner of the drawing
     *            rectangle
     * @param underline
     *            set to <code>true</code> to render the text underlined
     * @param strikethrough
     *            set to <code>true</code> to render the text strikethrough
     * @param paintBackground
     *            set to <code>false</code> to render the background
     *            transparent. Needed for example to render the background with
     *            an image or gradient with another painter so the text drawn
     *            here should have no background.
     * @param gc
     *            the GC on which to draw the text
     * @param style
     *            the style (SWT.UP or SWT.DOWN)
     *            <p>
     *            Note: Only one of the style UP or DOWN may be specified.
     *            </p>
     */
    public static void drawVerticalText(String string, int x, int y,
            boolean underline, boolean strikethrough, boolean paintBackground,
            GC gc, int style) {
        // Get the current display
        Display display = Display.getCurrent();
        if (display == null)
            SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

        // Determine string's dimensions
        Point pt = gc.textExtent(string.trim());

        // Create an image the same size as the string
        Image stringImage = new Image(display, pt.x, pt.y);

        // Create a GC so we can draw the image
        GC stringGc = new GC(stringImage);

        // Set attributes from the original GC to the new GC
        stringGc.setAntialias(gc.getAntialias());
        stringGc.setTextAntialias(gc.getTextAntialias());
        stringGc.setForeground(gc.getForeground());
        stringGc.setBackground(gc.getBackground());
        stringGc.setFont(gc.getFont());

        // Fill the image with the specified background color
        // to avoid white spaces if the text does not fill the
        // whole image (e.g. on new lines)
        stringGc.fillRectangle(0, 0, pt.x, pt.y);

        // Draw the text onto the image
        stringGc.drawText(string, 0, 0);

        // draw underline and/or strikethrough
        if (underline || strikethrough) {
            // check and draw underline and strikethrough separately so it is
            // possible to combine both
            if (underline) {
                // y = start y of text + font height
                // - half of the font descent so the underline is between the
                // baseline and the bottom
                int underlineY = pt.y
                        - (stringGc.getFontMetrics().getDescent() / 2);
                stringGc.drawLine(0, underlineY, pt.x, underlineY);
            }

            if (strikethrough) {
                // y = start y of text + half of font height + ascent so lower
                // case characters are
                // also strikethrough
                int strikeY = (pt.y / 2)
                        + (stringGc.getFontMetrics().getLeading() / 2);
                stringGc.drawLine(0, strikeY, pt.x, strikeY);
            }
        }

        // Draw the image vertically onto the original GC
        drawVerticalImage(stringImage, x, y, paintBackground, gc, style);

        // Dispose the new GC
        stringGc.dispose();

        // Dispose the image
        stringImage.dispose();
    }

    /**
     * Draws an image vertically (rotates plus or minus 90 degrees)
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param image
     *            the image to draw
     * @param x
     *            the x coordinate of the top left corner of the drawing
     *            rectangle
     * @param y
     *            the y coordinate of the top left corner of the drawing
     *            rectangle
     * @param gc
     *            the GC on which to draw the image
     * @param style
     *            the style (SWT.UP or SWT.DOWN)
     *            <p>
     *            Note: Only one of the style UP or DOWN may be specified.
     *            </p>
     */
    public static void drawVerticalImage(Image image, int x, int y, GC gc,
            int style) {
        drawVerticalImage(image, x, y, true, gc, style);
    }

    /**
     * Draws an image vertically (rotates plus or minus 90 degrees)
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param image
     *            the image to draw
     * @param x
     *            the x coordinate of the top left corner of the drawing
     *            rectangle
     * @param y
     *            the y coordinate of the top left corner of the drawing
     *            rectangle
     * @param paintBackground
     *            set to <code>false</code> to render the background
     *            transparent. Needed for example to render the background with
     *            an image or gradient with another painter so the text drawn
     *            here should have no background.
     * @param gc
     *            the GC on which to draw the image
     * @param style
     *            the style (SWT.UP or SWT.DOWN)
     *            <p>
     *            Note: Only one of the style UP or DOWN may be specified.
     *            </p>
     */
    public static void drawVerticalImage(Image image, int x, int y,
            boolean paintBackground, GC gc, int style) {
        // Get the current display
        Display display = Display.getCurrent();
        if (display == null)
            SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

        // Use the image's data to create a rotated image's data
        ImageData sd = image.getImageData();
        ImageData dd = new ImageData(sd.height, sd.width, sd.depth, sd.palette);
        dd.transparentPixel = sd.transparentPixel;

        // set the defined backgroundcolor to be transparent
        if (!paintBackground) {
            dd.transparentPixel = sd.palette.getPixel(gc.getBackground()
                    .getRGB());
        }

        // Determine which way to rotate, depending on up or down
        boolean up = (style & SWT.UP) == SWT.UP;

        // Run through the horizontal pixels
        for (int sx = 0; sx < sd.width; sx++) {
            // Run through the vertical pixels
            for (int sy = 0; sy < sd.height; sy++) {
                // Determine where to move pixel to in destination image data
                int dx = up ? sy : sd.height - sy - 1;
                int dy = up ? sd.width - sx - 1 : sx;
                // Swap the x, y source data to y, x in the destination
                dd.setPixel(dx, dy, sd.getPixel(sx, sy));
            }
        }

        // Create the vertical image
        Image vertical = new Image(display, dd);

        // Draw the vertical image onto the original GC
        gc.drawImage(vertical, x, y);

        // Dispose the vertical image
        vertical.dispose();
    }

    /**
     * Creates an image containing the specified text, rotated either plus or
     * minus 90 degrees.
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param text
     *            the text to rotate
     * @param font
     *            the font to use
     * @param foreground
     *            the color for the text
     * @param background
     *            the background color
     * @param style
     *            direction to rotate (up or down)
     * @return Image
     *         <p>
     *         Note: Only one of the style UP or DOWN may be specified.
     *         </p>
     */
    public static Image createRotatedText(String text, Font font,
            Color foreground, Color background, int style) {
        // Get the current display
        Display display = Display.getCurrent();
        if (display == null)
            SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

        // Create a GC to calculate font's dimensions
        GC gc = new GC(display);
        gc.setFont(font);

        // Determine string's dimensions
        Point pt = gc.textExtent(text);

        // Dispose that gc
        gc.dispose();

        // Create an image the same size as the string
        Image stringImage = new Image(display, pt.x, pt.y);
        // Create a gc for the image
        gc = new GC(stringImage);
        gc.setFont(font);
        gc.setForeground(foreground);
        gc.setBackground(background);

        // Draw the text onto the image
        gc.drawText(text, 0, 0);

        // Draw the image vertically onto the original GC
        Image image = createRotatedImage(stringImage, style);

        // Dispose the new GC
        gc.dispose();

        // Dispose the horizontal image
        stringImage.dispose();

        // Return the rotated image
        return image;
    }

    /**
     * Creates a rotated image (plus or minus 90 degrees)
     * <dl>
     * <dt><b>Styles: </b></dt>
     * <dd>UP, DOWN</dd>
     * </dl>
     *
     * @param image
     *            the image to rotate
     * @param style
     *            direction to rotate (up or down)
     * @return Image
     *         <p>
     *         Note: Only one of the style UP or DOWN may be specified.
     *         </p>
     */
    public static Image createRotatedImage(Image image, int style) {
        // Get the current display
        Display display = Display.getCurrent();
        if (display == null)
            SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

        // Use the image's data to create a rotated image's data
        ImageData sd = image.getImageData();
        ImageData dd = new ImageData(sd.height, sd.width, sd.depth, sd.palette);

        // Determine which way to rotate, depending on up or down
        boolean up = (style & SWT.UP) == SWT.UP;

        // Run through the horizontal pixels
        for (int sx = 0; sx < sd.width; sx++) {
            // Run through the vertical pixels
            for (int sy = 0; sy < sd.height; sy++) {
                // Determine where to move pixel to in destination image data
                int dx = up ? sy : sd.height - sy - 1;
                int dy = up ? sd.width - sx - 1 : sx;

                // Swap the x, y source data to y, x in the destination
                dd.setPixel(dx, dy, sd.getPixel(sx, sy));
            }
        }

        // Create the vertical image
        return new Image(display, dd);
    }

    /**
     * Draws a horizontal line starting at (x, y) and having the given width.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * width.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     *
     * @see #drawLineHorizontalBorderTop(GC, int, int, int)
     * @see #drawLineHorizontalBorderBottom(GC, int, int, int)
     * @see #drawLineHorizontal(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineHorizontal(GC gc, int x, int y, int width) {

        if (width == 0) {
            return;
        }

        int x2;

        // create coordinate for destination point
        if (width > 0) {
            x2 = x + width - 1;
        } else {
            x2 = x + width + 1;
        }

        // fix position and length
        if (x2 < x) {
            x++;
        } else {
            int lineWidth = gc.getLineWidth();
            if (lineWidth != 1) {
                x2++;
            }
        }

        gc.drawLine(x, y, x2, y);
    }

    /**
     * The difference between this method and
     * {@link #drawLineHorizontal(GC, int, int, int)} is that the line could be
     * extended to draw left and/or right corners (e.g. the corners of a
     * rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     * @param drawLeftCorner
     *            to draw the left corner
     * @param drawRightCorner
     *            to draw the right corner
     *
     * @see #drawLineHorizontal(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineHorizontal(GC gc, int x, int y, int width, boolean drawLeftCorner, boolean drawRightCorner) {

        if (width == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (width > 0) {
            if (drawLeftCorner) {
                x = x - (lineWidth / 2);
                width = width + (lineWidth / 2);
            }
            if (drawRightCorner) {
                width = width + ((lineWidth - 1) / 2);
            }
        } else {
            if (drawRightCorner) {
                x = x + ((lineWidth - 1) / 2);
                width = width - ((lineWidth - 1) / 2);
            }
            if (drawLeftCorner) {
                width = width - (lineWidth / 2);
            }
        }

        drawLineHorizontal(gc, x, y, width);
    }

    /**
     * Draws a vertical line starting at (x, y) and having the given height.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * height.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     *
     * @see #drawLineVerticalBorderLeft(GC, int, int, int)
     * @see #drawLineVerticalBorderRight(GC, int, int, int)
     * @see #drawLineVertical(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineVertical(GC gc, int x, int y, int height) {

        if (height == 0) {
            return;
        }

        int y2;

        // create coordinate for destination point
        if (height > 0) {
            y2 = y + height - 1;
        } else {
            y2 = y + height + 1;
        }

        // fix position and length
        if (y2 < y) {
            y++;
        } else {
            int lineWidth = gc.getLineWidth();
            if (lineWidth != 1) {
                y2++;
            }
        }

        gc.drawLine(x, y, x, y2);
    }

    /**
     * The difference between this method and
     * {@link #drawLineVertical(GC, int, int, int)} is that the line could be
     * extended to draw top and/or bottom corners (e.g. the corners of a
     * rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     * @param drawTopCorner
     *            to draw the top corner
     * @param drawBottomCorner
     *            to draw the bottom corner
     *
     * @see #drawLineVertical(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineVertical(GC gc, int x, int y, int height, boolean drawTopCorner, boolean drawBottomCorner) {

        if (height == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (height > 0) {
            if (drawTopCorner) {
                y = y - (lineWidth / 2);
                height = height + (lineWidth / 2);
            }
            if (drawBottomCorner) {
                height = height + ((lineWidth - 1) / 2);
            }
        } else {
            if (drawBottomCorner) {
                y = y + ((lineWidth - 1) / 2);
                height = height - ((lineWidth - 1) / 2);
            }
            if (drawTopCorner) {
                height = height - (lineWidth / 2);
            }
        }

        drawLineVertical(gc, x, y, height);
    }

    /**
     * Draws a horizontal line starting at (x, y) and having the given width.
     * The increased thickness resulting from {@link GC#getLineWidth} will be
     * strictly drawn below the line.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * width.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     *
     * @see #drawLineHorizontal(GC, int, int, int)
     * @see #drawLineHorizontalBorderTop(GC, int, int, int)
     * @see #drawLineHorizontalBorderBottom(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineHorizontalBorderBottom(GC gc, int x, int y, int width) {

        if (width == 0) {
            return;
        }

        // adjust the line to make it have the border at bottom
        int lineWidth = gc.getLineWidth();
        y = y + (lineWidth / 2);

        drawLineHorizontal(gc, x, y, width);
    }

    /**
     * The difference between this method and
     * {@link #drawLineHorizontalBorderBottom(GC, int, int, int)} is that the
     * line could be extended to draw left and/or right corners (e.g. the
     * corners of a rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     * @param drawLeftCorner
     *            to draw the left corner
     * @param drawRightCorner
     *            to draw the right corner
     *
     * @see #drawLineHorizontalBorderBottom(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineHorizontalBorderBottom(GC gc, int x, int y, int width, boolean drawLeftCorner, boolean drawRightCorner) {

        if (width == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (width > 0) {
            if (drawLeftCorner) {
                x = x - (lineWidth - 1);
                width = width + (lineWidth - 1);
            }
            if (drawRightCorner) {
                width = width + (lineWidth - 1);
            }
        } else {
            if (drawRightCorner) {
                x = x + (lineWidth - 1);
                width = width - (lineWidth - 1);
            }
            if (drawLeftCorner) {
                width = width - (lineWidth - 1);
            }
        }

        drawLineHorizontalBorderBottom(gc, x, y, width);
    }

    /**
     * Draws a horizontal line starting at (x, y) and having the given width.
     * The increased thickness resulting from {@link GC#getLineWidth} will be
     * strictly drawn above the line.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * width.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     *
     * @see #drawLineHorizontal(GC, int, int, int)
     * @see #drawLineHorizontalBorderBottom(GC, int, int, int)
     * @see #drawLineHorizontalBorderTop(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineHorizontalBorderTop(GC gc, int x, int y, int width) {

        if (width == 0) {
            return;
        }

        // adjust the line to make it have the border at top
        int lineWidth = gc.getLineWidth();
        y = y - ((lineWidth - 1) / 2);

        drawLineHorizontal(gc, x, y, width);
    }

    /**
     * The difference between this method and
     * {@link #drawLineHorizontalBorderTop(GC, int, int, int)} is that the line
     * could be extended to draw left and/or right corners (e.g. the corners of
     * a rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param width
     *            the width of the line to draw
     * @param drawLeftCorner
     *            to draw the left corner
     * @param drawRightCorner
     *            to draw the right corner
     *
     * @see #drawLineHorizontalBorderTop(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineHorizontalBorderTop(GC gc, int x, int y, int width, boolean drawLeftCorner, boolean drawRightCorner) {

        if (width == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (width > 0) {
            if (drawLeftCorner) {
                x = x - (lineWidth - 1);
                width = width + (lineWidth - 1);
            }
            if (drawRightCorner) {
                width = width + (lineWidth - 1);
            }
        } else {
            if (drawRightCorner) {
                x = x + (lineWidth - 1);
                width = width - (lineWidth - 1);
            }
            if (drawLeftCorner) {
                width = width - (lineWidth - 1);
            }
        }

        drawLineHorizontalBorderTop(gc, x, y, width);
    }

    /**
     * Draws a vertical line starting at (x, y) and having the given height. The
     * increased thickness resulting from {@link GC#getLineWidth} will be
     * strictly drawn to the right of the line.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * height.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     *
     * @see #drawLineVertical(GC, int, int, int)
     * @see #drawLineVerticalBorderLeft(GC, int, int, int)
     * @see #drawLineVerticalBorderRight(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineVerticalBorderRight(GC gc, int x, int y, int height) {

        if (height == 0) {
            return;
        }

        // adjust the line to make it have the border at right
        int lineWidth = gc.getLineWidth();
        x = x + (lineWidth / 2);

        drawLineVertical(gc, x, y, height);
    }

    /**
     * The difference between this method and
     * {@link #drawLineVerticalBorderRight(GC, int, int, int)} is that the line
     * could be extended to draw top and/or bottom corners (e.g. the corners of
     * a rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     * @param drawTopCorner
     *            to draw the top corner
     * @param drawBottomCorner
     *            to draw the bottom corner
     *
     * @see #drawLineVerticalBorderRight(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineVerticalBorderRight(GC gc, int x, int y, int height, boolean drawTopCorner, boolean drawBottomCorner) {

        if (height == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (height > 0) {
            if (drawTopCorner) {
                y = y - (lineWidth - 1);
                height = height + (lineWidth - 1);
            }
            if (drawBottomCorner) {
                height = height + (lineWidth - 1);
            }
        } else {
            if (drawBottomCorner) {
                y = y + (lineWidth - 1);
                height = height - (lineWidth - 1);
            }
            if (drawTopCorner) {
                height = height - (lineWidth - 1);
            }
        }

        drawLineVerticalBorderRight(gc, x, y, height);
    }

    /**
     * Draws a vertical line starting at (x, y) and having the given height. The
     * increased thickness resulting from {@link GC#getLineWidth} will be
     * strictly drawn to the left of the line.
     * <p>
     * Unlike {@link GC#drawLine}, this method guarantees that the line will
     * always start at the given coordinates and will always have the given
     * height.
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     *
     * @see #drawLineVertical(GC, int, int, int)
     * @see #drawLineVerticalBorderRight(GC, int, int, int)
     * @see #drawLineVerticalBorderLeft(GC, int, int, int, boolean, boolean)
     * @since 1.5
     */
    public static void drawLineVerticalBorderLeft(GC gc, int x, int y, int height) {

        if (height == 0) {
            return;
        }

        // adjust the line to make it have the border at left
        int lineWidth = gc.getLineWidth();
        x = x - ((lineWidth - 1) / 2);

        drawLineVertical(gc, x, y, height);
    }

    /**
     * The difference between this method and
     * {@link #drawLineVerticalBorderLeft(GC, int, int, int)} is that the line
     * could be extended to draw top and/or bottom corners (e.g. the corners of
     * a rectangle).
     *
     * @param gc
     *            the GC to use to draw
     * @param x
     *            the starting point's x coordinate
     * @param y
     *            the starting point's y coordinate
     * @param height
     *            the height of the line to draw
     * @param drawTopCorner
     *            to draw the top corner
     * @param drawBottomCorner
     *            to draw the bottom corner
     *
     * @see #drawLineVerticalBorderLeft(GC, int, int, int)
     * @since 1.5
     */
    public static void drawLineVerticalBorderLeft(GC gc, int x, int y, int height, boolean drawTopCorner, boolean drawBottomCorner) {

        if (height == 0) {
            return;
        }

        // adjust the position and length in order to create the corners
        int lineWidth = gc.getLineWidth();
        if (height > 0) {
            if (drawTopCorner) {
                y = y - (lineWidth - 1);
                height = height + (lineWidth - 1);
            }
            if (drawBottomCorner) {
                height = height + (lineWidth - 1);
            }
        } else {
            if (drawBottomCorner) {
                y = y + (lineWidth - 1);
                height = height - (lineWidth - 1);
            }
            if (drawTopCorner) {
                height = height - (lineWidth - 1);
            }
        }

        drawLineVerticalBorderLeft(gc, x, y, height);
    }

    /**
     * Draws a filled rectangle.
     * <p>
     * Unlike {@link GC#fillRectangle(Rectangle)}, this method guarantees that
     * the rectangle will always start at the given coordinates even in case of
     * negative width/height.
     *
     * @param gc
     *            the GC to use to draw
     * @param rect
     *            the rectangle to draw
     * @since 1.5
     */
    public static void fillRectangle(GC gc, Rectangle rect) {

        Rectangle rectangle = new Rectangle(rect.x, rect.y, rect.width, rect.height);

        // even with negative lengths, the rectangle should start from (x, y)
        if (rectangle.width < 0) {
            rectangle.x++;
        }
        if (rectangle.height < 0) {
            rectangle.y++;
        }

        gc.fillRectangle(rectangle);
    }

    /**
     * Draws a rectangle with the given border style.
     * <p>
     * Unlike {@link GC#drawRectangle(Rectangle)}:
     * <ul>
     * <li>the width and height of the resulting rectangle will be always
     * exactly {@code rect.width} and {@code rect.height}
     * <li>the rectangle will always start at the given coordinates even in case
     * of negative width/height
     * </ul>
     *
     * @param gc
     *            the GC to use to draw
     * @param rect
     *            the rectangle to draw
     * @param borderStyle
     *            the border style of the rectangle
     * @since 1.5
     */
    public static void drawRectangle(GC gc, Rectangle rect, BorderStyle borderStyle) {

        int originalLineStyle = gc.getLineStyle();
        int originalLineWidth = gc.getLineWidth();
        Color originalForeground = gc.getForeground();

        gc.setLineStyle(LineStyleEnum.toSWT(borderStyle.getLineStyle()));
        gc.setLineWidth(borderStyle.getThickness());
        gc.setForeground(borderStyle.getColor());

        switch (borderStyle.getBorderMode()) {
            case CENTERED:
                drawRectangle(gc, rect);
                break;
            case INTERNAL:
                drawRectangleBorderInternal(gc, rect);
                break;
            case EXTERNAL:
                drawRectangleBorderExternal(gc, rect);
                break;
        }

        gc.setLineStyle(originalLineStyle);
        gc.setLineWidth(originalLineWidth);
        gc.setForeground(originalForeground);
    }

    /**
     * Draws a rectangle.
     * <p>
     * Unlike {@link GC#drawRectangle(Rectangle)}:
     * <ul>
     * <li>the width and height of the resulting rectangle will be always
     * exactly {@code rect.width} and {@code rect.height}
     * <li>the rectangle will always start at the given coordinates even in case
     * of negative width/height
     * </ul>
     *
     * @param gc
     *            the GC to use to draw
     * @param rect
     *            the rectangle to draw
     *
     * @see #drawRectangleBorderInternal
     * @see #drawRectangleBorderExternal
     * @since 1.5
     */
    public static void drawRectangle(GC gc, Rectangle rect) {

        if (rect.width == 0 || rect.height == 0) {
            return;
        }

        Rectangle rectangle = new Rectangle(rect.x, rect.y, rect.width, rect.height);

        int absWidth = Math.abs(rectangle.width);
        int absHeight = Math.abs(rectangle.height);

        // it's a point but it can grow with the border width
        if (absWidth == 1 && absHeight == 1) {

            int lineWidth = gc.getLineWidth();

            rectangle.x = rectangle.x - (lineWidth / 2);
            rectangle.y = rectangle.y - (lineWidth / 2);
            rectangle.width = rectangle.width + lineWidth - 1;
            rectangle.height = rectangle.height + lineWidth - 1;

            Color originalBackground = gc.getBackground();
            gc.setBackground(gc.getForeground());
            fillRectangle(gc, rectangle);
            gc.setBackground(originalBackground);

            return;
        }

        // it's a line
        if (absWidth == 1) {
            drawLineVertical(gc, rectangle.x, rectangle.y, rectangle.height);
            return;
        }
        if (absHeight == 1) {
            drawLineHorizontal(gc, rectangle.x, rectangle.y, rectangle.width);
            return;
        }

        // even with negative lengths, the rectangle should start from (x, y)
        if (rectangle.width < 0) {
            rectangle.x++;
        }
        if (rectangle.height < 0) {
            rectangle.y++;
        }

        // fix the lengths
        rectangle.width--;
        rectangle.height--;

        gc.drawRectangle(rectangle);
    }

    /**
     * Draws a rectangle. The increased border thickness resulting from
     * {@link GC#getLineWidth} will be strictly drawn inside of the rectangle.
     * <p>
     * Unlike {@link GC#drawRectangle(Rectangle)}:
     * <ul>
     * <li>the width and height of the resulting rectangle will be always
     * exactly {@code rect.width} and {@code rect.height}
     * <li>the rectangle will always start at the given coordinates even in case
     * of negative width/height
     * </ul>
     *
     * @param gc
     *            the GC to use to draw
     * @param rect
     *            the rectangle to draw
     * @see #drawRectangle(GC, Rectangle)
     * @see #drawRectangleBorderExternal
     * @since 1.5
     */
    public static void drawRectangleBorderInternal(GC gc, Rectangle rect) {

        if (rect.width == 0 || rect.height == 0) {
            return;
        }

        Rectangle rectangle = new Rectangle(rect.x, rect.y, rect.width, rect.height);

        int lineWidth = gc.getLineWidth();

        // check if it's a line
        int absWidth = Math.abs(rectangle.width);
        if (absWidth == 1) {
            // we're supposed to draw inside the rectangle and since it's a line
            // we don't want it to grow outside, so we force the line width at 1
            if (lineWidth != 1) {
                gc.setLineWidth(1);
            }
            drawLineVertical(gc, rectangle.x, rectangle.y, rectangle.height);
            if (lineWidth != 1) {
                gc.setLineWidth(lineWidth);
            }
            return;
        }
        int absHeight = Math.abs(rectangle.height);
        if (absHeight == 1) {
            // we're supposed to draw inside the rectangle and since it's a line
            // we don't want it to grow outside, so we force the line width at 1
            if (lineWidth != 1) {
                gc.setLineWidth(1);
            }
            drawLineHorizontal(gc, rectangle.x, rectangle.y, rectangle.width);
            if (lineWidth != 1) {
                gc.setLineWidth(lineWidth);
            }
            return;
        }

        // if the border is big enough to cover the whole rectangle interior we
        // simply draw a fill rectangle
        int minSide = Math.min(absWidth, absHeight);
        if (minSide <= (lineWidth * 2)) {
            Color originalBackground = gc.getBackground();
            gc.setBackground(gc.getForeground());
            fillRectangle(gc, rectangle);
            gc.setBackground(originalBackground);
            return;
        }

        // adjust the rectangle to make it internal
        if (rectangle.width > 0) {
            rectangle.x = rectangle.x + (lineWidth / 2);
            rectangle.width = rectangle.width - (lineWidth - 1);
        } else {
            rectangle.x = rectangle.x - ((lineWidth - 1) / 2);
            rectangle.width = rectangle.width + (lineWidth - 1);
        }

        if (rectangle.height > 0) {
            rectangle.y = rectangle.y + (lineWidth / 2);
            rectangle.height = rectangle.height - (lineWidth - 1);
        } else {
            rectangle.y = rectangle.y - ((lineWidth - 1) / 2);
            rectangle.height = rectangle.height + (lineWidth - 1);
        }

        drawRectangle(gc, rectangle);
    }

    /**
     * Draws a rectangle. The increased border thickness resulting from
     * {@link GC#getLineWidth} will be strictly drawn outside of the rectangle.
     * <p>
     * Unlike {@link GC#drawRectangle(Rectangle)}:
     * <ul>
     * <li>the width and height of the resulting rectangle will be always
     * exactly {@code rect.width} and {@code rect.height}
     * <li>the rectangle will always start at the given coordinates even in case
     * of negative width/height
     * </ul>
     *
     * @param gc
     *            the GC to use to draw
     * @param rect
     *            the rectangle to draw
     *
     * @see #drawRectangle(GC, Rectangle)
     * @see #drawRectangleBorderInternal
     * @since 1.5
     */
    public static void drawRectangleBorderExternal(GC gc, Rectangle rect) {

        if (rect.width == 0 || rect.height == 0) {
            return;
        }

        Rectangle rectangle = new Rectangle(rect.x, rect.y, rect.width, rect.height);

        int lineWidth = gc.getLineWidth();

        // adjust the rectangle to make it external
        if (rectangle.width > 0) {
            rectangle.x = rectangle.x - ((lineWidth - 1) / 2);
            rectangle.width = rectangle.width + (lineWidth - 1);
        } else {
            rectangle.x = rectangle.x + (lineWidth / 2);
            rectangle.width = rectangle.width - (lineWidth - 1);
        }

        if (rectangle.height > 0) {
            rectangle.y = rectangle.y - ((lineWidth - 1) / 2);
            rectangle.height = rectangle.height + (lineWidth - 1);
        } else {
            rectangle.y = rectangle.y + (lineWidth / 2);
            rectangle.height = rectangle.height - (lineWidth - 1);
        }

        drawRectangle(gc, rectangle);
    }

    /**
     * Returns the external bounds of the rectangle that would be obtained by
     * painting <code>rectangle</code> with <code>borderStyle</code> using one
     * of the <code>GraphicsUtils.drawRectangle...</code> methods.
     *
     * @param rectangle
     *            the rectangle to consider
     * @param borderStyle
     *            the border style to consider
     * @return the external bounds of what would be the resulting rectangle
     *
     * @since 1.5
     */
    public static Rectangle getResultingExternalBounds(Rectangle rectangle, BorderStyle borderStyle) {
        if (rectangle == null || borderStyle == null) {
            return null;
        }

        Rectangle resultingRectangle = new Rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);

        int borderWidth = borderStyle.getThickness();
        BorderModeEnum borderMode = borderStyle.getBorderMode();

        switch (borderMode) {
            case CENTERED:
                if (resultingRectangle.width > 0) {
                    resultingRectangle.x = resultingRectangle.x - (borderWidth / 2);
                    resultingRectangle.width = resultingRectangle.width + (borderWidth - 1);
                } else {
                    resultingRectangle.x = resultingRectangle.x + ((borderWidth - 1) / 2);
                    resultingRectangle.width = resultingRectangle.width - (borderWidth - 1);
                }

                if (resultingRectangle.height > 0) {
                    resultingRectangle.y = resultingRectangle.y - (borderWidth / 2);
                    resultingRectangle.height = resultingRectangle.height + (borderWidth - 1);
                } else {
                    resultingRectangle.y = resultingRectangle.y + ((borderWidth - 1) / 2);
                    resultingRectangle.height = resultingRectangle.height - (borderWidth - 1);
                }
                break;
            case EXTERNAL:
                if (resultingRectangle.width > 0) {
                    resultingRectangle.x = resultingRectangle.x - (borderWidth - 1);
                    resultingRectangle.width = resultingRectangle.width + ((borderWidth - 1) * 2);
                } else {
                    resultingRectangle.x = resultingRectangle.x + (borderWidth - 1);
                    resultingRectangle.width = resultingRectangle.width - ((borderWidth - 1) * 2);
                }

                if (resultingRectangle.height > 0) {
                    resultingRectangle.y = resultingRectangle.y - (borderWidth - 1);
                    resultingRectangle.height = resultingRectangle.height + ((borderWidth - 1) * 2);
                } else {
                    resultingRectangle.y = resultingRectangle.y + (borderWidth - 1);
                    resultingRectangle.height = resultingRectangle.height - ((borderWidth - 1) * 2);
                }
                break;
            case INTERNAL:
                // the external bounds remain the same
                break;
        }

        return resultingRectangle;
    }
}